Merge pull request #8033 from beeankha/update_inv_src_module

Add New tower_inventory_source_update Module

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot] 2020-09-08 14:04:32 +00:00 committed by GitHub
commit 6e6aa1fdab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 300 additions and 16 deletions

View File

@ -52,11 +52,11 @@ patterns
--------
`mk` functions are single object fixtures. They should create only a single object with the minimum deps.
They should also accept a `persited` flag, if they must be persisted to work, they raise an error if persisted=False
They should also accept a `persisted` flag, if they must be persisted to work, they raise an error if persisted=False
`generate` and `apply` functions are helpers that build up the various parts of a `create` functions objects. These
should be useful for more than one create function to use and should explicitly accept all of the values needed
to execute. These functions should also be robust and have very speciifc error reporting about constraints and/or
to execute. These functions should also be robust and have very specific error reporting about constraints and/or
bad values.
`create` functions compose many of the `mk` and `generate` functions to make different object

View File

@ -128,6 +128,10 @@ options:
- list of notifications to send on error
type: list
elements: str
organization:
description:
- Name of the inventory source's inventory's organization.
type: str
extends_documentation_fragment: awx.awx.auth
'''
@ -140,6 +144,7 @@ EXAMPLES = '''
credential: previously-created-credential
overwrite: True
update_on_launch: True
organization: Default
source_vars:
private: false
'''
@ -168,6 +173,7 @@ def main():
enabled_value=dict(),
host_filter=dict(),
credential=dict(),
organization=dict(),
overwrite=dict(type='bool'),
overwrite_vars=dict(type='bool'),
custom_virtualenv=dict(),
@ -190,23 +196,30 @@ def main():
name = module.params.get('name')
new_name = module.params.get('new_name')
inventory = module.params.get('inventory')
organization = module.params.get('organization')
source_script = module.params.get('source_script')
credential = module.params.get('credential')
source_project = module.params.get('source_project')
state = module.params.get('state')
# Attempt to look up inventory source based on the provided name and inventory ID
inventory_id = module.resolve_name_to_id('inventories', inventory)
inventory_source = module.get_one('inventory_sources', **{
lookup_data = {'name': inventory}
if organization:
lookup_data['organization'] = module.resolve_name_to_id('organizations', organization)
inventory_object = module.get_one('inventories', data=lookup_data)
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', **{
'data': {
'name': name,
'inventory': inventory_id,
'inventory': inventory_object['id'],
}
})
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(inventory_source)
module.delete_if_needed(inventory_source_object)
# Attempt to look up associated field items the user specified.
association_fields = {}
@ -232,7 +245,7 @@ def main():
# Create the data that gets sent for create and update
inventory_source_fields = {
'name': new_name if new_name else name,
'inventory': inventory_id,
'inventory': inventory_object['id'],
}
# Attempt to look up the related items the user specified (these will fail the module if not found)
@ -261,12 +274,12 @@ def main():
inventory_source_fields['source_vars'] = dumps(inventory_source_fields['source_vars'])
# Sanity check on arguments
if state == 'present' and not inventory_source and not inventory_source_fields['source']:
if state == 'present' and not inventory_source_object and not inventory_source_fields['source']:
module.fail_json(msg="If creating a new inventory source, the source param must be present")
# If the state was present we can let the module build or update the existing inventory_source, this will return on its own
# If the state was present we can let the module build or update the existing inventory_source_object, this will return on its own
module.create_or_update_if_needed(
inventory_source, inventory_source_fields,
inventory_source_object, inventory_source_fields,
endpoint='inventory_sources', item_type='inventory source',
associations=association_fields
)

View File

@ -0,0 +1,153 @@
#!/usr/bin/python
# coding: utf-8 -*-
# (c) 2020, Bianca Henderson <bianca@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: tower_inventory_source_update
author: "Bianca Henderson (@beeankha)"
short_description: Update inventory source(s).
description:
- Update Ansible Tower inventory source(s). See
U(https://www.ansible.com/tower) for an overview.
options:
inventory:
description:
- Name of the inventory that contains the inventory source(s) to update.
required: True
type: str
inventory_source:
description:
- The name of the inventory source to update.
required: True
type: str
organization:
description:
- Name of the inventory source's inventory's organization.
type: str
wait:
description:
- Wait for the job to complete.
default: False
type: bool
interval:
description:
- The interval to request an update from Tower.
required: False
default: 1
type: float
timeout:
description:
- If waiting for the job to complete this will abort after this
amount of seconds
type: int
extends_documentation_fragment: awx.awx.auth
'''
EXAMPLES = '''
- name: Update a single inventory source
tower_inventory_source_update:
inventory: "My Inventory"
inventory_source: "Example Inventory Source"
organization: Default
- name: Update all inventory sources
tower_inventory_source_update:
inventory: "My Other Inventory"
inventory_source: "{{ item }}"
loop: "{{ query('awx.awx.tower_api', 'inventory_sources', query_params={ 'inventory': 30 }, return_ids=True ) }}"
'''
RETURN = '''
id:
description: id of the inventory update
returned: success
type: int
sample: 86
status:
description: status of the inventory update
returned: success
type: str
sample: pending
'''
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(
inventory=dict(required=True),
inventory_source=dict(required=True),
organization=dict(),
wait=dict(default=False, type='bool'),
interval=dict(default=1.0, type='float'),
timeout=dict(default=None, type='int'),
)
# Create a module for ourselves
module = TowerAPIModule(argument_spec=argument_spec)
# Extract our parameters
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')
timeout = module.params.get('timeout')
lookup_data = {'name': inventory}
if organization:
lookup_data['organization'] = module.resolve_name_to_id('organizations', organization)
inventory_object = module.get_one('inventories', data=lookup_data)
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', **{
'data': {
'name': inventory_source,
'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': {}})
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.json_output['changed'] = True
module.json_output['id'] = inventory_source_update_results['json']['id']
module.json_output['status'] = inventory_source_update_results['json']['status']
if not wait:
module.exit_json(**module.json_output)
# Invoke wait function
module.wait_on_url(
url=inventory_source_update_results['json']['url'],
object_name=inventory_object,
object_type='inventory_update',
timeout=timeout, interval=interval
)
module.exit_json(**module.json_output)
if __name__ == '__main__':
main()

