Repository: ansible/awx

Issue:
Add project update to the awx.awx.tower_project ansible module
<!-- 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
Add project update to the awx.awx.tower_project ansible module

##### SUMMARY
We are able to launch a project update with awxkit but not with the pendant ansible module in 16.0.0



Discussion:

Resolving patch:
diff --git a/awx_collection/plugins/modules/tower_project.py b/awx_collection/plugins/modules/tower_project.py
--- a/awx_collection/plugins/modules/tower_project.py
+++ b/awx_collection/plugins/modules/tower_project.py
@@ -91,6 +91,8 @@
     timeout:
       description:
         - The amount of time (in seconds) to run before the SCM Update is canceled. A value of 0 means no timeout.
+        - If waiting for the project to update this will abort after this
+          amount of seconds
       default: 0
       type: int
       aliases:
@@ -133,6 +135,19 @@
         - list of notifications to send on error
       type: list
       elements: str
+    update_project:
+      description:
+        - Force project to update after changes.
+        - Used in conjunction with wait, interval, and timeout.
+      default: False
+      type: bool
+    interval:
+      description:
+        - The interval to request an update from Tower.
+        - Requires wait.
+      required: False
+      default: 1
+      type: float
 extends_documentation_fragment: awx.awx.auth
 '''
 
@@ -164,7 +179,13 @@
 
 
 def wait_for_project_update(module, last_request):
-    # The current running job for the udpate is in last_request['summary_fields']['current_update']['id']
+    # The current running job for the update is in last_request['summary_fields']['current_update']['id']
+
+    # Get parameters that were not passed in
+    update_project = module.params.get('update_project')
+    wait = module.params.get('wait')
+    timeout = module.params.get('timeout')
+    interval = module.params.get('interval')
 
     if 'current_update' in last_request['summary_fields']:
         running = True
@@ -177,6 +198,25 @@ def wait_for_project_update(module, last_request):
 
         if result['status'] != 'successful':
             module.fail_json(msg="Project update failed")
+    elif update_project:
+        result = module.post_endpoint(last_request['related']['update'])
+
+        if result['status_code'] != 202:
+            module.fail_json(msg="Failed to update project, see response for details", response=result)
+
+        if not wait:
+            module.exit_json(**module.json_output)
+
+        # Grab our start time to compare against for the timeout
+        start = time.time()
+
+        # Invoke wait function
+        module.wait_on_url(
+            url=result['json']['url'],
+            object_name=module.get_item_name(last_request),
+            object_type='Project Update',
+            timeout=timeout, interval=interval
+        )
 
     module.exit_json(**module.json_output)
 
@@ -205,6 +245,8 @@ def main():
         notification_templates_error=dict(type="list", elements='str'),
         state=dict(choices=['present', 'absent'], default='present'),
         wait=dict(type='bool', default=True),
+        update_project=dict(default=False, type='bool'),
+        interval=dict(default=1.0, type='float'),
     )
 
     # Create a module for ourselves
@@ -231,6 +273,8 @@ def main():
     organization = module.params.get('organization')
     state = module.params.get('state')
     wait = module.params.get('wait')
+    update_project = module.params.get('update_project')
+    interval = module.params.get('interval')
 
     # Attempt to look up the related items the user specified (these will fail the module if not found)
     lookup_data = {}
@@ -300,7 +344,7 @@ def main():
     # If we are doing a not manual project, register our on_change method
     # An on_change function, if registered, will fire after an post_endpoint or update_if_needed completes successfully
     on_change = None
-    if wait and scm_type != '':
+    if wait and scm_type != '' or update_project and scm_type != '':
         on_change = wait_for_project_update
 
     # If the state was present and we can let the module build or update the existing project, this will return on its own
diff --git a/awx_collection/plugins/modules/tower_project_update.py b/awx_collection/plugins/modules/tower_project_update.py
--- a/awx_collection/plugins/modules/tower_project_update.py
+++ b/awx_collection/plugins/modules/tower_project_update.py
@@ -126,9 +126,6 @@ def main():
     # Grab our start time to compare against for the timeout
     start = time.time()
 
-    if not wait:
-        module.exit_json(**module.json_output)
-
     # Invoke wait function
     module.wait_on_url(
         url=result['json']['url'],


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
@@ -37,7 +37,7 @@
 # Add the module name as the key with the value being the list of params to ignore
 no_api_parameter_ok = {
     # The wait is for whether or not to wait for a project update on change
-    'tower_project': ['wait'],
+    'tower_project': ['wait', 'interval', 'update_project'],
     # Existing_token and id are for working with an existing tokens
     'tower_token': ['existing_token', 'existing_token_id'],
     # /survey spec is now how we handle associations
diff --git a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml
--- a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml
+++ b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml
@@ -138,6 +138,29 @@
     that:
       - result is changed
 
+- name: Update a git project, update the project and wait.
+  tower_project:
+    name: "{{ project_name3 }}"
+    organization: Default
+    scm_type: git
+    scm_branch: empty_branch
+    scm_url: https://github.com/ansible/test-playbooks
+    allow_override: true
+    wait: true
+    update_project: true
+  register: result
+
+- name: Update a git project, update the project without waiting.
+  tower_project:
+    name: "{{ project_name3 }}"
+    organization: Default
+    scm_type: git
+    scm_branch: empty_branch
+    scm_url: https://github.com/ansible/test-playbooks
+    wait: false
+    update_project: true
+  register: result
+
 - name: Create a job template that overrides the project scm_branch
   tower_job_template:
     name: "{{ jt1 }}"