From 8af315cf29f6c227b4b51dad1325546786f90439 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Tue, 11 Aug 2020 08:54:10 -0500 Subject: [PATCH 01/69] Create tower_project_update Add module to update tower project and wait until synced. --- .../plugins/modules/tower_project_update.py | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 awx_collection/plugins/modules/tower_project_update.py diff --git a/awx_collection/plugins/modules/tower_project_update.py b/awx_collection/plugins/modules/tower_project_update.py new file mode 100644 index 0000000000..1952244c93 --- /dev/null +++ b/awx_collection/plugins/modules/tower_project_update.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# 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.0', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: tower_project_update +author: "Sean Sullivan (@sean-m-sullivan)" +short_description: Sync a Project in Ansible Tower +description: + - Sync an Ansible Tower Project. See + U(https://www.ansible.com/tower) for an overview. +options: + name: + description: + - The name of the workflow template to run. + required: True + type: str + aliases: + - project + organization: + description: + - Organization the workflow job template exists in. + - Used to help lookup the object, cannot be modified using this module. + - If not provided, will lookup by name only, which does not work with duplicates. + type: str + wait: + description: + - Wait for the workflow to complete. + default: True + type: bool + interval: + description: + - The interval to request an update from Tower. + required: False + default: 1 + type: float + timeout: + description: + - If waiting for the workflow to complete this will abort after this + amount of seconds + type: int +extends_documentation_fragment: awx.awx.auth +''' + +RETURN = ''' +job_info: + description: dictionary containing information about the project updated + returned: If project synced + type: dict +''' + + +EXAMPLES = ''' +- name: Launch a workflow with a timeout of 10 seconds + tower_project_update: + project: "Networking Project" + timeout: 10 + +- name: Launch a Workflow with extra_vars without waiting + tower_project_update: + project: "Networking Project" + wait: False +''' + +from ..module_utils.tower_api import TowerModule +import json +import time + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + name=dict(required=True, aliases=['project']), + organization=dict(), + wait=dict(required=False, default=True, type='bool'), + interval=dict(required=False, default=1.0, type='float'), + timeout=dict(required=False, default=None, type='int'), + ) + + # Create a module for ourselves + module = TowerModule(argument_spec=argument_spec) + + # Extract our parameters + name = module.params.get('name') + organization = module.params.get('organization') + wait = module.params.get('wait') + interval = module.params.get('interval') + timeout = module.params.get('timeout') + + # Attempt to look up project based on the provided name + lookup_data = {'name': name} + if organization: + lookup_data['organization'] = module.resolve_name_to_id('organizations', organization) + project = module.get_one('projects', data=lookup_data) + + if project is None: + module.fail_json(msg="Unable to find workflow job template") + + # Launch the job + result = module.post_endpoint(project['related']['update']) + + if result['status_code'] != 202: + module.fail_json(msg="Failed to update project, see response for details", response=result) + + module.json_output['changed'] = True + module.json_output['id'] = result['json']['id'] + module.json_output['status'] = result['json']['status'] + + if not wait: + module.exit_json(**module.json_output) + + # Grab our start time to compare against for the timeout + start = time.time() + + job_url = result['json']['url'] + while not result['json']['finished']: + # If we are past our time out fail with a message + if timeout and timeout < time.time() - start: + module.json_output['msg'] = "Monitoring aborted due to timeout" + module.fail_json(**module.json_output) + + # Put the process to sleep for our interval + time.sleep(interval) + + result = module.get_endpoint(job_url) + module.json_output['status'] = result['json']['status'] + + # If the update has failed, we want to raise a task failure for that so we get a non-zero response. + if result['json']['failed']: + module.json_output['msg'] = 'The project "{0}" failed'.format(name) + module.fail_json(**module.json_output) + + module.exit_json(**module.json_output) + + +if __name__ == '__main__': + main() From af1fc5a9e9d71733d2b2162fc2ba920ed3481fbc Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Tue, 11 Aug 2020 09:28:17 -0500 Subject: [PATCH 02/69] add tests --- .../tower_project_update/tasks/main.yml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml diff --git a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml new file mode 100644 index 0000000000..be49c6734e --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Update a project without waiting + tower_project_update: + name: "AWX-Collection-tests-tower_job_template-proj-{{ test_id }}" + organization: Default + wait: False + register: result + +- assert: + that: + - result is changed + +- name: Update a project and wait + tower_project_update: + name: "AWX-Collection-tests-tower_job_template-proj-{{ test_id }}" + organization: Default + wait: True + register: result + +- assert: + that: + - result is changed + From 813e38636a18b2055497de64e4414e1dce5e1c94 Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Tue, 11 Aug 2020 09:38:18 -0500 Subject: [PATCH 03/69] fix documentation --- .../plugins/modules/tower_project_update.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/awx_collection/plugins/modules/tower_project_update.py b/awx_collection/plugins/modules/tower_project_update.py index 1952244c93..b71d187ff2 100644 --- a/awx_collection/plugins/modules/tower_project_update.py +++ b/awx_collection/plugins/modules/tower_project_update.py @@ -21,20 +21,20 @@ description: options: name: description: - - The name of the workflow template to run. + - The name of the project to update. required: True type: str aliases: - project organization: description: - - Organization the workflow job template exists in. + - Organization the project exists in. - Used to help lookup the object, cannot be modified using this module. - If not provided, will lookup by name only, which does not work with duplicates. type: str wait: description: - - Wait for the workflow to complete. + - Wait for the project to update. default: True type: bool interval: @@ -45,14 +45,14 @@ options: type: float timeout: description: - - If waiting for the workflow to complete this will abort after this + - If waiting for the project to update this will abort after this amount of seconds type: int extends_documentation_fragment: awx.awx.auth ''' RETURN = ''' -job_info: +project_info: description: dictionary containing information about the project updated returned: If project synced type: dict @@ -60,12 +60,12 @@ job_info: EXAMPLES = ''' -- name: Launch a workflow with a timeout of 10 seconds +- name: Launch a project with a timeout of 10 seconds tower_project_update: project: "Networking Project" timeout: 10 -- name: Launch a Workflow with extra_vars without waiting +- name: Launch a Project with extra_vars without waiting tower_project_update: project: "Networking Project" wait: False @@ -103,9 +103,9 @@ def main(): project = module.get_one('projects', data=lookup_data) if project is None: - module.fail_json(msg="Unable to find workflow job template") + module.fail_json(msg="Unable to find project") - # Launch the job + # Update the project result = module.post_endpoint(project['related']['update']) if result['status_code'] != 202: @@ -121,7 +121,7 @@ def main(): # Grab our start time to compare against for the timeout start = time.time() - job_url = result['json']['url'] + project_url = result['json']['url'] while not result['json']['finished']: # If we are past our time out fail with a message if timeout and timeout < time.time() - start: @@ -131,7 +131,7 @@ def main(): # Put the process to sleep for our interval time.sleep(interval) - result = module.get_endpoint(job_url) + result = module.get_endpoint(project_url) module.json_output['status'] = result['json']['status'] # If the update has failed, we want to raise a task failure for that so we get a non-zero response. From 69dc0a892f3037f00fd1bd0f9438fdc6678aad10 Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Tue, 11 Aug 2020 09:56:08 -0500 Subject: [PATCH 04/69] fix lint --- .../integration/targets/tower_project_update/tasks/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml index be49c6734e..0693f125d1 100644 --- a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml @@ -20,4 +20,3 @@ - assert: that: - result is changed - From d6815e51140657f778bc5804282a42f1494eae92 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Thu, 20 Aug 2020 10:30:43 -0500 Subject: [PATCH 05/69] Removed reference to sync Updated references to sync in favour of update. --- awx_collection/plugins/modules/tower_project_update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/tower_project_update.py b/awx_collection/plugins/modules/tower_project_update.py index b71d187ff2..6d98c00c77 100644 --- a/awx_collection/plugins/modules/tower_project_update.py +++ b/awx_collection/plugins/modules/tower_project_update.py @@ -14,9 +14,9 @@ DOCUMENTATION = ''' --- module: tower_project_update author: "Sean Sullivan (@sean-m-sullivan)" -short_description: Sync a Project in Ansible Tower +short_description: Update a Project in Ansible Tower description: - - Sync an Ansible Tower Project. See + - Update a Ansible Tower Project. See U(https://www.ansible.com/tower) for an overview. options: name: From 3794f095cfefef72ed87ee483b937716e00f2443 Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Sat, 22 Aug 2020 13:43:14 -0500 Subject: [PATCH 06/69] update to tower_project_update --- .../plugins/modules/tower_project_update.py | 20 +++++---- .../tower_project_update/tasks/main.yml | 41 +++++++++++++++++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/awx_collection/plugins/modules/tower_project_update.py b/awx_collection/plugins/modules/tower_project_update.py index 6d98c00c77..e9f85321d5 100644 --- a/awx_collection/plugins/modules/tower_project_update.py +++ b/awx_collection/plugins/modules/tower_project_update.py @@ -52,10 +52,16 @@ extends_documentation_fragment: awx.awx.auth ''' RETURN = ''' -project_info: - description: dictionary containing information about the project updated - returned: If project synced - type: dict +id: + description: project id of the updated project + returned: success + type: int + sample: 86 +status: + description: status of the updated project + returned: success + type: str + sample: pending ''' @@ -81,9 +87,9 @@ def main(): argument_spec = dict( name=dict(required=True, aliases=['project']), organization=dict(), - wait=dict(required=False, default=True, type='bool'), - interval=dict(required=False, default=1.0, type='float'), - timeout=dict(required=False, default=None, type='int'), + wait=dict(default=True, type='bool'), + interval=dict(default=1.0, type='float'), + timeout=dict(default=None, type='int'), ) # Create a module for ourselves diff --git a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml index 0693f125d1..82ef03b9b0 100644 --- a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml @@ -1,9 +1,31 @@ --- +- name: Generate a random string for test + set_fact: + test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + when: test_id is not defined + +- name: Generate names + set_fact: + project_name1: "AWX-Collection-tests-tower_project_update-project-{{ test_id }}" + +- name: Create a git project without credentials without waiting + tower_project: + name: "{{ project_name1 }}" + organization: Default + scm_type: git + scm_url: https://github.com/ansible/test-playbooks + wait: false + register: result + +- assert: + that: + - result is changed + - name: Update a project without waiting tower_project_update: - name: "AWX-Collection-tests-tower_job_template-proj-{{ test_id }}" + name: "{{ project_name1 }}" organization: Default - wait: False + wait: false register: result - assert: @@ -12,11 +34,22 @@ - name: Update a project and wait tower_project_update: - name: "AWX-Collection-tests-tower_job_template-proj-{{ test_id }}" + name: "{{ project_name1 }}" organization: Default - wait: True + wait: true register: result - assert: that: - result is changed + +- name: Delete the test project 1 + tower_project: + name: "{{ project_name1 }}" + organization: Default + state: absent + register: result + +- assert: + that: + - result is changed \ No newline at end of file From cda05c4f03528d770ccb81dc72d46a315531ca45 Mon Sep 17 00:00:00 2001 From: Sean Sullivan Date: Sat, 22 Aug 2020 20:16:27 -0500 Subject: [PATCH 07/69] Updated test for lint Updated test for lint --- .../integration/targets/tower_project_update/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml index 82ef03b9b0..e7a03ce4c9 100644 --- a/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project_update/tasks/main.yml @@ -52,4 +52,4 @@ - assert: that: - - result is changed \ No newline at end of file + - result is changed From 90e8d5697ecdc3f584ef396417ae53793f605915 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 24 Aug 2020 11:17:24 -0400 Subject: [PATCH 08/69] Adds fix to load template details screen even for users without webhook key permissions --- awx/ui_next/src/api/models/JobTemplates.js | 4 + .../src/api/models/WorkflowJobTemplates.js | 4 + awx/ui_next/src/screens/Template/Template.jsx | 17 ++-- .../src/screens/Template/Template.test.jsx | 86 +++++++++++++++++-- .../screens/Template/WorkflowJobTemplate.jsx | 16 +++- .../Template/WorkflowJobTemplate.test.jsx | 19 +++- .../Template/shared/data.job_template.json | 3 +- 7 files changed, 132 insertions(+), 17 deletions(-) diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index 0e2eba8079..2ea8c1137d 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -23,6 +23,10 @@ class JobTemplates extends SchedulesMixin( return this.http.post(`${this.baseUrl}${id}/launch/`, data); } + readTemplateOptions(id) { + return this.http.options(`${this.baseUrl}/${id}/`); + } + readLaunch(id) { return this.http.get(`${this.baseUrl}${id}/launch/`); } diff --git a/awx/ui_next/src/api/models/WorkflowJobTemplates.js b/awx/ui_next/src/api/models/WorkflowJobTemplates.js index 3074608796..d074900f58 100644 --- a/awx/ui_next/src/api/models/WorkflowJobTemplates.js +++ b/awx/ui_next/src/api/models/WorkflowJobTemplates.js @@ -12,6 +12,10 @@ class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) { return this.http.get(`${this.baseUrl}${id}/webhook_key/`); } + readWorkflowJobTemplateOptions(id) { + return this.http.options(`${this.baseUrl}/${id}/`); + } + updateWebhookKey(id) { return this.http.post(`${this.baseUrl}${id}/webhook_key/`); } diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx index 2358fb109b..12629ebe51 100644 --- a/awx/ui_next/src/screens/Template/Template.jsx +++ b/awx/ui_next/src/screens/Template/Template.jsx @@ -36,18 +36,23 @@ function Template({ i18n, me, setBreadcrumb }) { request: loadTemplateAndRoles, } = useRequest( useCallback(async () => { - const [{ data }, notifAdminRes] = await Promise.all([ + const [{ data }, actions, notifAdminRes] = await Promise.all([ JobTemplatesAPI.readDetail(templateId), + JobTemplatesAPI.readTemplateOptions(templateId), OrganizationsAPI.read({ page_size: 1, role_level: 'notification_admin_role', }), ]); - if (data.webhook_service && data?.related?.webhook_key) { - const { - data: { webhook_key }, - } = await JobTemplatesAPI.readWebhookKey(templateId); - data.webhook_key = webhook_key; + + if (actions.data.actions.PUT) { + if (data.webhook_service && data?.related?.webhook_key) { + const { + data: { webhook_key }, + } = await JobTemplatesAPI.readWebhookKey(templateId); + + data.webhook_key = webhook_key; + } } setBreadcrumb(data); diff --git a/awx/ui_next/src/screens/Template/Template.test.jsx b/awx/ui_next/src/screens/Template/Template.test.jsx index 8176e4486a..a38209db75 100644 --- a/awx/ui_next/src/screens/Template/Template.test.jsx +++ b/awx/ui_next/src/screens/Template/Template.test.jsx @@ -18,11 +18,16 @@ const mockMe = { is_system_auditor: false, }; describe('