Skip to content

Commit

Permalink
feat: return diff for present and update, return object state after u… (
Browse files Browse the repository at this point in the history
#210)

* feat: return diff for present and update, return object state after update in check mode

* feat: return user object in check_mode in present, state, add tests

* fix: get_users_by_identifier was ignoring the depth argument

* test: fix tests, added more tests, updated example in docs

* fix: check case when existing object is none in present state

* [no ci] doc: added note in checkmode docs

* [no ci] update module version

* [no ci] updated changelog
  • Loading branch information
rmocanu-ionos authored Sep 12, 2024
1 parent f9af922 commit e380da4
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 78 deletions.
11 changes: 11 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 7.5.0
### Features
* Added check_mode and diff to the user module
* Added ignored_properties to the user module
### Changes
* use filters when retrieving user to speed up the request
### Fixes
* mark RegEx pattern as a raw string (fix by @jaudriga)
### Docs
* Added declarative modules page to summary

## 7.5.0-beta.1
### Features
* Added check_mode and diff to the user module
Expand Down
15 changes: 14 additions & 1 deletion docs/usage/check_mode_and_diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Read bellow for info on how the IONOS Ansible module handles check mode and diff
### Check Mode
When using check_mode the playbook will not make changes in the API and a message will be returned if such changes would have been made. Example: "user <email> would be updated.". The returned state is "changed".
For operations that do not cause changes the regular response will be returned.
> **_NOTE:_** Please note that the value returned when using check mode is not exactly the same as what is returned by the server and even though check mode is passing without error, errors may occur when making the api calls.
### Diff
When using diff an additional property will be returned on the object showing the object states with before and after. These states only include the attributes which are checked by Ansible for update and recreate.
Expand Down Expand Up @@ -59,6 +60,18 @@ Output:
}
},
"failed": false,
"msg": "User <email> would be updated"
"msg": "User <email> would be updated",
"user": {
"id": "<user-id>",
"properties": {
"administrator": false,
"email": "<email>",
"firstname": "John",
"force_sec_auth": false,
"groups": "",
"lastname": "Doe",
"user_password": "user password will be updated"
}
}
}
}
2 changes: 1 addition & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace: ionoscloudsdk
name: ionoscloud

# The version of the collection.
version: 7.5.0-beta.1
version: 7.5.0

readme: README.md

