Merge pull request #21 from ansible/devel

Rebase
This commit is contained in:
Sean Sullivan
2020-11-10 08:13:52 -06:00
committed by GitHub
284 changed files with 9133 additions and 4349 deletions

View File

@@ -74,7 +74,8 @@ options:
- Deprecated, please use credential_type
required: False
type: str
choices: ["ssh", "vault", "net", "scm", "aws", "vmware", "satellite6", "cloudforms", "gce", "azure_rm", "openstack", "rhv", "insights", "tower"]
choices: ["aws", "tower", "gce", "azure_rm", "openstack", "cloudforms", "satellite6", "rhv", "vmware", "aim", "conjur", "hashivault_kv", "hashivault_ssh",
"azure_kv", "insights", "kubernetes_bearer_token", "net", "scm", "ssh", "github_token", "gitlab_token", "vault"]
host:
description:
- Host for this credential.
@@ -278,20 +279,28 @@ EXAMPLES = '''
from ..module_utils.tower_api import TowerAPIModule
KIND_CHOICES = {
'ssh': 'Machine',
'vault': 'Vault',
'net': 'Network',
'scm': 'Source Control',
'aws': 'Amazon Web Services',
'vmware': 'VMware vCenter',
'satellite6': 'Red Hat Satellite 6',
'cloudforms': 'Red Hat CloudForms',
'tower': 'Ansible Tower',
'gce': 'Google Compute Engine',
'azure_rm': 'Microsoft Azure Resource Manager',
'openstack': 'OpenStack',
'cloudforms': 'Red Hat CloudForms',
'satellite6': 'Red Hat Satellite 6',
'rhv': 'Red Hat Virtualization',
'vmware': 'VMware vCenter',
'aim': 'CyberArk AIM Central Credential Provider Lookup',
'conjur': 'CyberArk Conjur Secret Lookup',
'hashivault_kv': 'HashiCorp Vault Secret Lookup',
'hashivault_ssh': 'HashiCorp Vault Signed SSH',
'azure_kv': 'Microsoft Azure Key Vault',
'insights': 'Insights',
'tower': 'Ansible Tower',
'kubernetes_bearer_token': 'OpenShift or Kubernetes API Bearer Token',
'net': 'Network',
'scm': 'Source Control',
'ssh': 'Machine',
'github_token': 'GitHub Personal Access Token',
'gitlab_token': 'GitLab Personal Access Token',
'vault': 'Vault',
}

View File

@@ -82,9 +82,11 @@ EXAMPLES = '''
- name: Export all tower assets
tower_export:
all: True
- name: Export all inventories
tower_export:
inventory: 'all'
- name: Export a job template named "My Template" and all Credentials
tower_export:
job_template: "My Template"
@@ -135,27 +137,27 @@ def main():
# Otherwise we take either the string or None (if the parameter was not passed) to get one or no items
export_args[resource] = module.params.get(resource)
# Currently the import process does not return anything on error
# It simply just logs to pythons logger
# Setup a log gobbler to get error messages from import_assets
# Currently the export process does not return anything on error
# It simply just logs to Python's logger
# Set up a log gobbler to get error messages from export_assets
log_capture_string = StringIO()
ch = logging.StreamHandler(log_capture_string)
for logger_name in ['awxkit.api.pages.api', 'awxkit.api.pages.page']:
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARNING)
ch.setLevel(logging.WARNING)
logger.setLevel(logging.ERROR)
ch.setLevel(logging.ERROR)
logger.addHandler(ch)
log_contents = ''
# Run the import process
# Run the export process
try:
module.json_output['assets'] = module.get_api_v2_object().export_assets(**export_args)
module.exit_json(**module.json_output)
except Exception as e:
module.fail_json(msg="Failed to export assets {0}".format(e))
finally:
# Finally consume the logs incase there were any errors and die if there were
# Finally, consume the logs in case there were any errors and die if there were
log_contents = log_capture_string.getvalue()
log_capture_string.close()
if log_contents != '':

View File

