Skip to content

Commit

Permalink
feat: user check mode (#208)
Browse files Browse the repository at this point in the history
* feat: working on adding checkmode and diff support for user

* feat: added method to calculate diff for objects

* chore: reverted small changes

* fix: use existing values for diff if none are set to avoid changes in output, add tests

* test: fix s3 key test

* test: fix user test

* chore: user return for check_mode for clarity

* doc: added docs for check_mode and diff, fix typo, added to summary
  • Loading branch information
rmocanu-ionos authored Aug 27, 2024
1 parent 082cf96 commit 9009ac8
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 3 deletions.
2 changes: 2 additions & 0 deletions docs/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
* [Ansible Playbooks](usage/ansibleplaybooks.md)
* [Wait for Services](usage/waitforservices.md)
* [Incrementing servers](usage/incrementingservers.md)
* [Check Mode and Diff](usage/check_mode_and_diff.md)
* [SSH Key Authentication](usage/sshkeyauthentication.md)
* [Return values](usage/returnvalues.md)
* [Testing](usage/testing.md)
* [Declarative Changes](usage/declarative_changes.md)

## Tutorials
* [Tutorials introduction](tutorials/README.md)
Expand Down
57 changes: 57 additions & 0 deletions plugins/module_utils/common_ionos_module.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import yaml

from ansible.module_utils._text import to_native

from .common_ionos_methods import (
Expand Down Expand Up @@ -34,6 +36,18 @@ def _should_update_object(self, existing_object, clients):
"""
pass

def calculate_object_diff(self, existing_object, clients):
"""
Calculate before and after for the object, only used in diff mode
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
"""
pass


def _get_object_list(self, clients):
"""
Expand Down Expand Up @@ -67,25 +81,51 @@ 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 = {}
if module._diff:
module_diff = self.calculate_object_diff(existing_object, clients)

if self._should_replace_object(existing_object, clients):

if not module.params.get('allow_replace'):
module.fail_json(msg="{} should be replaced but allow_replace is set to False.".format(self.object_name))

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,
}

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,
}

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,
}

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

Expand All @@ -94,6 +134,7 @@ def update_replace_object(self, existing_object, clients):
'changed': False,
'failed': False,
'action': 'create',
'diff': module_diff,
self.returned_key: existing_object.to_dict()
}

Expand All @@ -107,6 +148,14 @@ def present_object(self, clients):
if existing_object:
return self.update_replace_object(existing_object, clients)

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(),
)
}

return {
'changed': True,
'failed': False,
Expand Down Expand Up @@ -159,6 +208,14 @@ def absent_object(self, clients):
self.module.exit_json(changed=False)
return

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(),
)
}

self._remove_object(existing_object, clients)

return {
Expand Down
24 changes: 23 additions & 1 deletion plugins/modules/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@
class UserModule(CommonIonosModule):
def __init__(self) -> None:
super().__init__()
self.module = AnsibleModule(argument_spec=get_module_arguments(OPTIONS, STATES))
self.module = AnsibleModule(argument_spec=get_module_arguments(OPTIONS, STATES), supports_check_mode=True)
self.returned_key = RETURNED_KEY
self.object_name = OBJECT_NAME
self.sdks = [ionoscloud]
Expand Down Expand Up @@ -311,6 +311,28 @@ def _should_update_object(self, existing_object, clients):
and 'groups' not in ignored_properties
)

def calculate_object_diff(self, existing_object, clients):
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',
}
}


def _get_object_list(self, clients):
all_users = ionoscloud.Users(items=[])
Expand Down
4 changes: 2 additions & 2 deletions reset.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ansible-galaxy collection build . --force
ansible-galaxy collection install ionoscloudsdk-ionoscloud-7.3.0.tar.gz --force
rm ionoscloudsdk-ionoscloud-7.3.0.tar.gz
ansible-galaxy collection install ionoscloudsdk-ionoscloud-7.4.1.tar.gz --force
rm ionoscloudsdk-ionoscloud-7.4.1.tar.gz
111 changes: 111 additions & 0 deletions tests/user-management/user-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,102 @@
user_password: "{{ password }}"
force_sec_auth: false
state: present
check_mode: true
diff: true
register: user_response

