Skip to content

Commit 5112d89

Browse files
new module: proxmox_user: management of users (#127)
* Initial version, without group module committed too... :-) * Fix more sanity * Fix more sanity * Fix more sanity * Fix more sanity * Fix more sanity
1 parent 2a156c8 commit 5112d89

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

meta/runtime.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ action_groups:
3131
- proxmox_storage_info
3232
- proxmox_tasks_info
3333
- proxmox_template
34+
- proxmox_user
3435
- proxmox_user_info
3536
- proxmox_vm_info

plugins/modules/proxmox_user.py

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright (c) 2025, Jeffrey van Pelt (@Thulium-Drake) <[email protected]>
5+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
6+
# SPDX-FileCopyrightText: (c) 2025, Jeffrey van Pelt (@Thulium-Drake) <[email protected]>
7+
# SPDX-License-Identifier: GPL-3.0-or-later
8+
from __future__ import (absolute_import, division, print_function)
9+
__metaclass__ = type
10+
11+
DOCUMENTATION = r"""
12+
module: proxmox_user
13+
short_description: User management for Proxmox VE cluster
14+
description:
15+
- Create or delete a user for Proxmox VE clusters.
16+
author: "Jeffrey van Pelt (@Thulium-Drake) <[email protected]>"
17+
version_added: "1.2.0"
18+
attributes:
19+
check_mode:
20+
support: full
21+
diff_mode:
22+
support: none
23+
options:
24+
userid:
25+
description:
26+
- The user name.
27+
- Must include the desired PVE authentication realm.
28+
type: str
29+
aliases: ["name"]
30+
required: true
31+
state:
32+
description:
33+
- Indicate desired state of the user.
34+
choices: ['present', 'absent']
35+
default: present
36+
type: str
37+
comment:
38+
description:
39+
- Specify the description for the user.
40+
type: str
41+
enable:
42+
description:
43+
- Whether or not the account is active.
44+
type: bool
45+
default: true
46+
email:
47+
description:
48+
- Email address for the user.
49+
type: str
50+
expire:
51+
description:
52+
- Expiration date of the user in seconds after epoch.
53+
- 0 means no expiration date.
54+
type: int
55+
default: 0
56+
firstname:
57+
description:
58+
- First name of the user.
59+
type: str
60+
lastname:
61+
description:
62+
- Last name of the user.
63+
type: str
64+
groups:
65+
description:
66+
- List of groups the user is a member of.
67+
type: list
68+
elements: str
69+
keys:
70+
description:
71+
- Keys for two factor authentication (yubico).
72+
type: str
73+
password:
74+
description:
75+
- Initial password.
76+
- Only for PVE Authentication Realm users.
77+
- Parameter is ignored when user already exists or O(state=absent).
78+
type: str
79+
80+
extends_documentation_fragment:
81+
- community.proxmox.proxmox.actiongroup_proxmox
82+
- community.proxmox.proxmox.documentation
83+
- community.proxmox.attributes
84+
"""
85+
86+
EXAMPLES = r"""
87+
- name: Create new Proxmox VE user
88+
community.proxmox.proxmox_user:
89+
api_host: node1
90+
api_user: root@pam
91+
api_password: password
92+
name: user@pve
93+
comment: Expires on 2026-01-01 00:00:00
94+
95+
enable: true
96+
expire: 1767222000
97+
firstname: User
98+
groups:
99+
- admins
100+
password: GoBananas!
101+
lastname: Some Guy
102+
103+
- name: Delete a Proxmox VE user
104+
community.proxmox.proxmox_user:
105+
api_host: node1
106+
api_user: root@pam
107+
api_password: password
108+
name: user@pve
109+
state: absent
110+
"""
111+
112+
RETURN = r"""
113+
userid:
114+
description: The user name.
115+
returned: success
116+
type: str
117+
sample: test
118+
msg:
119+
description: A short message on what the module did.
120+
returned: always
121+
type: str
122+
sample: "User administrators successfully created"
123+
"""
124+
125+
from ansible.module_utils.basic import AnsibleModule
126+
from ansible_collections.community.proxmox.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
127+
128+
129+
class ProxmoxUserAnsible(ProxmoxAnsible):
130+
131+
def is_user_existing(self, userid):
132+
"""Check whether user already exist
133+
134+
:param userid: str - name of the user
135+
:return: bool - is user exists?
136+
"""
137+
try:
138+
users = self.proxmox_api.access.users.get()
139+
for user in users:
140+
if user['userid'] == userid:
141+
return user
142+
return False
143+
except Exception as e:
144+
self.module.fail_json(msg="Unable to retrieve users: {0}".format(e))
145+
146+
def create_update_user(self, userid, comment=None, email=None, enable=True, expire=0, firstname=None, groups=None, password=None, keys=None, lastname=None):
147+
"""Create or update Proxmox VE user
148+
149+
:param userid: str - name of the user
150+
:param comment: str, optional - Description of a user
151+
:param email: str, optional - Email of the user
152+
:param enable: bool, optional - Whether or not user is active
153+
:param expire: str, optional - Expiration date of the user
154+
:param firstname: str, optional - First name of the user
155+
:param groups: list, optional - Groups that the user should be a member of
156+
:param password: str, optional - Password of the user, PVE realm only
157+
:param keys: str, optional - 2FA keys for the user
158+
:param lastname: str, optional - Lastname of the user
159+
:return: None
160+
"""
161+
162+
# Translate input to make API happy
163+
enable = int(enable)
164+
groups = ','.join(groups)
165+
166+
if self.is_user_existing(userid):
167+
if self.module.check_mode:
168+
self.module.exit_json(changed=False, userid=userid, msg="Would update {0} (check mode)".format(userid))
169+
170+
# Update the user details
171+
try:
172+
self.proxmox_api.access.users(userid).put(comment=comment,
173+
email=email,
174+
enable=enable,
175+
expire=expire,
176+
firstname=firstname,
177+
groups=groups,
178+
keys=keys,
179+
lastname=lastname)
180+
181+
self.module.exit_json(changed=True, userid=userid, msg="User {0} updated".format(userid))
182+
183+
except Exception as e:
184+
self.module.fail_json(changed=False, userid=userid, msg="Failed to update user with ID {0}: {1}".format(userid, e))
185+
186+
# We have no way of testing if the user's password needs to be changed
187+
# so, if it's provided we will update it anyway
188+
if password:
189+
try:
190+
self.proxmox_api.access.password.put(userid=userid, password=password)
191+
self.module.exit_json(changed=True, userid=userid, msg="User {0} updated".format(userid))
192+
except Exception as e:
193+
self.module.fail_json(changed=False, userid=userid, msg="Failed to update user password for user ID {0}: {1}".format(userid, e))
194+
195+
if self.module.check_mode:
196+
self.module.exit_json(changed=True, userid=userid, msg="Would update user {0} (check mode)".format(userid))
197+
198+
# if the user is new, post it to the API
199+
try:
200+
self.proxmox_api.access.users.post(userid=userid,
201+
comment=comment,
202+
email=email,
203+
enable=enable,
204+
expire=expire,
205+
firstname=firstname,
206+
groups=groups,
207+
password=password,
208+
keys=keys,
209+
lastname=lastname)
210+
self.module.exit_json(changed=True, userid=userid, msg="Created user {0}".format(userid))
211+
except Exception as e:
212+
self.module.fail_json(msg="Failed to create user with ID {0}: {1}".format(userid, e))
213+
214+
def delete_user(self, userid):
215+
"""Delete Proxmox VE user
216+
217+
:param userid: str - name of the user
218+
:return: None
219+
"""
220+
if not self.is_user_existing(userid):
221+
self.module.exit_json(changed=False, userid=userid, msg="User {0} doesn't exist".format(userid))
222+
223+
if self.module.check_mode:
224+
self.module.exit_json(changed=False, userid=userid, msg="Would deleted user with ID {0} (check mode)".format(userid))
225+
226+
try:
227+
self.proxmox_api.access.users(userid).delete()
228+
self.module.exit_json(changed=True, userid=userid, msg="Deleted user with ID {0}".format(userid))
229+
except Exception as e:
230+
self.module.fail_json(msg="Failed to delete user with ID {0}: {1}".format(userid, e))
231+
232+
233+
def main():
234+
module_args = proxmox_auth_argument_spec()
235+
users_args = dict(
236+
userid=dict(type="str", aliases=["name"], required=True),
237+
comment=dict(type="str"),
238+
email=dict(type="str"),
239+
enable=dict(default=True, type="bool"),
240+
expire=dict(default=0, type="int"),
241+
firstname=dict(type="str"),
242+
groups=dict(type="list", elements="str"),
243+
lastname=dict(type="str"),
244+
keys=dict(type="str", no_log=True),
245+
password=dict(type="str", no_log=True),
246+
state=dict(default="present", choices=["present", "absent"]),
247+
)
248+
249+
module_args.update(users_args)
250+
251+
module = AnsibleModule(
252+
argument_spec=module_args,
253+
required_together=[("api_token_id", "api_token_secret")],
254+
required_one_of=[("api_password", "api_token_id")],
255+
supports_check_mode=True
256+
)
257+
258+
userid = module.params["userid"]
259+
comment = module.params["comment"]
260+
email = module.params["email"]
261+
enable = module.params["enable"]
262+
expire = module.params["expire"]
263+
firstname = module.params["firstname"]
264+
groups = module.params["groups"]
265+
lastname = module.params["lastname"]
266+
keys = module.params["keys"]
267+
password = module.params["password"]
268+
state = module.params["state"]
269+
270+
proxmox = ProxmoxUserAnsible(module)
271+
272+
if state == "present":
273+
proxmox.create_update_user(userid, comment, email, enable, expire, firstname, groups, password, keys, lastname)
274+
module.exit_json(changed=True, userid=userid, msg="User {0} successfully created".format(userid))
275+
else:
276+
proxmox.delete_user(userid)
277+
module.exit_json(changed=True, userid=userid, msg="User {0} successfully deleted".format(userid))
278+
279+
280+
if __name__ == "__main__":
281+
main()

0 commit comments

Comments
 (0)