From 6db6c6c5baa0a8b49acf702c4a2065e1962ddbc5 Mon Sep 17 00:00:00 2001 From: beeankha Date: Mon, 16 Mar 2020 11:18:08 -0400 Subject: [PATCH] Revert module util changes, reorder params in group module --- .../plugins/module_utils/tower_api.py | 209 ++++++++---------- awx_collection/plugins/modules/tower_group.py | 12 +- 2 files changed, 104 insertions(+), 117 deletions(-) diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index e84c29a363..e395f53fe9 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -111,7 +111,7 @@ class TowerModule(AnsibleModule): except ConfigFileException: self.fail_json('The config file {0} is not properly formatted'.format(config_file)) - # If we have a specified tower config, load it + # If we have a specified tower config, load it if self.params.get('tower_config_file'): duplicated_params = [] for direct_field in ('tower_host', 'tower_username', 'tower_password', 'validate_certs', 'tower_oauthtoken'): @@ -126,7 +126,7 @@ class TowerModule(AnsibleModule): # TODO: warn if there are conflicts with other params self.load_config(self.params.get('tower_config_file')) except ConfigFileException as cfe: - # Since we were told specifically to load this, we want it to fail if we have an error + # Since we were told specifically to load this we want it to fail if we have an error self.fail_json(msg=cfe) def load_config(self, config_path): @@ -164,7 +164,7 @@ class TowerModule(AnsibleModule): else: config.readfp(placeholder_file) - # If we made it to this point, then we have values from reading the ini file, so let's pull them out into a dict + # If we made it here then we have values from reading the ini file, so let's pull them out into a dict config_data = {} for honorred_setting in self.honorred_settings: try: @@ -262,7 +262,7 @@ class TowerModule(AnsibleModule): if response is not None: return name_or_id except ValueError: - # If we got a value error, then we didn't have an integer so we can just pass and fall down to the fail + # If we got a value error than we didn't have an integer so we can just pass and fall down to the fail pass self.fail_json(msg="The {0} {1} was not found on the Tower server".format(endpoint, name_or_id)) @@ -270,7 +270,7 @@ class TowerModule(AnsibleModule): self.fail_json(msg="Found too many names {0} at endpoint {1} try using an ID instead of a name".format(name_or_id, endpoint)) def make_request(self, method, endpoint, *args, **kwargs): - # In case someone is calling this directly; make sure we were given a method, let's not just assume a GET + # In case someone is calling us directly; make sure we were given a method, let's not just assume a GET if not method: raise Exception("The HTTP method must be defined") @@ -290,7 +290,7 @@ class TowerModule(AnsibleModule): # This method will set a cookie in the cookie jar for us self.authenticate(**kwargs) if self.oauth_token: - # If we have an oauth token, we just use a bearer header + # If we have a oauth token, we just use a bearer header headers['Authorization'] = 'Bearer {0}'.format(self.oauth_token) # Update the URL path with the endpoint @@ -379,7 +379,7 @@ class TowerModule(AnsibleModule): "application": None, "scope": "write", } - # Post to the tokens endpoint with basic auth to try and get a token + # Post to the tokens endpoint with baisc auth to try and get a token api_token_url = (self.url._replace(path='/api/v2/tokens/')).geturl() try: @@ -414,14 +414,14 @@ class TowerModule(AnsibleModule): except(Exception) as excinfo: self.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo)) - def delete_if_needed(self, existing_item, on_delete=None): - # This will exit from the module on its own. - # If the method successfully deletes an item and on_delete param is defined, + def delete_if_needed(self, existing_item, handle_response=True, on_delete=None): + # This will exit from the module on its own unless handle_response is False. + # If handle_response is True and the method successfully deletes an item and on_delete param is defined, # the on_delete parameter will be called as a method pasing in this object and the json from the response - # This will return one of two things: + # If you pass handle_response=False, it will return one of two things: # 1. None if the existing_item is not defined (so no delete needs to happen) # 2. The response from Tower from calling the delete on the endpont. It's up to you to process the response and exit from the module - # Note: common error codes from the Tower API can cause the module to fail + # Note: common error codes from the Tower API can cause the module to fail even if handle_response is set to False if existing_item: # If we have an item, we can try to delete it try: @@ -440,7 +440,9 @@ class TowerModule(AnsibleModule): response = self.delete_endpoint(item_url) - if response['status_code'] in [202, 204]: + if not handle_response: + return response + elif response['status_code'] in [202, 204]: if on_delete: on_delete(self, response['json']) self.json_output['changed'] = True @@ -458,40 +460,21 @@ class TowerModule(AnsibleModule): else: self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['status_code'])) else: - self.exit_json(**self.json_output) - - def modify_associations(self, association_endpoint, new_association_list): - # First, get the existing associations - response = self.get_all_endpoint(association_endpoint) - existing_associated_ids = [association['id'] for association in response['json']['results']] - - # Disassociate anything that is in existing_associated_ids but not in new_association_list - ids_to_remove = list(set(existing_associated_ids) - set(new_association_list)) - for an_id in ids_to_remove: - response = self.post_endpoint(association_endpoint, **{'data': {'id': int(an_id), 'disassociate': True}}) - if response['status_code'] == 204: - self.json_output['changed'] = True + if not handle_response: + return None else: - self.fail_json(msg="Failed to disassociate item {0}".format(response['json']['detail'])) + self.exit_json(**self.json_output) - # Associate anything that is in new_association_list but not in `association` - for an_id in list(set(new_association_list) - set(existing_associated_ids)): - response = self.post_endpoint(association_endpoint, **{'data': {'id': int(an_id)}}) - if response['status_code'] == 204: - self.json_output['changed'] = True - else: - self.fail_json(msg="Failed to associate item {0}".format(response['json']['detail'])) - - def create_if_needed(self, existing_item, new_item, endpoint, on_create=None, item_type='unknown', associations=None): - - # This will exit from the module on its own - # If the method successfully creates an item and on_create param is defined, + def create_if_needed(self, existing_item, new_item, endpoint, handle_response=True, on_create=None, item_type='unknown'): + # + # This will exit from the module on its own unless handle_response is False. + # If handle_response is True and the method successfully creates an item and on_create param is defined, # the on_create parameter will be called as a method pasing in this object and the json from the response - # This will return one of two things: + # If you pass handle_response=False it will return one of two things: # 1. None if the existing_item is already defined (so no create needs to happen) # 2. The response from Tower from calling the patch on the endpont. It's up to you to process the response and exit from the module - # Note: common error codes from the Tower API can cause the module to fail - + # Note: common error codes from the Tower API can cause the module to fail even if handle_response is set to False + # if not endpoint: self.fail_json(msg="Unable to create new {0} due to missing endpoint".format(item_type)) @@ -499,7 +482,11 @@ class TowerModule(AnsibleModule): try: existing_item['url'] except KeyError as ke: - self.fail_json(msg="Unable to process create of item due to missing data {0}".format(ke)) + self.fail_json(msg="Unable to process delete of item due to missing data {0}".format(ke)) + if not handle_response: + return None + else: + self.exit_json(**self.json_output) else: # If we don't have an exisitng_item, we can try to create it @@ -508,7 +495,9 @@ class TowerModule(AnsibleModule): item_name = new_item.get('name', 'unknown') response = self.post_endpoint(endpoint, **{'data': new_item}) - if response['status_code'] == 201: + if not handle_response: + return response + elif response['status_code'] == 201: self.json_output['name'] = 'unknown' if 'name' in response['json']: self.json_output['name'] = response['json']['name'] @@ -517,6 +506,10 @@ class TowerModule(AnsibleModule): self.json_output['name'] = response['json']['username'] self.json_output['id'] = response['json']['id'] self.json_output['changed'] = True + if on_create is None: + self.exit_json(**self.json_output) + else: + on_create(self, response['json']) else: if 'json' in response and '__all__' in response['json']: self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['json']['__all__'][0])) @@ -525,83 +518,77 @@ class TowerModule(AnsibleModule): else: self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['status_code'])) - # Process any associations with this item - if associations is not None: - for association_type in associations: - self.modify_associations(response, associations[association_type]) - - # If we have an on_create method and we actually changed something, we can call on_create - if on_create is not None and self.json_output['changed']: - on_create(self, response['json']) - else: - self.exit_json(**self.json_output) - - def update_if_needed(self, existing_item, new_item, on_update=None, associations=None): - # This will exit from the module on its own - # If the method successfully updates an item and on_update param is defined, + def update_if_needed(self, existing_item, new_item, handle_response=True, on_update=None): + # This will exit from the module on its own unless handle_response is False. + # If handle_response is True and the method successfully updates an item and on_update param is defined, # the on_update parameter will be called as a method pasing in this object and the json from the response - # This will return one of three things: + # If you pass handle_response=False it will return one of three things: # 1. None if the existing_item does not need to be updated # 2. The response from Tower from patching to the endpoint. It's up to you to process the response and exit from the module. # 3. An ItemNotDefined exception, if the existing_item does not exist - # Note: common error codes from the Tower API can cause the module to fail - if not existing_item: - self.fail_json(msg="The exstiing item is not defined and thus cannot be updated") - - # If we have an item, we can see if it needs an update - try: - item_url = existing_item['url'] - item_type = existing_item['type'] - if item_type == 'user': - item_name = existing_item['username'] - else: - item_name = existing_item['name'] - item_id = existing_item['id'] - except KeyError as ke: - self.fail_json(msg="Unable to process update of item due to missing data {0}".format(ke)) - - # Check to see if anything within the item requires the item to be updated - needs_update = False - for field in new_item: - existing_field = existing_item.get(field, None) - new_field = new_item.get(field, None) - # If the two items don't match and we are not comparing '' to None - if existing_field != new_field and not (existing_field in (None, '') and new_field == ''): - # Something doesn't match so let's update it - needs_update = True - break - - # If we decided the item needs to be updated, update it - self.json_output['id'] = item_id - if needs_update: - response = self.patch_endpoint(item_url, **{'data': new_item}) - if response['status_code'] == 200: - self.json_output['changed'] = True - elif 'json' in response and '__all__' in response['json']: - self.fail_json(msg=response['json']['__all__']) - else: - self.fail_json(**{'msg': "Unable to update {0} {1}, see response".format(item_type, item_name), 'response': response}) - - # Process any associations with this item - if associations is not None: - for association_type in associations: - self.modify_associations(response['json']['related'][association_type], associations[association_type]) - - if on_update is not None and self.json_output['changed']: - on_update(self, response['json']) - else: - self.exit_json(**self.json_output) - - def create_or_update_if_needed(self, existing_item, new_item, endpoint=None, item_type='unknown', on_create=None, on_update=None, associations=None): + # Note: common error codes from the Tower API can cause the module to fail even if handle_response is set to False if existing_item: - return self.update_if_needed(existing_item, new_item, on_update=on_update, associations=associations) + # If we have an item, we can see if it needs an update + try: + item_url = existing_item['url'] + item_type = existing_item['type'] + if item_type == 'user': + item_name = existing_item['username'] + else: + item_name = existing_item['name'] + item_id = existing_item['id'] + except KeyError as ke: + self.fail_json(msg="Unable to process update of item due to missing data {0}".format(ke)) + + needs_update = False + for field in new_item: + existing_field = existing_item.get(field, None) + new_field = new_item.get(field, None) + # If the two items don't match and we are not comparing '' to None + if existing_field != new_field and not (existing_field in (None, '') and new_field == ''): + # Something doesn't match so let's update it + needs_update = True + break + + if needs_update: + response = self.patch_endpoint(item_url, **{'data': new_item}) + if not handle_response: + return response + elif response['status_code'] == 200: + self.json_output['changed'] = True + self.json_output['id'] = item_id + if on_update is None: + self.exit_json(**self.json_output) + else: + on_update(self, response['json']) + elif 'json' in response and '__all__' in response['json']: + self.fail_json(msg=response['json']['__all__']) + else: + self.fail_json(**{'msg': "Unable to update {0} {1}, see response".format(item_type, item_name), 'response': response}) + else: + if not handle_response: + return None + + # Since we made it here, we don't need to update, status ok + self.json_output['changed'] = False + self.json_output['id'] = item_id + self.exit_json(**self.json_output) else: - return self.create_if_needed(existing_item, new_item, endpoint, on_create=on_create, item_type=item_type, associations=associations) + if handle_response: + self.fail_json(msg="The exstiing item is not defined and thus cannot be updated") + else: + raise ItemNotDefined("Not given an existing item to update") + + def create_or_update_if_needed(self, existing_item, new_item, endpoint=None, handle_response=True, item_type='unknown', on_create=None, on_update=None): + if existing_item: + return self.update_if_needed(existing_item, new_item, handle_response=handle_response, on_update=on_update) + else: + return self.create_if_needed(existing_item, new_item, endpoint, handle_response=handle_response, on_create=on_create, item_type=item_type) def logout(self): if self.oauth_token_id is not None and self.username and self.password: # Attempt to delete our current token from /api/v2/tokens/ - # Post to the tokens endpoint with basic auth to try and get a token + # Post to the tokens endpoint with baisc auth to try and get a token api_token_url = (self.url._replace(path='/api/v2/tokens/{0}/'.format(self.oauth_token_id))).geturl() try: diff --git a/awx_collection/plugins/modules/tower_group.py b/awx_collection/plugins/modules/tower_group.py index bb0b22ae77..4b68a50bb0 100644 --- a/awx_collection/plugins/modules/tower_group.py +++ b/awx_collection/plugins/modules/tower_group.py @@ -28,12 +28,6 @@ options: - The name to use for the group. required: True type: str - new_name: - description: - - A new name for this group (for renaming) - required: False - type: str - version_added: "3.7" description: description: - The description to use for the group. @@ -53,6 +47,12 @@ options: default: "present" choices: ["present", "absent"] type: str + new_name: + description: + - A new name for this group (for renaming) + required: False + type: str + version_added: "3.7" tower_oauthtoken: description: - The Tower OAuth token to use.