diff --git a/awx_collection/README.md b/awx_collection/README.md index a9a3c06374..2ddaf994b5 100644 --- a/awx_collection/README.md +++ b/awx_collection/README.md @@ -22,6 +22,7 @@ The following notes are changes that may require changes to playbooks. - Creating a "scan" type job template is no longer supported. - `extra_vars` in the `tower_job_launch` module worked with a list previously, but is now configured to work solely in a `dict` format. - When the `extra_vars` parameter is used with the `tower_job_launch` module, the Job Template launch will fail unless `add_extra_vars` or `survey_enabled` is explicitly set to `True` on the Job Template. + - tower_group used to also service inventory sources. tower_inventory_source has been split out into its own module. ## Running diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 921e123da9..514a3f898c 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -14,6 +14,7 @@ import re from json import loads, dumps from os.path import isfile, expanduser, split, join, exists, isdir from os import access, R_OK, getcwd +import yaml class ConfigFileException(Exception): @@ -556,3 +557,27 @@ class TowerModule(AnsibleModule): return False else: return True + + def load_variables_if_file_specified(self, vars_value, var_name): + if not vars_value.startswith('@'): + return vars_value + + file_name = None + file_content = None + try: + file_name = expanduser(vars_value[1:]) + with open(file_name, 'r') as f: + file_content = f.read() + except Exception as e: + self.fail_json(msg="Failed to load file {0} for {1} : {2}".format(file_name, var_name, e)) + + try: + vars_value = yaml.safe_load(file_content) + except yaml.YAMLError: + # Maybe it wasn't a YAML structure... lets try JSON + try: + vars_value = loads(file_content) + except ValueError: + self.fail_json(msg="Failed to load file {0} specifed by {1} as yaml or json".format(file_name, var_name)) + + return dumps(vars_value) diff --git a/awx_collection/plugins/modules/tower_credential_type.py b/awx_collection/plugins/modules/tower_credential_type.py index 86362f36f9..b6c8806199 100644 --- a/awx_collection/plugins/modules/tower_credential_type.py +++ b/awx_collection/plugins/modules/tower_credential_type.py @@ -150,7 +150,6 @@ def main(): # Add entries to json_output to match old module module.json_output['credential_type'] = name module.json_output['state'] = state - module.json_output['existing_credential_type'] = credential_type if state == 'absent': # If the state was absent we can let the module delete it if needed, the module will handle exiting from this diff --git a/awx_collection/plugins/modules/tower_group.py b/awx_collection/plugins/modules/tower_group.py index 0c16e8a9ce..ccfd7dc2bd 100644 --- a/awx_collection/plugins/modules/tower_group.py +++ b/awx_collection/plugins/modules/tower_group.py @@ -28,6 +28,11 @@ 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 description: description: - The description to use for the group. @@ -41,55 +46,17 @@ options: description: - Variables to use for the group, use C(@) for a file. type: str - credential: - description: - - Credential to use for the group. - type: str - source: - description: - - The source to use for this group. - choices: ["manual", "file", "ec2", "vmware", "gce", "azure", "azure_rm", "openstack", "satellite6" , "cloudforms", "custom"] - type: str - source_regions: - description: - - Regions for cloud provider. - type: str - source_vars: - description: - - Override variables from source with variables from this field. - type: str - instance_filters: - description: - - Comma-separated list of filter expressions for matching hosts. - type: str - group_by: - description: - - Limit groups automatically created from inventory source. - type: str - source_script: - description: - - Inventory script to be used when group type is C(custom). - type: str - overwrite: - description: - - Delete child groups and hosts not found in source. - type: bool - default: 'no' - overwrite_vars: - description: - - Override vars in child groups and hosts with those from external source. - type: bool - update_on_launch: - description: - - Refresh inventory data from its source each time a job is run. - type: bool - default: 'no' state: description: - Desired state of the resource. default: "present" choices: ["present", "absent"] type: str + tower_oauthtoken: + description: + - The Tower OAuth token to use. + required: False + type: str extends_documentation_fragment: awx.awx.auth ''' @@ -104,86 +71,62 @@ EXAMPLES = ''' tower_config_file: "~/tower_cli.cfg" ''' -import os - -from ..module_utils.ansible_tower import TowerModule, tower_auth_config, tower_check_mode - -try: - import tower_cli - import tower_cli.exceptions as exc - - from tower_cli.conf import settings -except ImportError: - pass +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( name=dict(required=True), + new_name=dict(required=False), description=dict(), inventory=dict(required=True), variables=dict(), - credential=dict(), - source=dict(choices=["manual", "file", "ec2", "vmware", - "gce", "azure", "azure_rm", "openstack", - "satellite6", "cloudforms", "custom"]), - source_regions=dict(), - source_vars=dict(), - instance_filters=dict(), - group_by=dict(), - source_script=dict(), - overwrite=dict(type='bool'), - overwrite_vars=dict(type='bool'), - update_on_launch=dict(type='bool'), state=dict(choices=['present', 'absent'], default='present'), ) + # Create a module for ourselves module = TowerModule(argument_spec=argument_spec, supports_check_mode=True) + # Extract our parameters name = module.params.get('name') + new_name = module.params.get('new_name') inventory = module.params.get('inventory') - credential = module.params.get('credential') + description = module.params.get('description') state = module.params.pop('state') - variables = module.params.get('variables') + + # Attempt to look up the related items the user specified (these will fail the module if not found) + inventory_id = module.resolve_name_to_id('inventories', inventory) + + # Attempt to look up the object based on the provided name and inventory ID + group = module.get_one('groups', **{ + 'data': { + 'name': name, + 'inventory': inventory_id + } + }) + + # If the variables were specified as a file, load them if variables: - if variables.startswith('@'): - filename = os.path.expanduser(variables[1:]) - with open(filename, 'r') as f: - variables = f.read() + variables = module.load_variables_if_file_specified(variables, 'variables') - json_output = {'group': name, 'state': state} + # Create data to sent to create and update + group_fields = { + 'name': new_name if new_name else name, + 'inventory': inventory_id, + } + if description: + group_fields['description'] = description + if variables: + group_fields['variables'] = variables - tower_auth = tower_auth_config(module) - with settings.runtime_values(**tower_auth): - tower_check_mode(module) - group = tower_cli.get_resource('group') - try: - params = module.params.copy() - params['create_on_missing'] = True - params['variables'] = variables - - inv_res = tower_cli.get_resource('inventory') - inv = inv_res.get(name=inventory) - params['inventory'] = inv['id'] - - if credential: - cred_res = tower_cli.get_resource('credential') - cred = cred_res.get(name=credential) - params['credential'] = cred['id'] - - if state == 'present': - result = group.modify(**params) - json_output['id'] = result['id'] - elif state == 'absent': - result = group.delete(**params) - except (exc.NotFound) as excinfo: - module.fail_json(msg='Failed to update the group, inventory not found: {0}'.format(excinfo), changed=False) - except (exc.ConnectionError, exc.BadRequest, exc.AuthError) as excinfo: - module.fail_json(msg='Failed to update the group: {0}'.format(excinfo), changed=False) - - json_output['changed'] = result['changed'] - module.exit_json(**json_output) + 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(group) + elif state == 'present': + # If the state was present we can let the module build or update the existing group, this will return on its own + module.create_or_update_if_needed(group, group_fields, endpoint='groups', item_type='group') if __name__ == '__main__': diff --git a/awx_collection/plugins/modules/tower_inventory.py b/awx_collection/plugins/modules/tower_inventory.py index 65da8badfa..e52771ec63 100644 --- a/awx_collection/plugins/modules/tower_inventory.py +++ b/awx_collection/plugins/modules/tower_inventory.py @@ -117,7 +117,7 @@ def main(): } }) - # Create data to sent to create and update + # Create the data sent to create and update inventory_fields = { 'name': name, 'description': description, diff --git a/awx_collection/plugins/modules/tower_organization.py b/awx_collection/plugins/modules/tower_organization.py index faa127b940..73c49eaa10 100644 --- a/awx_collection/plugins/modules/tower_organization.py +++ b/awx_collection/plugins/modules/tower_organization.py @@ -43,7 +43,7 @@ options: description: - The max hosts allowed in this organizations default: "0" - type: str + type: int required: False state: description: @@ -86,7 +86,7 @@ def main(): name=dict(type='str', required=True), description=dict(type='str', required=False), custom_virtualenv=dict(type='str', required=False), - max_hosts=dict(type='str', required=False, default="0"), + max_hosts=dict(type='int', required=False, default="0"), state=dict(type='str', choices=['present', 'absent'], default='present', required=False), ) @@ -119,12 +119,7 @@ def main(): if custom_virtualenv: org_fields['custom_virtualenv'] = custom_virtualenv if max_hosts: - int_max_hosts = 0 - try: - int_max_hosts = int(max_hosts) - except Exception: - module.fail_json(msg="Unable to convert max_hosts to an integer") - org_fields['max_hosts'] = int_max_hosts + org_fields['max_hosts'] = max_hosts if state == 'absent': # If the state was absent we can let the module delete it if needed, the module will handle exiting from this diff --git a/awx_collection/test/awx/conftest.py b/awx_collection/test/awx/conftest.py index 2c4f90e2c9..49badc22dd 100644 --- a/awx_collection/test/awx/conftest.py +++ b/awx_collection/test/awx/conftest.py @@ -42,7 +42,6 @@ def sanitize_dict(din): @pytest.fixture def run_module(request): - # A placeholder to use while modules get converted def rf(module_name, module_params, request_user): def new_request(self, method, url, **kwargs): diff --git a/awx_collection/test/awx/test_credential.py b/awx_collection/test/awx/test_credential.py index 30871ee4ff..28ccba7f36 100644 --- a/awx_collection/test/awx/test_credential.py +++ b/awx_collection/test/awx/test_credential.py @@ -78,7 +78,6 @@ def test_create_custom_credential_type(run_module, admin_user): ct = CredentialType.objects.get(name='Nexus') result.pop('invocation') - result.pop('existing_credential_type') result.pop('name') assert result == { "credential_type": "Nexus", diff --git a/awx_collection/test/awx/test_group.py b/awx_collection/test/awx/test_group.py index 59cb68e5f8..e4e8e1afac 100644 --- a/awx_collection/test/awx/test_group.py +++ b/awx_collection/test/awx/test_group.py @@ -25,8 +25,9 @@ def test_create_group(run_module, admin_user): result.pop('invocation') assert result == { + 'credential_type': 'Nexus', 'id': group.id, - 'group': 'Test Group', + 'name': 'Test Group', 'changed': True, 'state': 'present' } @@ -53,7 +54,8 @@ def test_tower_group_idempotent(run_module, admin_user): result.pop('invocation') assert result == { 'id': group.id, - 'group': 'Test Group', + 'credential_type': 'Nexus', + 'name': 'Test Group', 'changed': False, # idempotency assertion 'state': 'present' } diff --git a/awx_collection/test/awx/test_organization.py b/awx_collection/test/awx/test_organization.py index c55312cd5b..95d6866d35 100644 --- a/awx_collection/test/awx/test_organization.py +++ b/awx_collection/test/awx/test_organization.py @@ -27,7 +27,6 @@ def test_create_organization(run_module, admin_user): assert result.get('changed'), result org = Organization.objects.get(name='foo') - result.pop('existing_credential_type') assert result == { "name": "foo", "changed": True, @@ -55,7 +54,6 @@ def test_create_organization_with_venv(run_module, admin_user, mocker): org = Organization.objects.get(name='foo') result.pop('invocation') - result.pop('existing_credential_type') assert result == { "credential_type": "Nexus", "state": "present", diff --git a/awx_collection/test/awx/test_project.py b/awx_collection/test/awx/test_project.py index b65fe2f45d..ad6f589265 100644 --- a/awx_collection/test/awx/test_project.py +++ b/awx_collection/test/awx/test_project.py @@ -23,7 +23,6 @@ def test_create_project(run_module, admin_user, organization): assert proj.organization == organization result.pop('invocation') - result.pop('existing_credential_type') assert result == { 'credential_type': 'Nexus', 'state': 'present', diff --git a/awx_collection/test/awx/test_team.py b/awx_collection/test/awx/test_team.py index 7ae1753c16..b4eef38185 100644 --- a/awx_collection/test/awx/test_team.py +++ b/awx_collection/test/awx/test_team.py @@ -20,7 +20,6 @@ def test_create_team(run_module, admin_user): team = Team.objects.filter(name='foo_team').first() result.pop('invocation') - result.pop('existing_credential_type') assert result == { "changed": True, "name": "foo_team", @@ -50,7 +49,6 @@ def test_modify_team(run_module, admin_user): }, admin_user) team.refresh_from_db() result.pop('invocation') - result.pop('existing_credential_type') assert result == { "state": "present", "changed": True, @@ -67,7 +65,6 @@ def test_modify_team(run_module, admin_user): 'organization': 'foo' }, admin_user) result.pop('invocation') - result.pop('existing_credential_type') assert result == { "credential_type": "Nexus", "name": "foo_team", diff --git a/awx_collection/tests/sanity/ignore-2.10.txt b/awx_collection/tests/sanity/ignore-2.10.txt index fa05535904..c9df9574f5 100644 --- a/awx_collection/tests/sanity/ignore-2.10.txt +++ b/awx_collection/tests/sanity/ignore-2.10.txt @@ -1,2 +1 @@ -plugins/modules/tower_group.py use-argspec-type-path -plugins/modules/tower_host.py use-argspec-type-path \ No newline at end of file +plugins/modules/tower_host.py use-argspec-type-path