From 4c55685656342818f46287f7bf025e6a743b4eae Mon Sep 17 00:00:00 2001 From: Tom Page Date: Thu, 11 Jun 2020 17:52:30 +0100 Subject: [PATCH 1/5] Add tower_credential_input_source to awx_collection Signed-off-by: Tom Page --- .../modules/tower_credential_input_source.py | 134 +++++++++ .../test/awx/test_credential_input_source.py | 268 ++++++++++++++++++ .../tasks/main.yml | 74 +++++ 3 files changed, 476 insertions(+) create mode 100644 awx_collection/plugins/modules/tower_credential_input_source.py create mode 100644 awx_collection/test/awx/test_credential_input_source.py create mode 100644 awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml diff --git a/awx_collection/plugins/modules/tower_credential_input_source.py b/awx_collection/plugins/modules/tower_credential_input_source.py new file mode 100644 index 0000000000..d6245911ec --- /dev/null +++ b/awx_collection/plugins/modules/tower_credential_input_source.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# Copyright: (c) 2017, Wayne Witzel III +# 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_credential_input_source +author: "Tom Page (@Tompage1994)" +version_added: "2.3" +short_description: create, update, or destroy Ansible Tower credential input sources. +description: + - Create, update, or destroy Ansible Tower credential input sources. See + U(https://www.ansible.com/tower) for an overview. +options: + description: + description: + - The description to use for the credential input source. + type: str + input_field_name: + description: + - The input field the credential source will be used for + required: True + type: str + metadata: + description: + - A JSON or YAML string + required: False + type: str + target_credential: + description: + - The credential which will have its input defined by this source + required: true + type: str + source_credential: + description: + - The credential which is the source of the credential lookup + required: true + type: str + state: + description: + - Desired state of the resource. + choices: ["present", "absent"] + default: "present" + type: str + +extends_documentation_fragment: awx.awx.auth +''' + + +EXAMPLES = ''' +- name: Use CyberArk Lookup credential as password source + tower_credential_input_source: + input_field_name: password + target_credential: new_cred + source_credential: cyberark_lookup + metadata: + object_query: "Safe=MY_SAFE;Object=awxuser" + object_query_format: "Exact" + state: present + +''' + +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( + description=dict(default=''), + input_field_name=dict(required=True), + target_credential=dict(required=True), + source_credential=dict(required=True), + metadata=dict(type=dict), + state=dict(choices=['present', 'absent'], default='present'), + ) + + # Create a module for ourselves + module = TowerModule(argument_spec=argument_spec) + + # Extract our parameters + description = module.params.get('description') + input_field_name = module.params.get('input_field_name') + target_credential = module.params.get('target_credential') + source_credential = module.params.get('source_credential') + metadata = module.params.get('metadata') + state = module.params.get('state') + + target_credential_id = module.resolve_name_to_id('credentials', target_credential) + source_credential_id = module.resolve_name_to_id('credentials', source_credential) + + # Attempt to look up the object based on the provided name, credential type and optional organization + lookup_data = { + 'target_credential': target_credential_id, + 'source_credential': source_credential_id, + 'input_field_name': input_field_name, + } + + credential_input_source = module.get_one('credential_input_sources', **{'data': lookup_data}) + + if state == 'absent': + # If the state was absent we can let the module delete it if needed, the module will handle exiting from this + if credential_input_source: + credential_input_source['name'] = '' + module.delete_if_needed(credential_input_source) + + # Create the data that gets sent for create and update + credential_input_source_fields = { + 'target_credential': target_credential_id, + 'source_credential': source_credential_id, + 'input_field_name': input_field_name, + } + if metadata: + credential_input_source_fields['metadata'] = metadata + if description: + credential_input_source_fields['description'] = description + + # 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( + credential_input_source, credential_input_source_fields, endpoint='credential_input_sources', item_type='credential_input_source' + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/test/awx/test_credential_input_source.py b/awx_collection/test/awx/test_credential_input_source.py new file mode 100644 index 0000000000..4c5cf84e8f --- /dev/null +++ b/awx_collection/test/awx/test_credential_input_source.py @@ -0,0 +1,268 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from awx.main.models import CredentialInputSource, Credential, CredentialType, Organization + + +# Test CyberArk AIM credential source +@pytest.fixture +def source_cred_aim(organization): + # Make a credential type which will be used by the credential + ct=CredentialType.defaults['aim']() + ct.save() + return Credential.objects.create( + name='CyberArk AIM Cred', + credential_type=ct, + inputs={ + "url": "https://cyberark.example.com", + "app_id": "myAppID", + "verify": "false" + } + ) + + +@pytest.mark.django_db +def test_aim_credential_source(run_module, admin_user, organization, silence_deprecation): + src_cred = source_cred_aim(organization) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount" + assert cis.source_credential.name == src_cred.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' + assert result['id'] == cis.pk + + +# Test CyberArk Conjur credential source +@pytest.fixture +def source_cred_conjur(organization): + # Make a credential type which will be used by the credential + ct=CredentialType.defaults['conjur']() + ct.save() + return Credential.objects.create( + name='CyberArk CONJUR Cred', + credential_type=ct, + inputs={ + "url": "https://cyberark.example.com", + "api_key": "myApiKey", + "account": "account", + "username": "username" + } + ) + + +@pytest.mark.django_db +def test_conjur_credential_source(run_module, admin_user, organization, silence_deprecation): + src_cred = source_cred_conjur(organization) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"secret_path": "/path/to/secret"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['secret_path'] == "/path/to/secret" + assert cis.source_credential.name == src_cred.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' + assert result['id'] == cis.pk + + +# Test Hashicorp Vault secret credential source +@pytest.fixture +def source_cred_hashi_secret(organization): + # Make a credential type which will be used by the credential + ct=CredentialType.defaults['hashivault_kv']() + ct.save() + return Credential.objects.create( + name='HashiCorp secret Cred', + credential_type=ct, + inputs={ + "url": "https://secret.hash.example.com", + "token": "myApiKey", + "role_id": "role", + "secret_id": "secret" + } + ) + + +@pytest.mark.django_db +def test_hashi_secret_credential_source(run_module, admin_user, organization, silence_deprecation): + src_cred = source_cred_hashi_secret(organization) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "secret_backend": "backend", "secret_key": "a_key"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['secret_path'] == "/path/to/secret" + assert cis.metadata['auth_path'] == "/path/to/auth" + assert cis.metadata['secret_backend'] == "backend" + assert cis.metadata['secret_key'] == "a_key" + assert cis.source_credential.name == src_cred.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' + assert result['id'] == cis.pk + + +# Test Hashicorp Vault signed ssh credential source +@pytest.fixture +def source_cred_hashi_ssh(organization): + # Make a credential type which will be used by the credential + ct=CredentialType.defaults['hashivault_ssh']() + ct.save() + return Credential.objects.create( + name='HashiCorp ssh Cred', + credential_type=ct, + inputs={ + "url": "https://ssh.hash.example.com", + "token": "myApiKey", + "role_id": "role", + "secret_id": "secret" + } + ) + + +@pytest.mark.django_db +def test_hashi_ssh_credential_source(run_module, admin_user, organization, silence_deprecation): + src_cred = source_cred_hashi_ssh(organization) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "role": "role", "public_key": "a_key", "valid_principals": "some_value"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['secret_path'] == "/path/to/secret" + assert cis.metadata['auth_path'] == "/path/to/auth" + assert cis.metadata['role'] == "role" + assert cis.metadata['public_key'] == "a_key" + assert cis.metadata['valid_principals'] == "some_value" + assert cis.source_credential.name == src_cred.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' + assert result['id'] == cis.pk + + +# Test Azure Key Vault credential source +@pytest.fixture +def source_cred_azure_kv(organization): + # Make a credential type which will be used by the credential + ct=CredentialType.defaults['azure_kv']() + ct.save() + return Credential.objects.create( + name='Azure KV Cred', + credential_type=ct, + inputs={ + "url": "https://key.azure.example.com", + "client": "client", + "secret": "secret", + "tenant": "tenant", + "cloud_name": "the_cloud", + } + ) + + +@pytest.mark.django_db +def test_azure_kv_credential_source(run_module, admin_user, organization, silence_deprecation): + src_cred = source_cred_azure_kv(organization) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"secret_field": "my_pass"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['secret_field'] == "my_pass" + assert cis.source_credential.name == src_cred.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' + assert result['id'] == cis.pk diff --git a/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml new file mode 100644 index 0000000000..9e17847818 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml @@ -0,0 +1,74 @@ +--- +- name: Generate names + set_fact: + src_cred_name: src_cred + target_cred_name: target_cred + +- name: Add Tower credential Lookup + tower_credential: + description: Credential for Testing Source + name: "{{ src_cred_name }}" + credential_type: CyberArk AIM Central Credential Provider Lookup + inputs: + url: "https://cyberark.example.com" + app_id: "My-App-ID" + organization: Default + register: result + +- assert: + that: + - "result is changed" + +- name: Add Tower credential Target + tower_credential: + description: Credential for Testing Target + name: "{{ target_cred_name }}" + credential_type: Machine + inputs: + username: user + organization: Default + register: result + +- assert: + that: + - "result is changed" + +- name: Add credential Input Source + tower_credential_input_source: + input_field_name: password + target_credential: "{{ target_cred_name }}" + source_credential: "{{ src_cred_name }}" + metadata: + object_query: "Safe=MY_SAFE;Object=AWX-user" + object_query_format: "Exact" + state: present + +- assert: + that: + - "result is changed" + +- name: Remove a Tower credential type + tower_credential_input_source: + input_field_name: password + target_credential: "{{ target_cred_name }}" + source_credential: "{{ src_cred_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Remove Tower credential Lookup + tower_credential: + name: "{{ src_cred_name }}" + organization: Default + state: absent + register: result + +- name: Remove Tower credential Lookup + tower_credential: + name: "{{ target_cred_name }}" + organization: Default + state: absent + register: result From 1c78190385db53b18fd70253c246410a031e2d4f Mon Sep 17 00:00:00 2001 From: Tom Page Date: Tue, 16 Jun 2020 13:56:49 +0100 Subject: [PATCH 2/5] Change cred_input_src to remove src_cred as primarykey Signed-off-by: Tom Page --- .../plugins/module_utils/tower_api.py | 5 ++ .../modules/tower_credential_input_source.py | 17 ++-- .../test/awx/test_credential_input_source.py | 83 +++++++++++++++++-- .../tasks/main.yml | 37 ++++++++- 4 files changed, 123 insertions(+), 19 deletions(-) diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index d836c457c0..a0b8b789ff 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -521,6 +521,9 @@ class TowerModule(AnsibleModule): elif item_type == 'o_auth2_access_token': # An oauth2 token has no name, instead we will use its id for any of the messages item_name = existing_item['id'] + elif item_type == 'credential_input_source': + # An credential_input_source has no name, instead we will use its id for any of the messages + item_name = existing_item['id'] else: self.fail_json(msg="Unable to process delete of {0} due to missing name".format(item_type)) @@ -691,6 +694,8 @@ class TowerModule(AnsibleModule): item_name = existing_item['username'] elif item_type == 'workflow_job_template_node': item_name = existing_item['identifier'] + elif item_type == 'credential_input_source': + item_name = existing_item['id'] else: item_name = existing_item['name'] item_id = existing_item['id'] diff --git a/awx_collection/plugins/modules/tower_credential_input_source.py b/awx_collection/plugins/modules/tower_credential_input_source.py index d6245911ec..ffaae2642f 100644 --- a/awx_collection/plugins/modules/tower_credential_input_source.py +++ b/awx_collection/plugins/modules/tower_credential_input_source.py @@ -1,7 +1,7 @@ #!/usr/bin/python # coding: utf-8 -*- -# Copyright: (c) 2017, Wayne Witzel III +# Copyright: (c) 2020, Tom Page # 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 @@ -45,7 +45,6 @@ options: source_credential: description: - The credential which is the source of the credential lookup - required: true type: str state: description: @@ -79,7 +78,7 @@ def main(): description=dict(default=''), input_field_name=dict(required=True), target_credential=dict(required=True), - source_credential=dict(required=True), + source_credential=dict(default=''), metadata=dict(type=dict), state=dict(choices=['present', 'absent'], default='present'), ) @@ -96,29 +95,25 @@ def main(): state = module.params.get('state') target_credential_id = module.resolve_name_to_id('credentials', target_credential) - source_credential_id = module.resolve_name_to_id('credentials', source_credential) - # Attempt to look up the object based on the provided name, credential type and optional organization + # Attempt to look up the object based on the target credential and input field lookup_data = { 'target_credential': target_credential_id, - 'source_credential': source_credential_id, 'input_field_name': input_field_name, } - + module.json_output['all'] = module.get_all_endpoint('credential_input_sources', **{'data': {}}) credential_input_source = module.get_one('credential_input_sources', **{'data': lookup_data}) if state == 'absent': - # If the state was absent we can let the module delete it if needed, the module will handle exiting from this - if credential_input_source: - credential_input_source['name'] = '' module.delete_if_needed(credential_input_source) # Create the data that gets sent for create and update credential_input_source_fields = { 'target_credential': target_credential_id, - 'source_credential': source_credential_id, 'input_field_name': input_field_name, } + if source_credential: + credential_input_source_fields['source_credential'] = module.resolve_name_to_id('credentials', source_credential) if metadata: credential_input_source_fields['metadata'] = metadata if description: diff --git a/awx_collection/test/awx/test_credential_input_source.py b/awx_collection/test/awx/test_credential_input_source.py index 4c5cf84e8f..7c6a46093a 100644 --- a/awx_collection/test/awx/test_credential_input_source.py +++ b/awx_collection/test/awx/test_credential_input_source.py @@ -5,13 +5,16 @@ import pytest from awx.main.models import CredentialInputSource, Credential, CredentialType, Organization +@pytest.fixture +def get_aim_cred_type(): + ct=CredentialType.defaults['aim']() + ct.save() + return ct + # Test CyberArk AIM credential source @pytest.fixture -def source_cred_aim(organization): - # Make a credential type which will be used by the credential - ct=CredentialType.defaults['aim']() - ct.save() +def source_cred_aim(ct): return Credential.objects.create( name='CyberArk AIM Cred', credential_type=ct, @@ -25,7 +28,8 @@ def source_cred_aim(organization): @pytest.mark.django_db def test_aim_credential_source(run_module, admin_user, organization, silence_deprecation): - src_cred = source_cred_aim(organization) + cred_type = get_aim_cred_type() + src_cred = source_cred_aim(cred_type) ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -266,3 +270,72 @@ def test_azure_kv_credential_source(run_module, admin_user, organization, silenc assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk + + +# Test Changing Credential Source +@pytest.fixture +def source_cred_aim_alt(ct): + return Credential.objects.create( + name='Alternate CyberArk AIM Cred', + credential_type=ct, + inputs={ + "url": "https://cyberark-alt.example.com", + "app_id": "myAltID", + "verify": "false" + } + ) + +@pytest.mark.django_db +def test_aim_credential_source(run_module, admin_user, organization, silence_deprecation): + cred_type=get_aim_cred_type() + src_cred = source_cred_aim(cred_type) + ct=CredentialType.defaults['ssh']() + ct.save() + tgt_cred = Credential.objects.create( + name='Test Machine Credential', + organization=organization, + credential_type=ct, + inputs={'username': 'bob'} + ) + + result = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, + state='present' + ), admin_user) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed'), result + + unchangedResult = run_module('tower_credential_input_source', dict( + source_credential=src_cred.name, + target_credential=tgt_cred.name, + input_field_name='password', + metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, + state='present' + ), admin_user) + + assert not unchangedResult.get('failed', False), result.get('msg', result) + assert not unchangedResult.get('changed'), result + + src_cred_alt = source_cred_aim_alt(cred_type) + + changedResult = run_module('tower_credential_input_source', dict( + source_credential=src_cred_alt.name, + target_credential=tgt_cred.name, + input_field_name='password', + state='present' + ), admin_user) + + assert not changedResult.get('failed', False), changedResult.get('msg', result) + assert changedResult.get('changed'), result + + assert CredentialInputSource.objects.count() == 1 + cis = CredentialInputSource.objects.first() + + assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount" + assert cis.source_credential.name == src_cred_alt.name + assert cis.target_credential.name == tgt_cred.name + assert cis.input_field_name == 'password' diff --git a/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml index 9e17847818..45be47ddf7 100644 --- a/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_credential_input_source/tasks/main.yml @@ -47,11 +47,32 @@ that: - "result is changed" -- name: Remove a Tower credential type +- name: Add Second Tower credential Lookup + tower_credential: + description: Credential for Testing Source Change + name: "{{ src_cred_name }}-2" + credential_type: CyberArk AIM Central Credential Provider Lookup + inputs: + url: "https://cyberark-prod.example.com" + app_id: "My-App-ID" + organization: Default + register: result + +- name: Change credential Input Source + tower_credential_input_source: + input_field_name: password + target_credential: "{{ target_cred_name }}" + source_credential: "{{ src_cred_name }}-2" + state: present + +- assert: + that: + - "result is changed" + +- name: Remove a Tower credential source tower_credential_input_source: input_field_name: password target_credential: "{{ target_cred_name }}" - source_credential: "{{ src_cred_name }}" state: absent register: result @@ -63,12 +84,22 @@ tower_credential: name: "{{ src_cred_name }}" organization: Default + credential_type: CyberArk AIM Central Credential Provider Lookup state: absent register: result -- name: Remove Tower credential Lookup +- name: Remove Alt Tower credential Lookup + tower_credential: + name: "{{ src_cred_name }}-2" + organization: Default + credential_type: CyberArk AIM Central Credential Provider Lookup + state: absent + register: result + +- name: Remove Tower credential tower_credential: name: "{{ target_cred_name }}" organization: Default + credential_type: Machine state: absent register: result From 73a39c1e5501416c6d80c81ba7e95097ff6b51d9 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Tue, 16 Jun 2020 14:30:28 +0100 Subject: [PATCH 3/5] Cleanup of debug and change test fixtures Signed-off-by: Tom Page --- .../modules/tower_credential_input_source.py | 1 - .../test/awx/test_credential_input_source.py | 60 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/awx_collection/plugins/modules/tower_credential_input_source.py b/awx_collection/plugins/modules/tower_credential_input_source.py index ffaae2642f..8e81d50569 100644 --- a/awx_collection/plugins/modules/tower_credential_input_source.py +++ b/awx_collection/plugins/modules/tower_credential_input_source.py @@ -101,7 +101,6 @@ def main(): 'target_credential': target_credential_id, 'input_field_name': input_field_name, } - module.json_output['all'] = module.get_all_endpoint('credential_input_sources', **{'data': {}}) credential_input_source = module.get_one('credential_input_sources', **{'data': lookup_data}) if state == 'absent': diff --git a/awx_collection/test/awx/test_credential_input_source.py b/awx_collection/test/awx/test_credential_input_source.py index 7c6a46093a..15773791da 100644 --- a/awx_collection/test/awx/test_credential_input_source.py +++ b/awx_collection/test/awx/test_credential_input_source.py @@ -6,7 +6,7 @@ import pytest from awx.main.models import CredentialInputSource, Credential, CredentialType, Organization @pytest.fixture -def get_aim_cred_type(): +def aim_cred_type(): ct=CredentialType.defaults['aim']() ct.save() return ct @@ -14,10 +14,10 @@ def get_aim_cred_type(): # Test CyberArk AIM credential source @pytest.fixture -def source_cred_aim(ct): +def source_cred_aim(aim_cred_type): return Credential.objects.create( name='CyberArk AIM Cred', - credential_type=ct, + credential_type=aim_cred_type, inputs={ "url": "https://cyberark.example.com", "app_id": "myAppID", @@ -27,9 +27,7 @@ def source_cred_aim(ct): @pytest.mark.django_db -def test_aim_credential_source(run_module, admin_user, organization, silence_deprecation): - cred_type = get_aim_cred_type() - src_cred = source_cred_aim(cred_type) +def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -40,7 +38,7 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_aim.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, @@ -54,7 +52,7 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep cis = CredentialInputSource.objects.first() assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount" - assert cis.source_credential.name == src_cred.name + assert cis.source_credential.name == source_cred_aim.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk @@ -79,8 +77,7 @@ def source_cred_conjur(organization): @pytest.mark.django_db -def test_conjur_credential_source(run_module, admin_user, organization, silence_deprecation): - src_cred = source_cred_conjur(organization) +def test_conjur_credential_source(run_module, admin_user, organization, source_cred_conjur, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -91,7 +88,7 @@ def test_conjur_credential_source(run_module, admin_user, organization, silence_ ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_conjur.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"secret_path": "/path/to/secret"}, @@ -105,7 +102,7 @@ def test_conjur_credential_source(run_module, admin_user, organization, silence_ cis = CredentialInputSource.objects.first() assert cis.metadata['secret_path'] == "/path/to/secret" - assert cis.source_credential.name == src_cred.name + assert cis.source_credential.name == source_cred_conjur.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk @@ -130,8 +127,7 @@ def source_cred_hashi_secret(organization): @pytest.mark.django_db -def test_hashi_secret_credential_source(run_module, admin_user, organization, silence_deprecation): - src_cred = source_cred_hashi_secret(organization) +def test_hashi_secret_credential_source(run_module, admin_user, organization, source_cred_hashi_secret, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -142,7 +138,7 @@ def test_hashi_secret_credential_source(run_module, admin_user, organization, si ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_hashi_secret.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "secret_backend": "backend", "secret_key": "a_key"}, @@ -159,7 +155,7 @@ def test_hashi_secret_credential_source(run_module, admin_user, organization, si assert cis.metadata['auth_path'] == "/path/to/auth" assert cis.metadata['secret_backend'] == "backend" assert cis.metadata['secret_key'] == "a_key" - assert cis.source_credential.name == src_cred.name + assert cis.source_credential.name == source_cred_hashi_secret.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk @@ -184,8 +180,7 @@ def source_cred_hashi_ssh(organization): @pytest.mark.django_db -def test_hashi_ssh_credential_source(run_module, admin_user, organization, silence_deprecation): - src_cred = source_cred_hashi_ssh(organization) +def test_hashi_ssh_credential_source(run_module, admin_user, organization, source_cred_hashi_ssh, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -196,7 +191,7 @@ def test_hashi_ssh_credential_source(run_module, admin_user, organization, silen ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_hashi_ssh.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"secret_path": "/path/to/secret", "auth_path": "/path/to/auth", "role": "role", "public_key": "a_key", "valid_principals": "some_value"}, @@ -214,7 +209,7 @@ def test_hashi_ssh_credential_source(run_module, admin_user, organization, silen assert cis.metadata['role'] == "role" assert cis.metadata['public_key'] == "a_key" assert cis.metadata['valid_principals'] == "some_value" - assert cis.source_credential.name == src_cred.name + assert cis.source_credential.name == source_cred_hashi_ssh.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk @@ -240,8 +235,7 @@ def source_cred_azure_kv(organization): @pytest.mark.django_db -def test_azure_kv_credential_source(run_module, admin_user, organization, silence_deprecation): - src_cred = source_cred_azure_kv(organization) +def test_azure_kv_credential_source(run_module, admin_user, organization, source_cred_azure_kv, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -252,7 +246,7 @@ def test_azure_kv_credential_source(run_module, admin_user, organization, silenc ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_azure_kv.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"secret_field": "my_pass"}, @@ -266,7 +260,7 @@ def test_azure_kv_credential_source(run_module, admin_user, organization, silenc cis = CredentialInputSource.objects.first() assert cis.metadata['secret_field'] == "my_pass" - assert cis.source_credential.name == src_cred.name + assert cis.source_credential.name == source_cred_azure_kv.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' assert result['id'] == cis.pk @@ -274,10 +268,10 @@ def test_azure_kv_credential_source(run_module, admin_user, organization, silenc # Test Changing Credential Source @pytest.fixture -def source_cred_aim_alt(ct): +def source_cred_aim_alt(aim_cred_type): return Credential.objects.create( name='Alternate CyberArk AIM Cred', - credential_type=ct, + credential_type=aim_cred_type, inputs={ "url": "https://cyberark-alt.example.com", "app_id": "myAltID", @@ -286,9 +280,7 @@ def source_cred_aim_alt(ct): ) @pytest.mark.django_db -def test_aim_credential_source(run_module, admin_user, organization, silence_deprecation): - cred_type=get_aim_cred_type() - src_cred = source_cred_aim(cred_type) +def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, source_cred_aim_alt, silence_deprecation): ct=CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( @@ -299,7 +291,7 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep ) result = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_aim.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, @@ -310,7 +302,7 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep assert result.get('changed'), result unchangedResult = run_module('tower_credential_input_source', dict( - source_credential=src_cred.name, + source_credential=source_cred_aim.name, target_credential=tgt_cred.name, input_field_name='password', metadata={"object_query": "Safe=SUPERSAFE;Object=MyAccount"}, @@ -320,10 +312,8 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep assert not unchangedResult.get('failed', False), result.get('msg', result) assert not unchangedResult.get('changed'), result - src_cred_alt = source_cred_aim_alt(cred_type) - changedResult = run_module('tower_credential_input_source', dict( - source_credential=src_cred_alt.name, + source_credential=source_cred_aim_alt.name, target_credential=tgt_cred.name, input_field_name='password', state='present' @@ -336,6 +326,6 @@ def test_aim_credential_source(run_module, admin_user, organization, silence_dep cis = CredentialInputSource.objects.first() assert cis.metadata['object_query'] == "Safe=SUPERSAFE;Object=MyAccount" - assert cis.source_credential.name == src_cred_alt.name + assert cis.source_credential.name == source_cred_aim_alt.name assert cis.target_credential.name == tgt_cred.name assert cis.input_field_name == 'password' From fd18194b1b580532d49da34f9bc587896cb9092b Mon Sep 17 00:00:00 2001 From: Tom Page Date: Tue, 16 Jun 2020 15:57:19 +0100 Subject: [PATCH 4/5] Fix pylint errors --- .../modules/tower_credential_input_source.py | 1 + .../test/awx/test_credential_input_source.py | 82 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/awx_collection/plugins/modules/tower_credential_input_source.py b/awx_collection/plugins/modules/tower_credential_input_source.py index 8e81d50569..dcce387054 100644 --- a/awx_collection/plugins/modules/tower_credential_input_source.py +++ b/awx_collection/plugins/modules/tower_credential_input_source.py @@ -72,6 +72,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( diff --git a/awx_collection/test/awx/test_credential_input_source.py b/awx_collection/test/awx/test_credential_input_source.py index 15773791da..a676ab15cb 100644 --- a/awx_collection/test/awx/test_credential_input_source.py +++ b/awx_collection/test/awx/test_credential_input_source.py @@ -5,9 +5,10 @@ import pytest from awx.main.models import CredentialInputSource, Credential, CredentialType, Organization + @pytest.fixture def aim_cred_type(): - ct=CredentialType.defaults['aim']() + ct = CredentialType.defaults['aim']() ct.save() return ct @@ -19,16 +20,16 @@ def source_cred_aim(aim_cred_type): name='CyberArk AIM Cred', credential_type=aim_cred_type, inputs={ - "url": "https://cyberark.example.com", - "app_id": "myAppID", - "verify": "false" - } + "url": "https://cyberark.example.com", + "app_id": "myAppID", + "verify": "false" + } ) @pytest.mark.django_db def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', @@ -62,23 +63,23 @@ def test_aim_credential_source(run_module, admin_user, organization, source_cred @pytest.fixture def source_cred_conjur(organization): # Make a credential type which will be used by the credential - ct=CredentialType.defaults['conjur']() + ct = CredentialType.defaults['conjur']() ct.save() return Credential.objects.create( name='CyberArk CONJUR Cred', credential_type=ct, inputs={ - "url": "https://cyberark.example.com", - "api_key": "myApiKey", - "account": "account", - "username": "username" - } + "url": "https://cyberark.example.com", + "api_key": "myApiKey", + "account": "account", + "username": "username" + } ) @pytest.mark.django_db def test_conjur_credential_source(run_module, admin_user, organization, source_cred_conjur, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', @@ -112,23 +113,23 @@ def test_conjur_credential_source(run_module, admin_user, organization, source_c @pytest.fixture def source_cred_hashi_secret(organization): # Make a credential type which will be used by the credential - ct=CredentialType.defaults['hashivault_kv']() + ct = CredentialType.defaults['hashivault_kv']() ct.save() return Credential.objects.create( name='HashiCorp secret Cred', credential_type=ct, inputs={ - "url": "https://secret.hash.example.com", - "token": "myApiKey", - "role_id": "role", - "secret_id": "secret" - } + "url": "https://secret.hash.example.com", + "token": "myApiKey", + "role_id": "role", + "secret_id": "secret" + } ) @pytest.mark.django_db def test_hashi_secret_credential_source(run_module, admin_user, organization, source_cred_hashi_secret, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', @@ -165,23 +166,23 @@ def test_hashi_secret_credential_source(run_module, admin_user, organization, so @pytest.fixture def source_cred_hashi_ssh(organization): # Make a credential type which will be used by the credential - ct=CredentialType.defaults['hashivault_ssh']() + ct = CredentialType.defaults['hashivault_ssh']() ct.save() return Credential.objects.create( name='HashiCorp ssh Cred', credential_type=ct, inputs={ - "url": "https://ssh.hash.example.com", - "token": "myApiKey", - "role_id": "role", - "secret_id": "secret" - } + "url": "https://ssh.hash.example.com", + "token": "myApiKey", + "role_id": "role", + "secret_id": "secret" + } ) @pytest.mark.django_db def test_hashi_ssh_credential_source(run_module, admin_user, organization, source_cred_hashi_ssh, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', @@ -219,24 +220,24 @@ def test_hashi_ssh_credential_source(run_module, admin_user, organization, sourc @pytest.fixture def source_cred_azure_kv(organization): # Make a credential type which will be used by the credential - ct=CredentialType.defaults['azure_kv']() + ct = CredentialType.defaults['azure_kv']() ct.save() return Credential.objects.create( name='Azure KV Cred', credential_type=ct, inputs={ - "url": "https://key.azure.example.com", - "client": "client", - "secret": "secret", - "tenant": "tenant", - "cloud_name": "the_cloud", - } + "url": "https://key.azure.example.com", + "client": "client", + "secret": "secret", + "tenant": "tenant", + "cloud_name": "the_cloud", + } ) @pytest.mark.django_db def test_azure_kv_credential_source(run_module, admin_user, organization, source_cred_azure_kv, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', @@ -273,15 +274,16 @@ def source_cred_aim_alt(aim_cred_type): name='Alternate CyberArk AIM Cred', credential_type=aim_cred_type, inputs={ - "url": "https://cyberark-alt.example.com", - "app_id": "myAltID", - "verify": "false" - } + "url": "https://cyberark-alt.example.com", + "app_id": "myAltID", + "verify": "false" + } ) + @pytest.mark.django_db def test_aim_credential_source(run_module, admin_user, organization, source_cred_aim, source_cred_aim_alt, silence_deprecation): - ct=CredentialType.defaults['ssh']() + ct = CredentialType.defaults['ssh']() ct.save() tgt_cred = Credential.objects.create( name='Test Machine Credential', From 7bf1d4946e71cbbc6934e68dbb238ae740c72d23 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Tue, 16 Jun 2020 17:14:52 +0100 Subject: [PATCH 5/5] Fixed final lint issues Signed-off-by: Tom Page --- .../plugins/modules/tower_credential_input_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/tower_credential_input_source.py b/awx_collection/plugins/modules/tower_credential_input_source.py index dcce387054..bc2cb85579 100644 --- a/awx_collection/plugins/modules/tower_credential_input_source.py +++ b/awx_collection/plugins/modules/tower_credential_input_source.py @@ -36,7 +36,7 @@ options: description: - A JSON or YAML string required: False - type: str + type: dict target_credential: description: - The credential which will have its input defined by this source @@ -80,7 +80,7 @@ def main(): input_field_name=dict(required=True), target_credential=dict(required=True), source_credential=dict(default=''), - metadata=dict(type=dict), + metadata=dict(type="dict"), state=dict(choices=['present', 'absent'], default='present'), )