Repository: ansible/awx

Issue:
Add insignts_credential paramter to tower_inventory
<!-- Issues are for **concrete, actionable bugs and feature requests** only - if you're just asking for debugging help or technical support, please use:

- http://webchat.freenode.net/?channels=ansible-awx
- https://groups.google.com/forum/#!forum/awx-project

We have to limit this because of limited volunteer time to respond to issues! -->

##### ISSUE TYPE
 - Feature Idea

##### SUMMARY
<!-- Briefly describe the problem or desired enhancement. -->
Per PR #7963 tower_inventory is missing support for the insights_credential API parameter.


Discussion:

Resolving patch:
diff --git a/awx_collection/plugins/modules/tower_inventory.py b/awx_collection/plugins/modules/tower_inventory.py
--- a/awx_collection/plugins/modules/tower_inventory.py
+++ b/awx_collection/plugins/modules/tower_inventory.py
@@ -48,7 +48,11 @@
       type: str
     host_filter:
       description:
-        -  The host_filter field. Only useful when C(kind=smart).
+        - The host_filter field. Only useful when C(kind=smart).
+      type: str
+    insights_credential:
+      description:
+        - Credentials to be used by hosts belonging to this inventory when accessing Red Hat Insights API.
       type: str
     state:
       description:
@@ -84,6 +88,7 @@ def main():
         variables=dict(type='dict'),
         kind=dict(choices=['', 'smart'], default=''),
         host_filter=dict(),
+        insights_credential=dict(),
         state=dict(choices=['present', 'absent'], default='present'),
     )
 
@@ -98,6 +103,7 @@ def main():
     state = module.params.get('state')
     kind = module.params.get('kind')
     host_filter = module.params.get('host_filter')
+    insights_credential = module.params.get('insights_credential')
 
     # Attempt to look up the related items the user specified (these will fail the module if not found)
     org_id = module.resolve_name_to_id('organizations', organization)
@@ -125,6 +131,8 @@ def main():
         inventory_fields['description'] = description
     if variables is not None:
         inventory_fields['variables'] = json.dumps(variables)
+    if insights_credential is not None:
+        inventory_fields['insights_credential'] = module.resolve_name_to_id('credentials', insights_credential)
 
     # We need to perform a check to make sure you are not trying to convert a regular inventory into a smart one.
     if inventory and inventory['kind'] == '' and inventory_fields['kind'] == 'smart':


Test patch:
diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py
--- a/awx_collection/test/awx/test_completeness.py
+++ b/awx_collection/test/awx/test_completeness.py
@@ -58,7 +58,6 @@
 ]
 needs_param_development = {
     'tower_host': ['instance_id'],
-    'tower_inventory': ['insights_credential'],
 }
 # -----------------------------------------------------------------------------------------------------------
 
diff --git a/awx_collection/test/awx/test_inventory.py b/awx_collection/test/awx/test_inventory.py
--- a/awx_collection/test/awx/test_inventory.py
+++ b/awx_collection/test/awx/test_inventory.py
@@ -3,20 +3,26 @@
 
 import pytest
 
-from awx.main.models import Inventory
+from awx.main.models import Inventory, Credential
+from awx.main.tests.functional.conftest import insights_credential, credentialtype_insights
 
 
 @pytest.mark.django_db
-def test_inventory_create(run_module, admin_user, organization):
+def test_inventory_create(run_module, admin_user, organization, insights_credential):
+    # Create an insights credential
+
     result = run_module('tower_inventory', {
         'name': 'foo-inventory',
         'organization': organization.name,
         'variables': {'foo': 'bar', 'another-foo': {'barz': 'bar2'}},
+        'insights_credential': insights_credential.name,
         'state': 'present'
     }, admin_user)
+    assert not result.get('failed', False), result.get('msg', result)
 
     inv = Inventory.objects.get(name='foo-inventory')
     assert inv.variables == '{"foo": "bar", "another-foo": {"barz": "bar2"}}'
+    assert inv.insights_credential.name == insights_credential.name
 
     result.pop('module_args', None)
     result.pop('invocation', None)
diff --git a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml
--- a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml
+++ b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml
@@ -1,101 +1,140 @@
 ---
+- name: Generate a test ID
+  set_fact:
+    test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
+
 - name: Generate names
   set_fact:
-    inv_name1: "AWX-Collection-tests-tower_inventory-inv1-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
-    inv_name2: "AWX-Collection-tests-tower_inventory-inv2-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
-
-- name: Create an Inventory
-  tower_inventory:
-    name: "{{ inv_name1 }}"
-    organization: Default
-    state: present
-  register: result
-
-- assert:
-    that:
-      - "result is changed"
-
-- name: Test Inventory module idempotency
-  tower_inventory:
-    name: "{{ inv_name1 }}"
-    organization: Default
-    state: present
-  register: result
-
-- assert:
-    that:
-      - "result is not changed"
-
-- name: Fail Change Regular to Smart
-  tower_inventory:
-    name: "{{ inv_name1 }}"
-    organization: Default
-    kind: smart
-  register: result
-  ignore_errors: true
-
-- assert:
-    that:
-      - "result is failed"
-
-- name: Create a smart inventory
-  tower_inventory:
-    name: "{{ inv_name2 }}"
-    organization: Default
-    kind: smart
-    host_filter: name=foo
-  register: result
-
-- assert:
-    that:
-      - "result is changed"
-
-- name: Delete a smart inventory
-  tower_inventory:
-    name: "{{ inv_name2 }}"
-    organization: Default
-    kind: smart
-    host_filter: name=foo
-    state: absent
-  register: result
-
-- assert:
-    that:
-      - "result is changed"
-
-- name: Delete an Inventory
-  tower_inventory:
-    name: "{{ inv_name1 }}"
-    organization: Default
-    state: absent
-  register: result
-
-- assert:
-    that:
-      - "result is changed"
-
-- name: Delete a Non-Existent Inventory
-  tower_inventory:
-    name: "{{ inv_name1 }}"
-    organization: Default
-    state: absent
-  register: result
-
-- assert:
-    that:
-      - "result is not changed"
-
-- name: Check module fails with correct msg
-  tower_inventory:
-    name: test-inventory
-    description: Inventory Description
-    organization: test-non-existing-org
-    state: present
-  register: result
-  ignore_errors: true
-
-- assert:
-    that:
-      - "result is not changed"
-      - "result.msg =='Failed to update inventory, organization not found: The requested object could not be found.'
-        or result.msg =='The organizations test-non-existing-org was not found on the Tower server'"
+    inv_name1: "AWX-Collection-tests-tower_inventory-inv1-{{ test_id }}"
+    inv_name2: "AWX-Collection-tests-tower_inventory-inv2-{{ test_id }}"
+    cred_name1: "AWX-Collection-tests-tower_inventory-cred1-{{ test_id }}"
+
+- block:
+    - name: Create an Insights Credential
+      tower_credential:
+        name: "{{ cred_name1 }}"
+        organization: Default
+        kind: insights
+        inputs:
+          username: joe
+          password: secret
+        state: present
+      register: result
+
+    - assert:
+        that:
+          - "result is changed"
+
+    - name: Create an Inventory
+      tower_inventory:
+        name: "{{ inv_name1 }}"
+        organization: Default
+        insights_credential: "{{ cred_name1 }}"
+        state: present
+      register: result
+
+    - assert:
+        that:
+          - "result is changed"
+
+    - name: Test Inventory module idempotency
+      tower_inventory:
+        name: "{{ inv_name1 }}"
+        organization: Default
+        insights_credential: "{{ cred_name1 }}"
+        state: present
+      register: result
+
+    - assert:
+        that:
+          - "result is not changed"
+
+    - name: Fail Change Regular to Smart
+      tower_inventory:
+        name: "{{ inv_name1 }}"
+        organization: Default
+        kind: smart
+      register: result
+      ignore_errors: true
+
+    - assert:
+        that:
+          - "result is failed"
+
+    - name: Create a smart inventory
+      tower_inventory:
+        name: "{{ inv_name2 }}"
+        organization: Default
+        kind: smart
+        host_filter: name=foo
+      register: result
+
+    - assert:
+        that:
+          - "result is changed"
+
+    - name: Delete a smart inventory
+      tower_inventory:
+        name: "{{ inv_name2 }}"
+        organization: Default
+        kind: smart
+        host_filter: name=foo
+        state: absent
+      register: result
+
+    - assert:
+        that:
+          - "result is changed"
+
+    - name: Delete an Inventory
+      tower_inventory:
+        name: "{{ inv_name1 }}"
+        organization: Default
+        state: absent
+      register: result
+
+    - assert:
+        that:
+          - "result is changed"
+
+    - name: Delete a Non-Existent Inventory
+      tower_inventory:
+        name: "{{ inv_name1 }}"
+        organization: Default
+        state: absent
+      register: result
+
+    - assert:
+        that:
+          - "result is not changed"
+
+    - name: Check module fails with correct msg
+      tower_inventory:
+        name: test-inventory
+        description: Inventory Description
+        organization: test-non-existing-org
+        state: present
+      register: result
+      ignore_errors: true
+
+    - assert:
+        that:
+          - "result is not changed"
+          - "result.msg =='Failed to update inventory, organization not found: The requested object could not be found.'
+            or result.msg =='The organizations test-non-existing-org was not found on the Tower server'"
+  always:
+    - name: Delete Inventories
+      tower_inventory:
+        name: "{{ item }}"
+        organization: Default
+        state: absent
+      loop:
+        - "{{ inv_name1 }}"
+        - "{{ inv_name2 }}"
+
+    - name: Delete Insights Credential
+      tower_credential:
+        name: "{{ cred_name1 }}"
+        organization: "Default"
+        kind: insights
+        state: absent