diff --git a/awx/main/constants.py b/awx/main/constants.py index fbd783b968..e8ac403723 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -14,7 +14,7 @@ __all__ = [ 'STANDARD_INVENTORY_UPDATE_ENV', ] -CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'tower') +CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'tower', 'insights') PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), diff --git a/awx/main/migrations/0146_add_insights_inventory.py b/awx/main/migrations/0146_add_insights_inventory.py new file mode 100644 index 0000000000..d4d9df3c32 --- /dev/null +++ b/awx/main/migrations/0146_add_insights_inventory.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.16 on 2021-06-08 18:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0145_deregister_managed_ee_objs'), + ] + + operations = [ + migrations.RemoveField( + model_name='host', + name='insights_system_id', + ), + migrations.AlterField( + model_name='inventorysource', + name='source', + field=models.CharField( + choices=[ + ('file', 'File, Directory or Script'), + ('scm', 'Sourced from a Project'), + ('ec2', 'Amazon EC2'), + ('gce', 'Google Compute Engine'), + ('azure_rm', 'Microsoft Azure Resource Manager'), + ('vmware', 'VMware vCenter'), + ('satellite6', 'Red Hat Satellite 6'), + ('openstack', 'OpenStack'), + ('rhv', 'Red Hat Virtualization'), + ('tower', 'Ansible Tower'), + ('insights', 'Red Hat Insights'), + ], + default=None, + max_length=32, + ), + ), + migrations.AlterField( + model_name='inventoryupdate', + name='source', + field=models.CharField( + choices=[ + ('file', 'File, Directory or Script'), + ('scm', 'Sourced from a Project'), + ('ec2', 'Amazon EC2'), + ('gce', 'Google Compute Engine'), + ('azure_rm', 'Microsoft Azure Resource Manager'), + ('vmware', 'VMware vCenter'), + ('satellite6', 'Red Hat Satellite 6'), + ('openstack', 'OpenStack'), + ('rhv', 'Red Hat Virtualization'), + ('tower', 'Ansible Tower'), + ('insights', 'Red Hat Insights'), + ], + default=None, + max_length=32, + ), + ), + ] diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index f33a0d4671..0e5cac28f0 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -954,6 +954,10 @@ ManagedCredentialType( "scm_username": "{{username}}", "scm_password": "{{password}}", }, + 'env': { + 'INSIGHTS_USER': '{{username}}', + 'INSIGHTS_PASSWORD': '{{password}}', + }, }, ) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 89a6cb1ad7..da8c4f1c38 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -828,6 +828,7 @@ class InventorySourceOptions(BaseModel): ('openstack', _('OpenStack')), ('rhv', _('Red Hat Virtualization')), ('tower', _('Ansible Tower')), + ('insights', _('Red Hat Insights')), ] # From the options of the Django management base command @@ -1548,5 +1549,21 @@ class tower(PluginFileInjector): collection = 'awx' +class insights(PluginFileInjector): + plugin_name = 'insights' + base_injector = 'template' + namespace = 'redhatinsights' + collection = 'insights' + downstream_namespace = 'redhat' + downstream_collection = 'insights' + use_fqcn = 'true' + + def inventory_as_dict(self, inventory_update, private_data_dir): + ret = super(insights, self).inventory_as_dict(inventory_update, private_data_dir) + # this inventory plugin requires the fully qualified inventory plugin name + ret['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}' + return ret + + for cls in PluginFileInjector.__subclasses__(): InventorySourceOptions.injectors[cls.__name__] = cls diff --git a/awx/main/tests/data/inventory/plugins/insights/env.json b/awx/main/tests/data/inventory/plugins/insights/env.json new file mode 100644 index 0000000000..46eb0a34e7 --- /dev/null +++ b/awx/main/tests/data/inventory/plugins/insights/env.json @@ -0,0 +1,5 @@ +{ + "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", + "INSIGHTS_USER": "fooo", + "INSIGHTS_PASSWORD": "fooo" +} \ No newline at end of file diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index bf6e908c52..9fe6328633 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -652,6 +652,31 @@ def test_satellite6_create_ok(post, organization, admin): assert decrypt_field(cred, 'password') == 'some_password' +# +# RH Insights Credentials +# +@pytest.mark.django_db +def test_insights_create_ok(post, organization, admin): + params = { + 'credential_type': 1, + 'name': 'Best credential ever', + 'inputs': { + 'username': 'some_username', + 'password': 'some_password', + }, + } + sat6 = CredentialType.defaults['insights']() + sat6.save() + params['organization'] = organization.id + response = post(reverse('api:credential_list'), params, admin) + assert response.status_code == 201 + + assert Credential.objects.count() == 1 + cred = Credential.objects.all()[:1].get() + assert cred.inputs['username'] == 'some_username' + assert decrypt_field(cred, 'password') == 'some_password' + + # # AWS Credentials # diff --git a/awx/main/tests/functional/models/test_inventory.py b/awx/main/tests/functional/models/test_inventory.py index eab7e02895..c7303367b3 100644 --- a/awx/main/tests/functional/models/test_inventory.py +++ b/awx/main/tests/functional/models/test_inventory.py @@ -209,6 +209,7 @@ class TestInventorySourceInjectors: ('vmware', 'community.vmware.vmware_vm_inventory'), ('rhv', 'ovirt.ovirt.ovirt'), ('satellite6', 'theforeman.foreman.foreman'), + ('insights', 'redhatinsights.insights.insights'), ('tower', 'awx.awx.tower'), ], ) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index ec6c9d1bee..fe563e39cf 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -1746,6 +1746,34 @@ class TestInventoryUpdateCredentials(TestJobExecution): assert env["FOREMAN_PASSWORD"] == "secret" assert safe_env["FOREMAN_PASSWORD"] == tasks.HIDDEN_PASSWORD + def test_insights_source(self, inventory_update, private_data_dir, mocker): + task = tasks.RunInventoryUpdate() + task.instance = inventory_update + insights = CredentialType.defaults['insights']() + inventory_update.source = 'insights' + + def get_cred(): + cred = Credential( + pk=1, + credential_type=insights, + inputs={ + 'username': 'bob', + 'password': 'secret', + }, + ) + cred.inputs['password'] = encrypt_field(cred, 'password') + return cred + + inventory_update.get_cloud_credential = get_cred + inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) + + env = task.build_env(inventory_update, private_data_dir, False) + safe_env = build_safe_env(env) + + assert env["INSIGHTS_USER"] == "bob" + assert env["INSIGHTS_PASSWORD"] == "secret" + assert safe_env['INSIGHTS_PASSWORD'] == tasks.HIDDEN_PASSWORD + @pytest.mark.parametrize('verify', [True, False]) def test_tower_source(self, verify, inventory_update, private_data_dir, mocker): task = tasks.RunInventoryUpdate() diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index ea47b43907..d1b38a2c1e 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -693,6 +693,14 @@ SATELLITE6_EXCLUDE_EMPTY_GROUPS = True SATELLITE6_INSTANCE_ID_VAR = 'foreman_id' # SATELLITE6_GROUP_PREFIX and SATELLITE6_GROUP_PATTERNS defined in source vars +# ---------------- +# -- Red Hat Insights -- +# ---------------- +# INSIGHTS_ENABLED_VAR = +# INSIGHTS_ENABLED_VALUE = +INSIGHTS_INSTANCE_ID_VAR = 'insights_id' +INSIGHTS_EXCLUDE_EMPTY_GROUPS = False + # --------------------- # ----- Custom ----- # ---------------------