@@ -38,7 +38,7 @@ EXAMPLES = '''
- name: Export all assets
tower_export:
all: True
registeR: export_output
register: export_output
- name: Import all tower assets from our export
tower_import:
@@ -51,7 +51,7 @@ EXAMPLES = '''
from ..module_utils.tower_awxkit import TowerAWXKitModule
# These two lines are not needed if awxkit changes to do progamatic notifications on issues
# These two lines are not needed if awxkit changes to do programatic notifications on issues
from ansible.module_utils.six.moves import StringIO
import logging
@@ -76,13 +76,15 @@ def main():
module.fail_json(msg="Your version of awxkit does not appear to have import/export")
# Currently the import process does not return anything on error
# It simply just logs to pythons logger
# Setup a log gobbler to get error messages from import_assets
# It simply just logs to Python's logger
# Set up a log gobbler to get error messages from import_assets
logger = logging.getLogger('awxkit.api.pages.api')
logger.setLevel(logging.WARNING)
logger.setLevel(logging.ERROR)
log_capture_string = StringIO()
ch = logging.StreamHandler(log_capture_string)
ch.setLevel(logging.WARNING)
ch.setLevel(logging.ERROR)
logger.addHandler(ch)
log_contents = ''
@@ -92,7 +94,7 @@ def main():
except Exception as e:
module.fail_json(msg="Failed to import assets {0}".format(e))
finally:
# Finally consume the logs incase there were any errors and die if there were
# Finally, consume the logs in case there were any errors and die if there were
log_contents = log_capture_string.getvalue()
log_capture_string.close()
if log_contents != '':

View File

@@ -111,7 +111,7 @@ def main():
# Attempt to look up an existing item based on the provided data
existing_item = module.get_one('instance_groups', name_or_id=name)
if state is 'absent':
if state == 'absent':
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
module.delete_if_needed(existing_item)

View File

