mirror of
https://github.com/ansible/awx.git
synced 2024-10-30 05:25:29 +03:00
Add updated tower_api module util file, update generator and template
This commit is contained in:
parent
53d27c933e
commit
88c46b4573
@ -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 here then we have values from reading the ini file, so let's pull them out into a dict
|
||||
# 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
|
||||
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 than we didn't have an integer so we can just pass and fall down to the fail
|
||||
# If we got a value error, then 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 us directly; make sure we were given a method, let's not just assume a GET
|
||||
# In case someone is calling this 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 a oauth token, we just use a bearer header
|
||||
# If we have an 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 baisc auth to try and get a token
|
||||
# Post to the tokens endpoint with basic 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, 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,
|
||||
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,
|
||||
# the on_delete parameter will be called as a method pasing in this object and the json from the response
|
||||
# If you pass handle_response=False, it will return one of two things:
|
||||
# This 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 even if handle_response is set to False
|
||||
# Note: common error codes from the Tower API can cause the module to fail
|
||||
if existing_item:
|
||||
# If we have an item, we can try to delete it
|
||||
try:
|
||||
@ -440,9 +440,7 @@ class TowerModule(AnsibleModule):
|
||||
|
||||
response = self.delete_endpoint(item_url)
|
||||
|
||||
if not handle_response:
|
||||
return response
|
||||
elif response['status_code'] in [202, 204]:
|
||||
if response['status_code'] in [202, 204]:
|
||||
if on_delete:
|
||||
on_delete(self, response['json'])
|
||||
self.json_output['changed'] = True
|
||||
@ -460,21 +458,40 @@ class TowerModule(AnsibleModule):
|
||||
else:
|
||||
self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['status_code']))
|
||||
else:
|
||||
if not handle_response:
|
||||
return None
|
||||
else:
|
||||
self.exit_json(**self.json_output)
|
||||
self.exit_json(**self.json_output)
|
||||
|
||||
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,
|
||||
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
|
||||
else:
|
||||
self.fail_json(msg="Failed to disassociate item {0}".format(response['json']['detail']))
|
||||
|
||||
# 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,
|
||||
# the on_create parameter will be called as a method pasing in this object and the json from the response
|
||||
# If you pass handle_response=False it will return one of two things:
|
||||
# This 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 even if handle_response is set to False
|
||||
#
|
||||
# Note: common error codes from the Tower API can cause the module to fail
|
||||
|
||||
if not endpoint:
|
||||
self.fail_json(msg="Unable to create new {0} due to missing endpoint".format(item_type))
|
||||
|
||||
@ -482,11 +499,7 @@ class TowerModule(AnsibleModule):
|
||||
try:
|
||||
existing_item['url']
|
||||
except KeyError as 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)
|
||||
self.fail_json(msg="Unable to process create of item due to missing data {0}".format(ke))
|
||||
else:
|
||||
# If we don't have an exisitng_item, we can try to create it
|
||||
|
||||
@ -495,9 +508,7 @@ class TowerModule(AnsibleModule):
|
||||
item_name = new_item.get('name', 'unknown')
|
||||
|
||||
response = self.post_endpoint(endpoint, **{'data': new_item})
|
||||
if not handle_response:
|
||||
return response
|
||||
elif response['status_code'] == 201:
|
||||
if response['status_code'] == 201:
|
||||
self.json_output['name'] = 'unknown'
|
||||
if 'name' in response['json']:
|
||||
self.json_output['name'] = response['json']['name']
|
||||
@ -506,10 +517,6 @@ 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]))
|
||||
@ -518,77 +525,83 @@ class TowerModule(AnsibleModule):
|
||||
else:
|
||||
self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, item_name, response['status_code']))
|
||||
|
||||
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,
|
||||
# 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,
|
||||
# the on_update parameter will be called as a method pasing in this object and the json from the response
|
||||
# If you pass handle_response=False it will return one of three things:
|
||||
# This 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 even if handle_response is set to False
|
||||
if existing_item:
|
||||
# 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))
|
||||
# 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")
|
||||
|
||||
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})
|
||||
# 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:
|
||||
if not handle_response:
|
||||
return None
|
||||
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))
|
||||
|
||||
# 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:
|
||||
if handle_response:
|
||||
self.fail_json(msg="The exstiing item is not defined and thus cannot be updated")
|
||||
# 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:
|
||||
raise ItemNotDefined("Not given an existing item to update")
|
||||
self.fail_json(**{'msg': "Unable to update {0} {1}, see response".format(item_type, item_name), 'response': response})
|
||||
|
||||
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)
|
||||
# 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:
|
||||
return self.create_if_needed(existing_item, new_item, endpoint, handle_response=handle_response, on_create=on_create, item_type=item_type)
|
||||
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):
|
||||
if existing_item:
|
||||
return self.update_if_needed(existing_item, new_item, on_update=on_update, associations=associations)
|
||||
else:
|
||||
return self.create_if_needed(existing_item, new_item, endpoint, on_create=on_create, item_type=item_type, associations=associations)
|
||||
|
||||
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 baisc auth to try and get a token
|
||||
# Post to the tokens endpoint with basic 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:
|
||||
|
@ -87,6 +87,7 @@ EXAMPLES = '''
|
||||
|
||||
from ..module_utils.tower_api import TowerModule
|
||||
|
||||
|
||||
def main():
|
||||
# Any additional arguments that are not fields of the item can be added here
|
||||
argument_spec = dict(
|
||||
|
@ -1,15 +1,16 @@
|
||||
---
|
||||
generate_for:
|
||||
# - credentials
|
||||
# - credential_types
|
||||
# - groups
|
||||
# - hosts
|
||||
# - inventorues
|
||||
# - inventory_sources
|
||||
# - job_templates
|
||||
# - labels
|
||||
# - notification_templates
|
||||
# - organizations
|
||||
# - projects
|
||||
- schedules
|
||||
# - teams
|
||||
# - users
|
||||
# - job_templates
|
||||
# - credentials
|
||||
# - notification_templates
|
||||
# - labels
|
||||
|
Loading…
Reference in New Issue
Block a user