View File

@ -23,9 +23,9 @@ no_module_for_endpoint = []
# Some modules work on the related fields of an endpoint. These modules will not have an auto-associated endpoint
no_endpoint_for_module = [
'tower_import', 'tower_meta', 'tower_export', 'tower_job_launch', 'tower_job_wait', 'tower_job_list',
'tower_license', 'tower_ping', 'tower_receive', 'tower_send', 'tower_workflow_launch', 'tower_job_cancel',
'tower_workflow_template',
'tower_import', 'tower_meta', 'tower_export', 'tower_inventory_source_update', 'tower_job_launch', 'tower_job_wait',
'tower_job_list', 'tower_license', 'tower_ping', 'tower_receive', 'tower_send', 'tower_workflow_launch',
'tower_job_cancel', 'tower_workflow_template',
]
# Global module parameters we can ignore
@ -43,7 +43,8 @@ no_api_parameter_ok = {
# /survey spec is now how we handle associations
# We take an organization here to help with the lookups only
'tower_job_template': ['survey_spec', 'organization'],
# Organization is how we looking job templates
'tower_inventory_source': ['organization'],
# Organization is how we are looking up job templates
'tower_workflow_job_template_node': ['organization'],
# Survey is how we handle associations
'tower_workflow_job_template': ['survey'],

View File

@ -80,7 +80,8 @@ def test_create_inventory_source_multiple_orgs(run_module, admin_user):
result = run_module('tower_inventory_source', dict(
name='Test Inventory Source',
inventory=inv2.id,
inventory=inv2.name,
organization='test-org-number-two',
source='ec2',
state='present'
), admin_user)

View File

@ -0,0 +1,116 @@
---
- name: Generate a test ID
set_fact:
test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
- name: Generate names
set_fact:
project_name: "AWX-Collection-tests-tower_inventory_source_update-project-{{ test_id }}"
inv_name: "AWX-Collection-tests-tower_inventory_source_update-inv-{{ test_id }}"
inv_source1: "AWX-Collection-tests-tower_inventory_source_update-source1-{{ test_id }}"
inv_source2: "AWX-Collection-tests-tower_inventory_source_update-source2-{{ test_id }}"
inv_source3: "AWX-Collection-tests-tower_inventory_source_update-source3-{{ test_id }}"
org_name: "AWX-Collection-tests-tower_inventory_source_update-org-{{ test_id }}"
- block:
- name: "Create a new organization"
tower_organization:
name: "{{ org_name }}"
register: created_org
- name: Create a git project without credentials
tower_project:
name: "{{ project_name }}"
organization: "{{ org_name }}"
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
wait: true
- name: Create an Inventory
tower_inventory:
name: "{{ inv_name }}"
organization: "{{ org_name }}"
state: present
- name: Create another inventory w/ same name, different org
tower_inventory:
name: "{{ inv_name }}"
organization: Default
state: present
register: created_inventory
- name: Create an Inventory Source (specifically connected to the randomly generated org)
tower_inventory_source:
name: "{{ inv_source1 }}"
source: scm
source_project: "{{ project_name }}"
source_path: inventories/inventory.ini
description: Source for Test inventory
organization: "{{ created_org.id }}"
inventory: "{{ inv_name }}"
- name: Create Another Inventory Source
tower_inventory_source:
name: "{{ inv_source2 }}"
source: scm
source_project: "{{ project_name }}"
source_path: inventories/create_10_hosts.ini
description: Source for Test inventory
organization: Default
inventory: "{{ inv_name }}"
- name: Create Yet Another Inventory Source (to make lookup plugin find multiple inv sources)
tower_inventory_source:
name: "{{ inv_source3 }}"
source: scm
source_project: "{{ project_name }}"
source_path: inventories/create_100_hosts.ini
description: Source for Test inventory
organization: Default
inventory: "{{ inv_name }}"
- name: Test Inventory Source Update
tower_inventory_source_update:
inventory: "{{ inv_name }}"
inventory_source: "{{ inv_source2 }}"
organization: Default
register: result
- assert:
that:
- "result is changed"
- name: Test Inventory Source Update for All Sources
tower_inventory_source_update:
inventory: "{{ inv_name }}"
inventory_source: "{{ item.name }}"
organization: Default
wait: true
loop: "{{ query('awx.awx.tower_api', 'inventory_sources', query_params={ 'inventory': created_inventory.id }, expect_objects=True, return_objects=True) }}"
loop_control:
label: "{{ item.name }}"
register: result
- assert:
that:
- "result is changed"
always:
- name: Delete Inventory
tower_inventory:
name: "{{ inv_name }}"
organization: Default
state: absent
- name: Delete Project
tower_project:
name: "{{ project_name }}"
organization: Default
state: absent
- name: "Remove the organization"
tower_organization:
name: "{{ org_name }}"
state: absent