- name: Asserting that check_mode and diff work correctly
assert:
that:
- user_response.msg == "User {{ random_user }} would be created"
- user_response.diff is not defined
msg: "check_mode and diff don't work correctly"

- name: Create user
ionoscloudsdk.ionoscloud.user:
firstname: John
lastname: Doe
email: "{{ random_user }}"
administrator: false
user_password: "{{ password }}"
force_sec_auth: false
state: present

- name: Create user
ionoscloudsdk.ionoscloud.user:
firstname: John
lastname: Doe
email: "{{ random_user }}"
administrator: false
user_password: "{{ password }}"
force_sec_auth: false
state: present
check_mode: true
diff: false
register: user_response

- name: Asserting that check_mode and diff work correctly
assert:
that:
- user_response.msg == "User {{ random_user }} would be updated"
- user_response.diff == {}
msg: "check_mode and diff don't work correctly"

- name: Create user
ionoscloudsdk.ionoscloud.user:
firstname: John
lastname: Doe
email: "{{ random_user }}"
administrator: false
user_password: "{{ password }}"
force_sec_auth: false
state: present
check_mode: true
diff: true
register: user_response

- name: Asserting that check_mode and diff work correctly
assert:
that:
- user_response.msg == "User {{ random_user }} would be updated"
- user_response.diff.before.administrator == user_response.diff.after.administrator == false
- user_response.diff.before.email == user_response.diff.after.email == "{{ random_user }}"
- user_response.diff.before.firstname == user_response.diff.after.firstname == 'John'
- user_response.diff.before.lastname == user_response.diff.after.lastname == 'Doe'
- user_response.diff.before.groups == user_response.diff.after.groups == ''
- user_response.diff.before.user_password != user_response.diff.after.user_password
- user_response.diff.before.user_password == ''
- user_response.diff.after.user_password == 'user password will be updated'
msg: "check_mode and diff don't work correctly"

- name: Create user
ionoscloudsdk.ionoscloud.user:
firstname: John changed
lastname: Doe changed
email: "{{ random_user }}"
administrator: true
user_password: "{{ password }}"
force_sec_auth: false
state: present
check_mode: true
diff: true
register: user_response

- name: Asserting that check_mode and diff work correctly 2
assert:
that:
- user_response.msg == "User {{ random_user }} would be updated"
- user_response.diff.before.administrator == false
- user_response.diff.after.administrator == true
- user_response.diff.before.email == user_response.diff.after.email == "{{ random_user }}"
- user_response.diff.before.firstname == 'John'
- user_response.diff.after.firstname == 'John changed'
- user_response.diff.before.lastname == 'Doe'
- user_response.diff.after.lastname == 'Doe changed'
- user_response.diff.before.groups == user_response.diff.after.groups == ''
- user_response.diff.before.user_password == ''
- user_response.diff.after.user_password == 'user password will be updated'
msg: "check_mode and diff don't work correctly"

- name: Test ignored fields
ionoscloudsdk.ionoscloud.user:
Expand Down Expand Up @@ -176,6 +272,21 @@
groups: []
state: update

- name: Delete user
ionoscloudsdk.ionoscloud.user:
user: "{{ random_user }}"
state: absent
check_mode: true
diff: true
register: user_response

- name: Asserting that check_mode and diff work correctly
assert:
that:
- user_response.msg == "User {{ random_user }} would be deleted"
- user_response.diff is not defined
msg: "check_mode and diff don't work correctly"

- name: Delete user
ionoscloudsdk.ionoscloud.user:
user: "{{ random_user }}"
Expand Down

0 comments on commit 9009ac8

Please sign in to comment.