Expand Down
2 changes: 1 addition & 1 deletion plugins/module_utils/common_ionos_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def get_users_by_identifier(api, all_users, identifier_value, depth=2):
# check if identifier_value is a UUID then try to get the user
try:
uuid.UUID(identifier_value)
user = api.um_users_find_by_id(identifier_value, depth=2)
user = api.um_users_find_by_id(identifier_value, depth=depth)
all_users.items += [user]
except Exception as e:
pass
Expand Down
160 changes: 115 additions & 45 deletions plugins/module_utils/common_ionos_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,29 @@ def _should_update_object(self, existing_object, clients):
"""
pass

def calculate_object_diff(self, existing_object, clients):

def get_object_before(self, existing_object, clients):
"""
Return a dict with the 'before' state for the object
existing_object : Ionoscloud object returned by API object
clients: authenticated ionoscloud clients list.
Returns:
dict, a dict with the object properties before the task is executed
"""
pass


def get_object_after(self, existing_object, clients):
"""
Calculate before and after for the object, only used in diff mode
Return a dict with the 'after' state for the object
existing_object : Ionoscloud object returned by API object
clients: authenticated ionoscloud clients list.
Returns:
dict, a dict with 2 keys: 'before' and 'after' which compares only the attributes watched by ansible in their states
dict, a dict with the object properties after the task is executed
"""
pass

Expand Down Expand Up @@ -82,9 +96,15 @@ def _get_object_identifier(self):
def update_replace_object(self, existing_object, clients):
module = self.module
obj_identifier = self._get_object_identifier() if self._get_object_identifier() is not None else self._get_object_name()
module_diff = {}
object_after = self.get_object_after(existing_object, clients)

returned_json = {}

if module._diff:
module_diff = self.calculate_object_diff(existing_object, clients)
returned_json['diff'] = {
'before': self.get_object_before(existing_object, clients),
'after': object_after,
}

if self._should_replace_object(existing_object, clients):

Expand All @@ -93,49 +113,67 @@ def update_replace_object(self, existing_object, clients):

if module.check_mode:
return {
'changed': True,
'msg': '{object_name} {object_name_identifier} would be recreated'.format(
object_name=self.object_name, object_name_identifier=obj_identifier,
),
'diff': module_diff,
**returned_json,
**{
'changed': True,
'msg': '{object_name} {object_name_identifier} would be recreated'.format(
object_name=self.object_name, object_name_identifier=obj_identifier,
),
self.returned_key: {
'id': existing_object.id,
'properties': object_after,
},
},
}

new_object = self._create_object(existing_object, clients).to_dict()
self._remove_object(existing_object, clients)
return {
'changed': True,
'failed': False,
'action': 'create',
'diff': module_diff,
self.returned_key: new_object,
**returned_json,
**{
'changed': True,
'failed': False,
'action': 'create',
self.returned_key: new_object,
},
}

if self._should_update_object(existing_object, clients):
if module.check_mode:
return {
'changed': True,
'msg': '{object_name} {object_name_identifier} would be updated'.format(
object_name=self.object_name, object_name_identifier=obj_identifier,
),
'diff': module_diff,
**returned_json,
**{
'changed': True,
'msg': '{object_name} {object_name_identifier} would be updated'.format(
object_name=self.object_name, object_name_identifier=obj_identifier,
),
self.returned_key: {
'id': existing_object.id,
'properties': object_after,
},
},
}

# Update
return {
'changed': True,
'failed': False,
'action': 'update',
'diff': module_diff,
self.returned_key: self._update_object(existing_object, clients).to_dict()
**returned_json,
**{
'changed': True,
'failed': False,
'action': 'update',
self.returned_key: self._update_object(existing_object, clients).to_dict(),
},
}

# No action
return {
'changed': False,
'failed': False,
'action': 'create',
'diff': module_diff,
self.returned_key: existing_object.to_dict()
**returned_json,
**{
'changed': False,
'failed': False,
'action': 'create',
self.returned_key: existing_object.to_dict(),
},
}


Expand All @@ -148,19 +186,37 @@ def present_object(self, clients):
if existing_object:
return self.update_replace_object(existing_object, clients)

returned_json = {}
object_after = self.get_object_after(existing_object, clients)
if self.module._diff:
returned_json['diff'] = {
'before': {},
'after': object_after,
}

if self.module.check_mode:
return {
'skipped': True,
'msg': '{object_name} {object_name_identifier} would be created'.format(
object_name=self.object_name, object_name_identifier=self._get_object_name(),
)
**returned_json,
**{
'changed': True,
'msg': '{object_name} {object_name_identifier} would be created'.format(
object_name=self.object_name, object_name_identifier=self._get_object_name(),
),
self.returned_key: {
'id': '<known after creation>',
'properties': object_after,
},
},
}

return {
'changed': True,
'failed': False,
'action': 'create',
self.returned_key: self._create_object(None, clients).to_dict()
**returned_json,
**{
'changed': True,
'failed': False,
'action': 'create',
self.returned_key: self._create_object(None, clients).to_dict(),
},
}


Expand Down Expand Up @@ -208,20 +264,34 @@ def absent_object(self, clients):
self.module.exit_json(changed=False)
return

returned_json = {}

if self.module._diff:
returned_json['diff'] = {
'before': self.get_object_before(existing_object, clients),
'after': {},
}

if self.module.check_mode:
return {
'skipped': True,
'msg': '{object_name} {object_name_identifier} would be deleted'.format(
object_name=self.object_name, object_name_identifier=self._get_object_identifier(),
)
**returned_json,
**{
'changed': True,
'msg': '{object_name} {object_name_identifier} would be deleted'.format(
object_name=self.object_name, object_name_identifier=self._get_object_identifier(),
),
},
}

self._remove_object(existing_object, clients)

return {
'action': 'delete',
'changed': True,
'id': existing_object.id,
**returned_json,
**{
'action': 'delete',
'changed': True,
'id': existing_object.id,
},
}


Expand Down
45 changes: 26 additions & 19 deletions plugins/modules/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,26 +323,33 @@ def _should_update_object(self, existing_object, clients):
and 'groups' not in ignored_properties
)

def calculate_object_diff(self, existing_object, clients):

def get_object_before(self, existing_object, clients):
return {
'lastname': existing_object.properties.lastname,
'firstname': existing_object.properties.firstname,
'email': existing_object.properties.email,
'administrator': existing_object.properties.administrator,
'force_sec_auth': existing_object.properties.force_sec_auth,
'user_password': '',
'groups': '',
}


def get_object_after(self, existing_object, clients):
try:
object_properties = existing_object.properties
except AttributeError:
object_properties = ionoscloud.UserProperties()

return {
'before': {
'lastname': existing_object.properties.lastname,
'firstname': existing_object.properties.firstname,
'email': existing_object.properties.email,
'administrator': existing_object.properties.administrator,
'force_sec_auth': existing_object.properties.force_sec_auth,
'user_password': '',
'groups': '',
},
'after': {
'lastname': existing_object.properties.lastname if self.module.params.get('lastname') is None else self.module.params.get('lastname'),
'firstname': existing_object.properties.firstname if self.module.params.get('firstname') is None else self.module.params.get('firstname'),
'email': existing_object.properties.email if self.module.params.get('email') is None else self.module.params.get('email'),
'administrator': existing_object.properties.administrator if self.module.params.get('administrator') is None else self.module.params.get('administrator'),
'force_sec_auth': existing_object.properties.force_sec_auth if self.module.params.get('force_sec_auth') is None else self.module.params.get('force_sec_auth'),
'user_password': '' if self.module.params.get('user_password') is None else 'user password will be updated',
'groups': '' if self.module.params.get('groups') is None else 'user groups will be updated',
}
'lastname': object_properties.lastname if self.module.params.get('lastname') is None else self.module.params.get('lastname'),
'firstname': object_properties.firstname if self.module.params.get('firstname') is None else self.module.params.get('firstname'),
'email': object_properties.email if self.module.params.get('email') is None else self.module.params.get('email'),
'administrator': object_properties.administrator if self.module.params.get('administrator') is None else self.module.params.get('administrator'),
'force_sec_auth': object_properties.force_sec_auth if self.module.params.get('force_sec_auth') is None else self.module.params.get('force_sec_auth'),
'user_password': '' if self.module.params.get('user_password') is None else 'user password will be updated',
'groups': '' if self.module.params.get('groups') is None else 'user groups will be updated',
}


Expand Down
Loading

0 comments on commit e380da4

Please sign in to comment.