2
2
# -*- coding: utf-8 -*-
3
3
#
4
4
# Copyright (c) 2025, Jeffrey van Pelt (@Thulium-Drake) <[email protected] >
5
+ # Copyright (c) 2025, Kevin Quick <[email protected] >
5
6
# 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
7
# SPDX-FileCopyrightText: (c) 2025, Jeffrey van Pelt (@Thulium-Drake) <[email protected] >
8
+ # SPDX-FileCopyrightText: (c) 2025, Kevin Quick <[email protected] >
7
9
# SPDX-License-Identifier: GPL-3.0-or-later
8
10
from __future__ import (absolute_import , division , print_function )
9
11
__metaclass__ = type
13
15
short_description: User management for Proxmox VE cluster
14
16
description:
15
17
- Create or delete a user for Proxmox VE clusters.
16
- author: "Jeffrey van Pelt (@Thulium-Drake) <[email protected] >"
18
+ author:
19
+ - Jeffrey van Pelt (@Thulium-Drake)
20
+ - Kevin Quick (@kevinquick)
17
21
version_added: "1.2.0"
18
22
attributes:
19
23
check_mode:
123
127
"""
124
128
125
129
from ansible .module_utils .basic import AnsibleModule
126
- from ansible_collections .community .proxmox .plugins .module_utils .proxmox import (proxmox_auth_argument_spec , ProxmoxAnsible )
130
+ from ansible_collections .community .proxmox .plugins .module_utils .proxmox import (
131
+ proxmox_auth_argument_spec , ProxmoxAnsible )
127
132
128
133
129
134
class ProxmoxUserAnsible (ProxmoxAnsible ):
@@ -132,18 +137,39 @@ def is_user_existing(self, userid):
132
137
"""Check whether user already exist
133
138
134
139
:param userid: str - name of the user
135
- :return: bool - is user exists?
140
+ :return: dict| bool - user data if exists, False otherwise
136
141
"""
137
142
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
+ user_data = self .proxmox_api .access .users (userid ).get ()
144
+ return user_data
143
145
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 ):
146
+ if "does not exist" in str (e ).lower () or "not found" in str (e ).lower ():
147
+ return False
148
+ else :
149
+ self .module .fail_json (msg = "Unable to retrieve user {0}: {1}" .format (userid , e ))
150
+
151
+ def _user_needs_update (self , existing_user , comment , email , enable , expire , firstname , lastname , groups , keys ):
152
+ """Check if user needs updating by comparing current vs desired state"""
153
+ # Check standard fields
154
+ fields = [('comment' , comment , '' ), ('email' , email , '' ), ('enable' , enable , 1 ),
155
+ ('expire' , expire , 0 ), ('firstname' , firstname , '' ),
156
+ ('lastname' , lastname , '' ), ('keys' , keys , '' )]
157
+
158
+ for field , new_value , default in fields :
159
+ if new_value is not None and existing_user .get (field , default ) != new_value :
160
+ return True
161
+
162
+ # Check groups (API returns list, we send comma-separated string)
163
+ if groups is not None :
164
+ existing_groups_str = ',' .join (existing_user .get ('groups' , []))
165
+ if existing_groups_str != groups :
166
+ return True
167
+
168
+ return False
169
+
170
+ def create_update_user (self , userid , comment = None , email = None , enable = True , expire = 0 ,
171
+ firstname = None , groups = None , password = None , keys = None ,
172
+ lastname = None ):
147
173
"""Create or update Proxmox VE user
148
174
149
175
:param userid: str - name of the user
@@ -158,30 +184,33 @@ def create_update_user(self, userid, comment=None, email=None, enable=True, expi
158
184
:param lastname: str, optional - Lastname of the user
159
185
:return: None
160
186
"""
161
-
162
187
# Translate input to make API happy
163
188
enable = int (enable )
164
- groups = ',' .join (groups )
165
-
166
- if self .is_user_existing (userid ):
189
+ groups = ',' .join (groups ) if groups else None
190
+ existing_user = self .is_user_existing (userid )
191
+ if existing_user :
192
+ needs_update = self ._user_needs_update (existing_user , comment , email , enable , expire ,
193
+ firstname , lastname , groups , keys )
194
+ if not needs_update and not password :
195
+ self .module .exit_json (changed = False , userid = userid , msg = "User {0} already up to date" .format (userid ))
167
196
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 ))
197
+ self .module .exit_json (changed = needs_update or bool (password ), userid = userid ,
198
+ msg = "Would update {0} (check mode)" .format (userid ))
182
199
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 ))
200
+ if needs_update :
201
+ try :
202
+ # Build update parameters - only include non-None values
203
+ update_params = {'enable' : enable }
204
+ for field , value in [('comment' , comment ), ('email' , email ), ('expire' , expire ),
205
+ ('firstname' , firstname ), ('lastname' , lastname ),
206
+ ('groups' , groups ), ('keys' , keys )]:
207
+ if value is not None :
208
+ update_params [field ] = value
209
+ self .proxmox_api .access .users (userid ).put (** update_params )
210
+ self .module .exit_json (changed = True , userid = userid , msg = "User {0} updated" .format (userid ))
211
+ except Exception as e :
212
+ self .module .fail_json (changed = False , userid = userid ,
213
+ msg = "Failed to update user with ID {0}: {1}" .format (userid , e ))
185
214
186
215
# We have no way of testing if the user's password needs to be changed
187
216
# so, if it's provided we will update it anyway
@@ -190,10 +219,11 @@ def create_update_user(self, userid, comment=None, email=None, enable=True, expi
190
219
self .proxmox_api .access .password .put (userid = userid , password = password )
191
220
self .module .exit_json (changed = True , userid = userid , msg = "User {0} updated" .format (userid ))
192
221
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 ))
222
+ self .module .fail_json (changed = False , userid = userid ,
223
+ msg = "Failed to update user password for user ID {0}: {1}" .format (userid , e ))
194
224
195
225
if self .module .check_mode :
196
- self .module .exit_json (changed = True , userid = userid , msg = "Would update user {0} (check mode)" .format (userid ))
226
+ self .module .exit_json (changed = True , userid = userid , msg = "Would create user {0} (check mode)" .format (userid ))
197
227
198
228
# if the user is new, post it to the API
199
229
try :
@@ -221,7 +251,8 @@ def delete_user(self, userid):
221
251
self .module .exit_json (changed = False , userid = userid , msg = "User {0} doesn't exist" .format (userid ))
222
252
223
253
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 ))
254
+ self .module .exit_json (changed = False , userid = userid ,
255
+ msg = "Would deleted user with ID {0} (check mode)" .format (userid ))
225
256
226
257
try :
227
258
self .proxmox_api .access .users (userid ).delete ()
@@ -269,12 +300,15 @@ def main():
269
300
270
301
proxmox = ProxmoxUserAnsible (module )
271
302
303
+ # Convert empty strings to None for proper comparison
304
+ for param in ['comment' , 'email' , 'firstname' , 'lastname' , 'keys' ]:
305
+ if locals ()[param ] == "" :
306
+ locals ()[param ] = None
307
+
272
308
if state == "present" :
273
309
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
310
else :
276
311
proxmox .delete_user (userid )
277
- module .exit_json (changed = True , userid = userid , msg = "User {0} successfully deleted" .format (userid ))
278
312
279
313
280
314
if __name__ == "__main__" :
0 commit comments