@@ -22,14 +22,16 @@ description:
- Update Ansible Tower inventory source(s). See
U(https://www.ansible.com/tower) for an overview.
options:
inventory:
name:
description:
- Name of the inventory that contains the inventory source(s) to update.
- The name or id of the inventory source to update.
required: True
type: str
inventory_source:
aliases:
- inventory_source
inventory:
description:
- The name of the inventory source to update.
- Name or id of the inventory that contains the inventory source(s) to update.
required: True
type: str
organization:
@@ -58,14 +60,14 @@ extends_documentation_fragment: awx.awx.auth
EXAMPLES = '''
- name: Update a single inventory source
tower_inventory_source_update:
name: "Example Inventory Source"
inventory: "My Inventory"
inventory_source: "Example Inventory Source"
organization: Default
- name: Update all inventory sources
tower_inventory_source_update:
name: "{{ item }}"
inventory: "My Other Inventory"
inventory_source: "{{ item }}"
loop: "{{ query('awx.awx.tower_api', 'inventory_sources', query_params={ 'inventory': 30 }, return_ids=True ) }}"
'''
@@ -88,8 +90,8 @@ from ..module_utils.tower_api import TowerAPIModule
def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict(
name=dict(required=True, aliases=['inventory_source']),
inventory=dict(required=True),
inventory_source=dict(required=True),
organization=dict(),
wait=dict(default=False, type='bool'),
interval=dict(default=1.0, type='float'),
@@ -100,8 +102,8 @@ def main():
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
name = module.params.get('name')
inventory = module.params.get('inventory')
inventory_source = module.params.get('inventory_source')
organization = module.params.get('organization')
wait = module.params.get('wait')
interval = module.params.get('interval')
@@ -115,20 +117,18 @@ def main():
if not inventory_object:
module.fail_json(msg='The specified inventory, {0}, was not found.'.format(lookup_data))
inventory_source_object = module.get_one('inventory_sources', name_or_id=inventory_source, **{
'data': {
'inventory': inventory_object['id'],
}
})
inventory_source_object = module.get_one('inventory_sources',
name_or_id=name,
data={'inventory': inventory_object['id']})
if not inventory_source_object:
module.fail_json(msg='The specified inventory source was not found.')
# Sync the inventory source(s)
inventory_source_update_results = module.post_endpoint(inventory_source_object['related']['update'], **{'data': {}})
inventory_source_update_results = module.post_endpoint(inventory_source_object['related']['update'])
if inventory_source_update_results['status_code'] != 202:
module.fail_json(msg="Failed to update inventory source, see response for details", **{'response': inventory_source_update_results})
module.fail_json(msg="Failed to update inventory source, see response for details", response=inventory_source_update_results)
module.json_output['changed'] = True
module.json_output['id'] = inventory_source_update_results['json']['id']

View File

@@ -30,7 +30,7 @@ options:
interval:
description:
- The interval in sections, to request an update from Tower.
- For backwards compatability if unset this will be set to the average of min and max intervals
- For backwards compatibility if unset this will be set to the average of min and max intervals
required: False
default: 1
type: float
@@ -48,6 +48,12 @@ options:
description:
- Maximum time in seconds to wait for a job to finish.
type: int
job_type:
description:
- Job type to wait for
choices: ['project_updates', 'jobs', 'inventory_updates', 'workflow_jobs']
default: 'jobs'
type: str
extends_documentation_fragment: awx.awx.auth
'''
@@ -99,6 +105,7 @@ def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict(
job_id=dict(type='int', required=True),
job_type=dict(choices=['project_updates', 'jobs', 'inventory_updates', 'workflow_jobs'], default='jobs'),
timeout=dict(type='int'),
min_interval=dict(type='float'),
max_interval=dict(type='float'),
@@ -110,6 +117,7 @@ def main():
# Extract our parameters
job_id = module.params.get('job_id')
job_type = module.params.get('job_type')
timeout = module.params.get('timeout')
min_interval = module.params.get('min_interval')
max_interval = module.params.get('max_interval')
@@ -130,14 +138,14 @@ def main():
)
# Attempt to look up job based on the provided id
job = module.get_one('jobs', **{
job = module.get_one(job_type, **{
'data': {
'id': job_id,
}
})
if job is None:
module.fail_json(msg='Unable to wait on job {0}; that ID does not exist in Tower.'.format(job_id))
module.fail_json(msg='Unable to wait on ' + job_type.rstrip("s") + ' {0}; that ID does not exist in Tower.'.format(job_id))
# Invoke wait function
result = module.wait_on_url(

View File

@@ -21,11 +21,11 @@ description:
- Get or Set Ansible Tower license. See
U(https://www.ansible.com/tower) for an overview.
options:
data:
manifest:
description:
- The contents of the license file
- file path to a Red Hat subscription manifest (a .zip file)
required: True
type: dict
type: str
eula_accepted:
description:
- Whether or not the EULA is accepted.
@@ -39,10 +39,11 @@ RETURN = ''' # '''
EXAMPLES = '''
- name: Set the license using a file
license:
data: "{{ lookup('file', '/tmp/my_tower.license') }}"
manifest: "/tmp/my_manifest.zip"
eula_accepted: True
'''
import base64
from ..module_utils.tower_api import TowerAPIModule
@@ -50,29 +51,31 @@ def main():
module = TowerAPIModule(
argument_spec=dict(
data=dict(type='dict', required=True),
manifest=dict(type='str', required=True),
eula_accepted=dict(type='bool', required=True),
),
)
json_output = {'changed': False}
json_output = {'changed': True}
if not module.params.get('eula_accepted'):
module.fail_json(msg='You must accept the EULA by passing in the param eula_accepted as True')
json_output['old_license'] = module.get_endpoint('settings/system/')['json']['LICENSE']
new_license = module.params.get('data')
try:
manifest = base64.b64encode(
open(module.params.get('manifest'), 'rb').read()
)
except OSError as e:
module.fail_json(msg=str(e))
if json_output['old_license'] != new_license:
json_output['changed'] = True
# Deal with check mode
if module.check_mode:
module.exit_json(**json_output)
# Deal with check mode
if module.check_mode:
module.exit_json(**json_output)
# We need to add in the EULA
new_license['eula_accepted'] = True
module.post_endpoint('config', data=new_license)
module.post_endpoint('config', data={
'eula_accepted': True,
'manifest': manifest.decode()
})
module.exit_json(**json_output)

View File

@@ -91,8 +91,28 @@ options:
description:
- Name of unified job template to run in the workflow.
- Can be a job template, project, inventory source, etc.
- Omit if creating an approval node (not yet implemented).
- Omit if creating an approval node.
- This parameter is mutually exclusive with C(approval_node).
type: str
approval_node:
description:
- A dictionary of Name, description, and timeout values for the approval node.
- This parameter is mutually exclusive with C(unified_job_template).
type: dict
suboptions:
name:
description:
- Name of this workflow approval template.
type: str
required: True
description:
description:
- Optional description of this workflow approval template.
type: str
timeout:
description:
- The amount of time (in seconds) before the approval node expires and fails.
type: int
all_parents_must_converge:
description:
- If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node
@@ -176,6 +196,7 @@ def main():
diff_mode=dict(type='bool'),
verbosity=dict(choices=['0', '1', '2', '3', '4', '5']),
unified_job_template=dict(),
approval_node=dict(type='dict'),
all_parents_must_converge=dict(type='bool'),
success_nodes=dict(type='list', elements='str'),
always_nodes=dict(type='list', elements='str'),
@@ -183,14 +204,24 @@ def main():
credentials=dict(type='list', elements='str'),
state=dict(choices=['present', 'absent'], default='present'),
)
mutually_exclusive = [("unified_job_template", "approval_node")]
required_if = [
['state', 'absent', ['identifier']],
['state', 'present', ['identifier']],
['state', 'present', ['unified_job_template', 'approval_node', 'success_nodes', 'always_nodes', 'failure_nodes'], 'true'],
]
# Create a module for ourselves
module = TowerAPIModule(argument_spec=argument_spec)
module = TowerAPIModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
)
# Extract our parameters
identifier = module.params.get('identifier')
state = module.params.get('state')
approval_node = module.params.get('approval_node')
new_fields = {}
search_fields = {'identifier': identifier}
@@ -264,10 +295,43 @@ def main():
# If the state was present and we can let the module build or update the existing item, this will return on its own
module.create_or_update_if_needed(
existing_item, new_fields,
endpoint='workflow_job_template_nodes', item_type='workflow_job_template_node',
endpoint='workflow_job_template_nodes', item_type='workflow_job_template_node', auto_exit=not approval_node,
associations=association_fields
)
# Create approval node unified template or update existing
if approval_node:
# Set Approval Fields
new_fields = {}
# Extract Parameters
if approval_node.get('name') is None:
module.fail_json(msg="Approval node name is required to create approval node.")
if approval_node.get('name') is not None:
new_fields['name'] = approval_node['name']
if approval_node.get('description') is not None:
new_fields['description'] = approval_node['description']
if approval_node.get('timeout') is not None:
new_fields['timeout'] = approval_node['timeout']
# Find created workflow node ID
search_fields = {'identifier': identifier}
search_fields['workflow_job_template'] = workflow_job_template_id
workflow_job_template_node = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
workflow_job_template_node_id = workflow_job_template_node['id']
module.json_output['workflow_node_id'] = workflow_job_template_node_id
existing_item = None
# Due to not able to lookup workflow_approval_templates, find the existing item in another place
if workflow_job_template_node['related'].get('unified_job_template') is not None:
existing_item = module.get_endpoint(workflow_job_template_node['related']['unified_job_template'])['json']
approval_endpoint = 'workflow_job_template_nodes/{0}/create_approval_template/'.format(workflow_job_template_node_id)
module.create_or_update_if_needed(
existing_item, new_fields,
endpoint=approval_endpoint, item_type='workflow_job_template_approval_node',
associations=association_fields
)
module.exit_json(**module.json_output)
if __name__ == '__main__':
main()