From 3fa3ddf04b35e5f8898631170d52177f6a80ed07 Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Thu, 4 Jun 2020 00:12:02 -0500 Subject: [PATCH 01/44] tower_job_template to use organizations --- .../plugins/modules/tower_job_template.py | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 946ffdf5e5..37011a4502 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -45,6 +45,14 @@ options: description: - Name of the inventory to use for the job template. type: str + organization: + description: + - Organization the job template exists in. + - Used to help lookup the object, cannot be modified using this module. + - The Organization is inferred from the associated project + - If not provided, will lookup by name only, which does not work with duplicates. + type: str + version_added: 2.10 project: description: - Name of the project to use for the job template. @@ -299,6 +307,7 @@ EXAMPLES = ''' tower_job_template: name: "Ping" job_type: "run" + organization: "Default" inventory: "Local" project: "Demo" playbook: "ping.yml" @@ -329,6 +338,10 @@ from ..module_utils.tower_api import TowerModule import json +def versiontuple(v): + return tuple(map(int, (v.split(".")))) + + def update_survey(module, last_request): spec_endpoint = last_request.get('related', {}).get('survey_spec') if module.params.get('survey_spec') == {}: @@ -349,6 +362,7 @@ def main(): name=dict(required=True), new_name=dict(), description=dict(default=''), + organization=dict(), job_type=dict(choices=['run', 'check']), inventory=dict(), project=dict(), @@ -415,19 +429,26 @@ def main(): credentials = [] credentials.append(credential) + new_fields = {} + search_fields = {'name': name} + + # Attempt to look up the related items the user specified (these will fail the module if not found) + organization_id = None + organization = module.params.get('organization') + if organization: + organization_id = module.resolve_name_to_id('organizations', organization) + tower_version = module.get_endpoint('ping')['json']['version'] + if versiontuple(tower_version) >= versiontuple("3.7.0"): + search_fields['organization'] = new_fields['organization'] = organization_id + # Attempt to look up an existing item based on the provided data - existing_item = module.get_one('job_templates', **{ - 'data': { - 'name': name, - } - }) + existing_item = module.get_one('job_templates', **{'data': search_fields}) if state == 'absent': # If the state was absent we can let the module delete it if needed, the module will handle exiting from this module.delete_if_needed(existing_item) # Create the data that gets sent for create and update - new_fields = {} new_fields['name'] = new_name if new_name else name for field_name in ( 'description', 'job_type', 'playbook', 'scm_branch', 'forks', 'limit', 'verbosity', @@ -454,7 +475,20 @@ def main(): if inventory is not None: new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory) if project is not None: - new_fields['project'] = module.resolve_name_to_id('projects', project) + if organization_id is not None: + project_data = module.get_one('projects', **{ + 'data': { + 'name': project, + 'organization': organization_id, + } + }) + if project_data is None: + module.fail_json(msg="The project {0} in organization {1} was not found on the Tower server".format( + project, organization + )) + new_fields['project'] = project_data['id'] + else: + new_fields['project'] = module.resolve_name_to_id('projects', project) if webhook_credential is not None: new_fields['webhook_credential'] = module.resolve_name_to_id('credentials', webhook_credential) From ced8f42835fc917bcfa5bd757f135518c888fbac Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Thu, 4 Jun 2020 14:19:46 -0400 Subject: [PATCH 02/44] Force worker processes to have a different signal handler from the parent Situations have come up where the 5+ minute kill signal for run_task_manager is emitted to the worker process running it, but since the worker improperly inherited the AWXConsumerBase().stop() handler a deadlock ultimately was triggered on the database connection. --- awx/main/dispatch/worker/base.py | 1 + awx/main/scheduler/task_manager.py | 1 + 2 files changed, 2 insertions(+) diff --git a/awx/main/dispatch/worker/base.py b/awx/main/dispatch/worker/base.py index b0611676fa..7001cd9bb9 100644 --- a/awx/main/dispatch/worker/base.py +++ b/awx/main/dispatch/worker/base.py @@ -35,6 +35,7 @@ class WorkerSignalHandler: def __init__(self): self.kill_now = False + signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, self.exit_gracefully) def exit_gracefully(self, *args, **kwargs): diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index 247f37544d..c16a1d1fa0 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -581,3 +581,4 @@ class TaskManager(): logger.debug("Starting Scheduler") with task_manager_bulk_reschedule(): self._schedule() + logger.debug("Finishing Scheduler") From 40cd87f25352993259208d487af75e008a09b2c4 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Wed, 3 Jun 2020 14:38:05 -0400 Subject: [PATCH 03/44] Adds message for unsynced projects for copy to clipboard button --- .../ClipboardCopyButton.jsx | 10 ++++++- .../ClipboardCopyButton.test.jsx | 13 ++++++++ .../Project/ProjectList/ProjectListItem.jsx | 23 +++++++++----- .../ProjectList/ProjectListItem.test.jsx | 30 +++++++++++++++++++ 4 files changed, 68 insertions(+), 8 deletions(-) diff --git a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx index 8fcac00468..30367bff79 100644 --- a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx +++ b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx @@ -40,7 +40,13 @@ class ClipboardCopyButton extends React.Component { }; render() { - const { clickTip, entryDelay, exitDelay, hoverTip } = this.props; + const { + clickTip, + entryDelay, + exitDelay, + hoverTip, + isDisabled, + } = this.props; const { copied } = this.state; return ( @@ -51,6 +57,7 @@ class ClipboardCopyButton extends React.Component { content={copied ? clickTip : hoverTip} > @@ -70,10 +70,10 @@ class ClipboardCopyButton extends React.Component { } ClipboardCopyButton.propTypes = { - clickTip: PropTypes.string.isRequired, + copyTip: PropTypes.string.isRequired, entryDelay: PropTypes.number, exitDelay: PropTypes.number, - hoverTip: PropTypes.string.isRequired, + copiedSuccessTip: PropTypes.string.isRequired, stringToCopy: PropTypes.string.isRequired, switchDelay: PropTypes.number, isDisabled: PropTypes.bool.isRequired, diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index c5090dfa30..d5651a130e 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -128,14 +128,14 @@ function ProjectListItem({ {project.scm_revision.substring(0, 7)} {!project.scm_revision && ( )} , ]} diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx index d833d58c27..7866015703 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx @@ -245,7 +245,7 @@ describe('', () => { ); expect( wrapper.find('span[aria-label="copy to clipboard disabled"]').text() - ).toBe('Sync to activate'); + ).toBe('Sync for revision'); expect(wrapper.find('ClipboardCopyButton').prop('isDisabled')).toBe(true); }); }); From 4de7de3ce9b92a546832edd8e8af97c20a4d2a0a Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Fri, 5 Jun 2020 13:29:37 -0400 Subject: [PATCH 05/44] Prevent exception for Non value --- awx_collection/plugins/modules/tower_settings.py | 4 ++++ .../tests/integration/targets/tower_settings/tasks/main.yml | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/awx_collection/plugins/modules/tower_settings.py b/awx_collection/plugins/modules/tower_settings.py index b7ecde45ef..a66acadf44 100644 --- a/awx_collection/plugins/modules/tower_settings.py +++ b/awx_collection/plugins/modules/tower_settings.py @@ -82,6 +82,10 @@ except ImportError: def coerce_type(module, value): + # If our value is already None we can just return directly + if value == None: + return value + yaml_ish = bool(( value.startswith('{') and value.endswith('}') ) or ( diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml index a02ca673de..6cebe979e2 100644 --- a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -74,3 +74,9 @@ - assert: that: - "result is changed" + +- name: Handle an omit value + tower_settings: + name: AWX_PROOT_BASE_PATH + value: '{{ junk_var | default(omit) }}' + From e6416d770b75c37988f56bcef9d0f37d4c59b3a4 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 21 May 2020 13:14:10 -0400 Subject: [PATCH 06/44] Initial cut at tower_token module --- .../plugins/module_utils/tower_api.py | 10 ++ awx_collection/plugins/modules/tower_token.py | 157 ++++++++++++++++++ awx_collection/test/awx/test_token.py | 28 ++++ .../targets/tower_token/tasks/main.yml | 25 +++ 4 files changed, 220 insertions(+) create mode 100644 awx_collection/plugins/modules/tower_token.py create mode 100644 awx_collection/test/awx/test_token.py create mode 100644 awx_collection/tests/integration/targets/tower_token/tasks/main.yml diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 5d4f221f4e..f950829328 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -96,6 +96,13 @@ class TowerModule(AnsibleModule): if direct_value is not None: setattr(self, short_param, direct_value) + # Perform magic depending on whether tower_oauthtoken is a string or a dict + if self.params.get('tower_oauthtoken'): + if type(self.params.get('tower_oauthtoken')) is dict: + setattr(self, 'oauth_token', self.params.get('tower_oauthtoken')['token']) + elif 'token' in self.params.get('tower_oauthtoken'): + setattr(self, 'oauth_token', self.params.get('tower_oauthtoken')) + # Perform some basic validation if not re.match('^https{0,1}://', self.host): self.host = "https://{0}".format(self.host) @@ -504,6 +511,9 @@ class TowerModule(AnsibleModule): item_name = existing_item['username'] elif 'identifier' in existing_item: item_name = existing_item['identifier'] + 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'] else: self.fail_json(msg="Unable to process delete of {0} due to missing name".format(item_type)) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py new file mode 100644 index 0000000000..7e229ac2c1 --- /dev/null +++ b/awx_collection/plugins/modules/tower_token.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# coding: utf-8 -*- + + +# (c) 2020, John Westcott IV +# 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_token +author: "John Westcott IV (@john-westcott-iv)" +version_added: "2.3" +short_description: create, update, or destroy Ansible Tower tokens. +description: + - Create, update, or destroy Ansible Tower tokens. See + U(https://www.ansible.com/tower) for an overview. +options: + description: + description: + - Optional description of this access token. + required: False + type: str + default: '' + application: + description: + - The application tied to this token. + required: False + type: str + scope: + description: + - Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write']. + required: False + type: str + default: 'write' + choices: ["read", "write"] + existing_token: + description: An existing token (for use with state absent) + type: dict + state: + description: + - Desired state of the resource. + choices: ["present", "absent"] + default: "present" + type: str + tower_oauthtoken: + description: + - The Tower OAuth token to use. + required: False + type: str + version_added: "3.7" +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +- name: Create a new token using an existing token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + tower_oauthtoken: "{{ ny_existing_token }}" + register: new_token + +- name: Use our new token to make another call + tower_job_list: + tower_oauthtoken: "{{ tower_token }}" + +- name: Delete our Token with the token we created + tower_token: + existing_token: "{{ tower_token }}" + state: absent +''' + +RETURNS = ''' +tower_token: + type: dict + description: A Tower token object which can be used for auth or token deletion + returned: on successful create +''' + +from ..module_utils.tower_api import TowerModule + + +def return_token(module, last_response): + # A token is special because you can never get the actual token ID back from the API. + # So the default module return would give you an ID but then the token would forever be masked on you. + # This method will return the entire token object we got back so that a user has access to the token + + module.json_output['token'] = last_response['token'] + module.json_output['ansible_facts'] = { + 'tower_token': last_response, + } + module.exit_json(**module.json_output) + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + description=dict(), + application=dict(), + scope=dict(choices=['read', 'write'], default='write'), + existing_token=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') + application = module.params.get('application') + scope = module.params.get('scope') + existing_token = module.params.get('existing_token') + state = module.params.get('state') + + with open('/tmp/john', 'w') as f: + f.write("State is {0}".format(state)) + + if state == 'absent': + with open('/tmp/john', 'a') as f: + f.write("Starting delete") + # If the state was absent we can let the module delete it if needed, the module will handle exiting from this + module.delete_if_needed(existing_token) + + # Attempt to look up the related items the user specified (these will fail the module if not found) + application_id = None + if application: + application_id = module.resolve_name_to_id('applications', application) + + # Create the data that gets sent for create and update + new_fields = {} + if description is not None: + new_fields['description'] = description + if application is not None: + new_fields['application'] = application_id + if scope is not None: + new_fields['scope'] = scope + + # If the state was present and we can let the module build or update the existing item, this will return on its own + module.create_or_update_if_needed( + None, new_fields, + endpoint='tokens', item_type='token', + associations={ + }, + on_create=return_token, + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/test/awx/test_token.py b/awx_collection/test/awx/test_token.py new file mode 100644 index 0000000000..107a833af6 --- /dev/null +++ b/awx_collection/test/awx/test_token.py @@ -0,0 +1,28 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from awx.main.models import OAuth2AccessToken + + +@pytest.mark.django_db +def test_create_token(run_module, admin_user): + + module_args = { + 'description': 'barfoo', + 'state': 'present', + 'scope': 'read', + 'tower_host': None, + 'tower_username': None, + 'tower_password': None, + 'validate_certs': None, + 'tower_oauthtoken': None, + 'tower_config_file': None, + } + + result = run_module('tower_token', module_args, admin_user) + assert result, result.get('changed') + + tokens = OAuth2AccessToken.objects.filter(description='barfoo') + assert len(tokens) == 1, tokens[0].description == 'barfoo' diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml new file mode 100644 index 0000000000..74fcd05237 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: Generate names + set_fact: + token_description: "AWX-Collection-tests-tower_token-description-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Create a Token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + register: new_token + +- name: Validate our token works by token + tower_job_list: + tower_oauthtoken: "{{ tower_token.token }}" + +- name: Validate out token works by object + tower_job_list: + tower_oauthtoken: "{{ tower_token }}" + +- name: Delete our Token with our own token + tower_token: + existing_token: "{{ tower_token }}" + tower_oauthtoken: "{{ tower_token }}" + state: absent From e50c8fc9c9b4c37a0af17b525bc910a39f195366 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 21 May 2020 14:10:14 -0400 Subject: [PATCH 07/44] Updated docs and added block on tests --- awx_collection/plugins/modules/tower_token.py | 33 +++++++++-------- .../targets/tower_token/tasks/main.yml | 37 ++++++++++--------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 7e229ac2c1..638b89cc6c 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -20,8 +20,9 @@ author: "John Westcott IV (@john-westcott-iv)" version_added: "2.3" short_description: create, update, or destroy Ansible Tower tokens. description: - - Create, update, or destroy Ansible Tower tokens. See + - Create or destroy Ansible Tower tokens. See U(https://www.ansible.com/tower) for an overview. + - If you create a token it is your responsibility to delete the token. options: description: description: @@ -60,22 +61,24 @@ extends_documentation_fragment: awx.awx.auth ''' EXAMPLES = ''' -- name: Create a new token using an existing token - tower_token: - description: '{{ token_description }}' - scope: "write" - state: present - tower_oauthtoken: "{{ ny_existing_token }}" - register: new_token +- block: + - name: Create a new token using an existing token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + tower_oauthtoken: "{{ ny_existing_token }}" -- name: Use our new token to make another call - tower_job_list: - tower_oauthtoken: "{{ tower_token }}" + - name: Use our new token to make another call + tower_job_list: + tower_oauthtoken: "{{ tower_token }}" -- name: Delete our Token with the token we created - tower_token: - existing_token: "{{ tower_token }}" - state: absent + always: + - name: Delete our Token with the token we created + tower_token: + existing_token: "{{ tower_token }}" + state: absent + when: tower_token is defined ''' RETURNS = ''' diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 74fcd05237..86edd620d7 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -3,23 +3,26 @@ set_fact: token_description: "AWX-Collection-tests-tower_token-description-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" -- name: Create a Token - tower_token: - description: '{{ token_description }}' - scope: "write" - state: present - register: new_token +- block: + - name: Create a Token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + register: new_token -- name: Validate our token works by token - tower_job_list: - tower_oauthtoken: "{{ tower_token.token }}" + - name: Validate our token works by token + tower_job_list: + tower_oauthtoken: "{{ tower_token.token }}" -- name: Validate out token works by object - tower_job_list: - tower_oauthtoken: "{{ tower_token }}" + - name: Validate out token works by object + tower_job_list: + tower_oauthtoken: "{{ tower_token }}" -- name: Delete our Token with our own token - tower_token: - existing_token: "{{ tower_token }}" - tower_oauthtoken: "{{ tower_token }}" - state: absent + always: + - name: Delete our Token with our own token + tower_token: + existing_token: "{{ tower_token }}" + tower_oauthtoken: "{{ tower_token }}" + state: absent + when: tower_token is defined From 6e9a43513ea9d5116ea6a3713231eca76e323e87 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 21 May 2020 14:14:14 -0400 Subject: [PATCH 08/44] Removed some debugging and added an exmaple --- awx_collection/plugins/modules/tower_token.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 638b89cc6c..8689f1625d 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -69,6 +69,19 @@ EXAMPLES = ''' state: present tower_oauthtoken: "{{ ny_existing_token }}" + - name: Delete this token + tower_token: + existing_token: "{{ tower_token }}" + state: absent + + - name: Create a new token using username/password + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + tower_username: "{{ ny_username }}" + tower_password: "{{ ny_password }}" + - name: Use our new token to make another call tower_job_list: tower_oauthtoken: "{{ tower_token }}" @@ -123,12 +136,7 @@ def main(): existing_token = module.params.get('existing_token') state = module.params.get('state') - with open('/tmp/john', 'w') as f: - f.write("State is {0}".format(state)) - if state == 'absent': - with open('/tmp/john', 'a') as f: - f.write("Starting delete") # If the state was absent we can let the module delete it if needed, the module will handle exiting from this module.delete_if_needed(existing_token) From 1dbea4614ba4d29ab7535649fe5cef9704451fa0 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 26 May 2020 12:30:43 -0400 Subject: [PATCH 09/44] Rebasing and fixing oauthtoken docs --- awx_collection/plugins/doc_fragments/auth.py | 3 ++- awx_collection/plugins/modules/tower_token.py | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/awx_collection/plugins/doc_fragments/auth.py b/awx_collection/plugins/doc_fragments/auth.py index 21e8d07482..c4cc6fcc3f 100644 --- a/awx_collection/plugins/doc_fragments/auth.py +++ b/awx_collection/plugins/doc_fragments/auth.py @@ -32,7 +32,8 @@ options: description: - The Tower OAuth token to use. - If value not set, will try environment variable C(TOWER_OAUTH_TOKEN) and then config files - type: str + type: raw + version_added: "3.7" validate_certs: description: - Whether to allow insecure connections to Tower or AWX. diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 8689f1625d..58990b783d 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -51,12 +51,6 @@ options: choices: ["present", "absent"] default: "present" type: str - tower_oauthtoken: - description: - - The Tower OAuth token to use. - required: False - type: str - version_added: "3.7" extends_documentation_fragment: awx.awx.auth ''' From 1a0407ba50c834800870eec1699566316e9f767a Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 1 Jun 2020 13:32:00 -0400 Subject: [PATCH 10/44] Fixing ny -> my typos --- awx_collection/plugins/modules/tower_token.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 58990b783d..68d5be87ac 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -61,7 +61,7 @@ EXAMPLES = ''' description: '{{ token_description }}' scope: "write" state: present - tower_oauthtoken: "{{ ny_existing_token }}" + tower_oauthtoken: "{{ my_existing_token }}" - name: Delete this token tower_token: @@ -73,8 +73,8 @@ EXAMPLES = ''' description: '{{ token_description }}' scope: "write" state: present - tower_username: "{{ ny_username }}" - tower_password: "{{ ny_password }}" + tower_username: "{{ my_username }}" + tower_password: "{{ my_password }}" - name: Use our new token to make another call tower_job_list: From 7c88a51992af7fdcc96a2370906c23ac755d6008 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 1 Jun 2020 13:32:56 -0400 Subject: [PATCH 11/44] Adding more details about tower_oauthtoken parameter --- awx_collection/plugins/doc_fragments/auth.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx_collection/plugins/doc_fragments/auth.py b/awx_collection/plugins/doc_fragments/auth.py index c4cc6fcc3f..1e77a63b4b 100644 --- a/awx_collection/plugins/doc_fragments/auth.py +++ b/awx_collection/plugins/doc_fragments/auth.py @@ -31,6 +31,9 @@ options: tower_oauthtoken: description: - The Tower OAuth token to use. + - This value can be in one of two formats. + - A string which is the token itself. (i.e. bqV5txm97wqJqtkxlMkhQz0pKhRMMX) + - A dictionary structure as returned by the tower_token module. - If value not set, will try environment variable C(TOWER_OAUTH_TOKEN) and then config files type: raw version_added: "3.7" From 30346618f161ed9eefc1563d25b6924017f26c29 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 1 Jun 2020 13:35:28 -0400 Subject: [PATCH 12/44] Adding more through testing of tower_oauthtoken including failure messages --- .../plugins/module_utils/tower_api.py | 16 ++++++++---- .../targets/tower_token/tasks/main.yml | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index f950829328..7c2f52d1ca 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -3,7 +3,7 @@ __metaclass__ = type from ansible.module_utils.basic import AnsibleModule, env_fallback from ansible.module_utils.urls import Request, SSLValidationError, ConnectionError -from ansible.module_utils.six import PY2 +from ansible.module_utils.six import PY2, string_types from ansible.module_utils.six.moves import StringIO from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode from ansible.module_utils.six.moves.urllib.error import HTTPError @@ -98,10 +98,16 @@ class TowerModule(AnsibleModule): # Perform magic depending on whether tower_oauthtoken is a string or a dict if self.params.get('tower_oauthtoken'): - if type(self.params.get('tower_oauthtoken')) is dict: - setattr(self, 'oauth_token', self.params.get('tower_oauthtoken')['token']) - elif 'token' in self.params.get('tower_oauthtoken'): - setattr(self, 'oauth_token', self.params.get('tower_oauthtoken')) + token_param = self.params.get('tower_oauthtoken') + if type(token_param) is dict: + if 'token' in token_param: + self.oauth_token = self.params.get('tower_oauthtoken')['token'] + else: + self.fail_json(msg="The provided dict in tower_oauthtoken did not properly contain the token entry") + elif isinstance(token_param, string_types): + self.oauth_token = self.params.get('tower_oauthtoken') + else: + self.fail_json(msg="The provided tower_oauthtoken type was not valid ({0}), please refer to ansible-doc for valid options".format(type(token_param).__name__)) # Perform some basic validation if not re.match('^https{0,1}://', self.host): diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 86edd620d7..52e5bb41d8 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -3,6 +3,30 @@ set_fact: token_description: "AWX-Collection-tests-tower_token-description-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +- name: Try to use a token as a dict which is missing the token parameter + tower_job_list: + tower_oauthtoken: + not_token: "This has no token entry" + register: results + ignore_errors: True + +- assert: + that: + - results is failed + - '"The provided dict in tower_oauthtoken did not properly contain the token entry" == results.msg' + +- name: Try to use a token as a list + tower_job_list: + tower_oauthtoken: + - dummy_token + register: results + ignore_errors: True + +- assert: + that: + - results is failed + - '"The provided tower_oauthtoken type was not valid (list), please refer to ansible-doc for valid options" == results.msg' + - block: - name: Create a Token tower_token: @@ -14,10 +38,12 @@ - name: Validate our token works by token tower_job_list: tower_oauthtoken: "{{ tower_token.token }}" + register: job_list - name: Validate out token works by object tower_job_list: tower_oauthtoken: "{{ tower_token }}" + register: job_list always: - name: Delete our Token with our own token From 11da8e254d45ae65b6a71fb30192969ad121ff94 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 2 Jun 2020 10:18:20 -0400 Subject: [PATCH 13/44] Changing documention and fixing sanity tests --- awx_collection/plugins/module_utils/tower_api.py | 3 ++- awx_collection/plugins/modules/tower_token.py | 14 ++++++++++---- .../integration/targets/tower_token/tasks/main.yml | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 7c2f52d1ca..ca5a365e8e 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -107,7 +107,8 @@ class TowerModule(AnsibleModule): elif isinstance(token_param, string_types): self.oauth_token = self.params.get('tower_oauthtoken') else: - self.fail_json(msg="The provided tower_oauthtoken type was not valid ({0}), please refer to ansible-doc for valid options".format(type(token_param).__name__)) + error_msg = "The provided tower_oauthtoken type was not valid ({0}). Valid options are str or dict.".format(type(token_param).__name__) + self.fail_json(msg=error_msg) # Perform some basic validation if not re.match('^https{0,1}://', self.host): diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 68d5be87ac..d620e7a18a 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -22,7 +22,10 @@ short_description: create, update, or destroy Ansible Tower tokens. description: - Create or destroy Ansible Tower tokens. See U(https://www.ansible.com/tower) for an overview. - - If you create a token it is your responsibility to delete the token. + - In addition, the module sets an Ansible fact which can be passed into other + tower_* modules as the parameter tower_oauthtoken. See examples for usage. + - Because of the sensitive nature of tokens, the created token value is only available once + through the Ansible fact. (See RETURN for details) options: description: description: @@ -88,10 +91,14 @@ EXAMPLES = ''' when: tower_token is defined ''' -RETURNS = ''' +RETURN = ''' tower_token: type: dict - description: A Tower token object which can be used for auth or token deletion + description: An Ansible Fact variable representing a Tower token object which can be used for auth in subsequent modules. See examples for usage. + contains: + token: + description: The token that was generated. This token can never be accessed again, make sure this value is noted before it is lost. + type: str returned: on successful create ''' @@ -103,7 +110,6 @@ def return_token(module, last_response): # So the default module return would give you an ID but then the token would forever be masked on you. # This method will return the entire token object we got back so that a user has access to the token - module.json_output['token'] = last_response['token'] module.json_output['ansible_facts'] = { 'tower_token': last_response, } diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 52e5bb41d8..8874e21e22 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -25,7 +25,7 @@ - assert: that: - results is failed - - '"The provided tower_oauthtoken type was not valid (list), please refer to ansible-doc for valid options" == results.msg' + - '"The provided tower_oauthtoken type was not valid (list). Valid options are str or dict." == results.msg' - block: - name: Create a Token From 41d0548af67fce58c67037f32c084914e7c15aa7 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 2 Jun 2020 13:07:11 -0400 Subject: [PATCH 14/44] Fixing truthy linting issue --- .../tests/integration/targets/tower_token/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 8874e21e22..0ba32c19b6 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -8,7 +8,7 @@ tower_oauthtoken: not_token: "This has no token entry" register: results - ignore_errors: True + ignore_errors: true - assert: that: @@ -20,7 +20,7 @@ tower_oauthtoken: - dummy_token register: results - ignore_errors: True + ignore_errors: true - assert: that: From 0e54f76f801a43e5a4e8615b315e876cb7ac94fc Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Fri, 5 Jun 2020 16:10:26 -0400 Subject: [PATCH 15/44] Fixing missing raw from merge type --- awx_collection/plugins/module_utils/tower_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index ca5a365e8e..d836c457c0 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -47,7 +47,7 @@ class TowerModule(AnsibleModule): tower_username=dict(required=False, fallback=(env_fallback, ['TOWER_USERNAME'])), tower_password=dict(no_log=True, required=False, fallback=(env_fallback, ['TOWER_PASSWORD'])), validate_certs=dict(type='bool', aliases=['tower_verify_ssl'], required=False, fallback=(env_fallback, ['TOWER_VERIFY_SSL'])), - tower_oauthtoken=dict(type='str', no_log=True, required=False, fallback=(env_fallback, ['TOWER_OAUTH_TOKEN'])), + tower_oauthtoken=dict(type='raw', no_log=True, required=False, fallback=(env_fallback, ['TOWER_OAUTH_TOKEN'])), tower_config_file=dict(type='path', required=False, default=None), ) short_params = { From 1f17e02fe9988fe3139d1f6dd4d876c61ceebe76 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Fri, 5 Jun 2020 16:11:26 -0400 Subject: [PATCH 16/44] Adding more documenation and added existing_token_id param --- awx_collection/plugins/modules/tower_token.py | 28 ++++++++++++- .../targets/tower_token/tasks/main.yml | 41 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index d620e7a18a..3ee2e2b504 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -26,6 +26,10 @@ description: tower_* modules as the parameter tower_oauthtoken. See examples for usage. - Because of the sensitive nature of tokens, the created token value is only available once through the Ansible fact. (See RETURN for details) + - Due to the nature of tokens in Tower this module is not idempotent. A second will + with the same parameters will create a new token. + - If you are creating a temporary token for use with modules you should delete the token + when you are done with it. See the example for how to do it. options: description: description: @@ -46,8 +50,11 @@ options: default: 'write' choices: ["read", "write"] existing_token: - description: An existing token (for use with state absent) + description: The data structure produced from tower_token in create mode to be used with state absent. type: dict + existing_token_id: + description: A token ID (number) which can be used to delete an arbitrary token with state absent. + type: str state: description: - Desired state of the resource. @@ -89,6 +96,11 @@ EXAMPLES = ''' existing_token: "{{ tower_token }}" state: absent when: tower_token is defined + +- name: Delete a token by its id + tower_toekn: + existing_token_id: 4 + state: absent ''' RETURN = ''' @@ -99,6 +111,9 @@ tower_token: token: description: The token that was generated. This token can never be accessed again, make sure this value is noted before it is lost. type: str + id: + description: The numeric ID of the token created + type: str returned: on successful create ''' @@ -123,6 +138,7 @@ def main(): application=dict(), scope=dict(choices=['read', 'write'], default='write'), existing_token=dict(type='dict'), + existing_token_id=dict(), state=dict(choices=['present', 'absent'], default='present'), ) @@ -134,9 +150,19 @@ def main(): application = module.params.get('application') scope = module.params.get('scope') existing_token = module.params.get('existing_token') + existing_token_id = module.params.get('existing_token_id') state = module.params.get('state') if state == 'absent': + if not existing_token: + if not existing_token_id: + module.fail_json(msg='When deleting a token you specify either the parameter existing_token or existing_token_id') + existing_token = module.get_one('tokens', **{ + 'data': { + 'id': existing_token_id, + } + }) + # If the state was absent we can let the module delete it if needed, the module will handle exiting from this module.delete_if_needed(existing_token) diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 0ba32c19b6..0c4d1031c1 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -27,6 +27,17 @@ - results is failed - '"The provided tower_oauthtoken type was not valid (list). Valid options are str or dict." == results.msg' +- name: Try to delete a token with no existing_token or existing_token_id + tower_token: + state: absent + register: results + ignore_errors: True + +- assert: + that: + - results is failed + - '"When deleting a token you specify either the parameter existing_token or existing_token_id" == results.msg' + - block: - name: Create a Token tower_token: @@ -52,3 +63,33 @@ tower_oauthtoken: "{{ tower_token }}" state: absent when: tower_token is defined + register: results + + - assert: + that: + - results is changed or results is skipped + +- block: + - name: Create a second token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + register: results + + - assert: + that: + - results is changed + + always: + - name: Delete the second Token with our own token + tower_token: + existing_token_id: "{{ tower_token['id'] }}" + tower_oauthtoken: "{{ tower_token }}" + state: absent + when: tower_token is defined + register: results + + - assert: + that: + - results is changed or resuslts is skipped From 91dbc2de30b56a836b1caa37d476267e5fa697cc Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 4 Jun 2020 18:32:14 -0400 Subject: [PATCH 17/44] Add queue / instance group registration to heartbeat for k8s installs There is some history here. https://github.com/ansible/awx/pull/7190 <- This PR was an attempt at fixing a bug notting ran into where some jobs on k8s installs would get stuck in Waiting forever. The PR mentioned above introduced a bug where there are no instance groups on a fresh k8s-based install. This is because this process currently happens in the launch scripts, before the database is up. With this patch, queue / instance group registration happens in the heartbeat, right after auto-registering the instance. --- .../management/commands/register_queue.py | 103 ++++++++++-------- awx/main/managers.py | 5 +- .../templates/launch_awx_task.sh.j2 | 4 +- 3 files changed, 63 insertions(+), 49 deletions(-) diff --git a/awx/main/management/commands/register_queue.py b/awx/main/management/commands/register_queue.py index 61761ec2aa..edd8068b89 100644 --- a/awx/main/management/commands/register_queue.py +++ b/awx/main/management/commands/register_queue.py @@ -16,31 +16,24 @@ class InstanceNotFound(Exception): super(InstanceNotFound, self).__init__(*args, **kwargs) -class Command(BaseCommand): +class RegisterQueue: + def __init__(self, queuename, controller, instance_percent, inst_min, hostname_list): + self.instance_not_found_err = None + self.queuename = queuename + self.controller = controller + self.instance_percent = instance_percent + self.instance_min = inst_min + self.hostname_list = hostname_list - def add_arguments(self, parser): - parser.add_argument('--queuename', dest='queuename', type=str, - help='Queue to create/update') - parser.add_argument('--hostnames', dest='hostnames', type=str, - help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)') - parser.add_argument('--controller', dest='controller', type=str, - default='', help='The controlling group (makes this an isolated group)') - parser.add_argument('--instance_percent', dest='instance_percent', type=int, default=0, - help='The percentage of active instances that will be assigned to this group'), - parser.add_argument('--instance_minimum', dest='instance_minimum', type=int, default=0, - help='The minimum number of instance that will be retained for this group from available instances') - - - def get_create_update_instance_group(self, queuename, instance_percent, instance_min): + def get_create_update_instance_group(self): created = False changed = False - - (ig, created) = InstanceGroup.objects.get_or_create(name=queuename) - if ig.policy_instance_percentage != instance_percent: - ig.policy_instance_percentage = instance_percent + (ig, created) = InstanceGroup.objects.get_or_create(name=self.queuename) + if ig.policy_instance_percentage != self.instance_percent: + ig.policy_instance_percentage = self.instance_percent changed = True - if ig.policy_instance_minimum != instance_min: - ig.policy_instance_minimum = instance_min + if ig.policy_instance_minimum != self.instance_min: + ig.policy_instance_minimum = self.instance_min changed = True if changed: @@ -48,12 +41,12 @@ class Command(BaseCommand): return (ig, created, changed) - def update_instance_group_controller(self, ig, controller): + def update_instance_group_controller(self, ig): changed = False control_ig = None - if controller: - control_ig = InstanceGroup.objects.filter(name=controller).first() + if self.controller: + control_ig = InstanceGroup.objects.filter(name=self.controller).first() if control_ig and ig.controller_id != control_ig.pk: ig.controller = control_ig @@ -62,10 +55,10 @@ class Command(BaseCommand): return (control_ig, changed) - def add_instances_to_group(self, ig, hostname_list): + def add_instances_to_group(self, ig): changed = False - instance_list_unique = set([x.strip() for x in hostname_list if x]) + instance_list_unique = set([x.strip() for x in self.hostname_list if x]) instances = [] for inst_name in instance_list_unique: instance = Instance.objects.filter(hostname=inst_name) @@ -86,43 +79,61 @@ class Command(BaseCommand): return (instances, changed) - def handle(self, **options): - instance_not_found_err = None - queuename = options.get('queuename') - if not queuename: - raise CommandError("Specify `--queuename` to use this command.") - ctrl = options.get('controller') - inst_per = options.get('instance_percent') - inst_min = options.get('instance_minimum') - hostname_list = [] - if options.get('hostnames'): - hostname_list = options.get('hostnames').split(",") - + def register(self): with advisory_lock('cluster_policy_lock'): with transaction.atomic(): changed2 = False changed3 = False - (ig, created, changed1) = self.get_create_update_instance_group(queuename, inst_per, inst_min) + (ig, created, changed1) = self.get_create_update_instance_group() if created: print("Creating instance group {}".format(ig.name)) elif not created: print("Instance Group already registered {}".format(ig.name)) - if ctrl: - (ig_ctrl, changed2) = self.update_instance_group_controller(ig, ctrl) + if self.controller: + (ig_ctrl, changed2) = self.update_instance_group_controller(ig) if changed2: - print("Set controller group {} on {}.".format(ctrl, queuename)) + print("Set controller group {} on {}.".format(self.controller, self.queuename)) try: - (instances, changed3) = self.add_instances_to_group(ig, hostname_list) + (instances, changed3) = self.add_instances_to_group(ig) for i in instances: print("Added instance {} to {}".format(i.hostname, ig.name)) except InstanceNotFound as e: - instance_not_found_err = e + self.instance_not_found_err = e if any([changed1, changed2, changed3]): print('(changed: True)') - if instance_not_found_err: - print(instance_not_found_err.message) + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('--queuename', dest='queuename', type=str, + help='Queue to create/update') + parser.add_argument('--hostnames', dest='hostnames', type=str, + help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)') + parser.add_argument('--controller', dest='controller', type=str, + default='', help='The controlling group (makes this an isolated group)') + parser.add_argument('--instance_percent', dest='instance_percent', type=int, default=0, + help='The percentage of active instances that will be assigned to this group'), + parser.add_argument('--instance_minimum', dest='instance_minimum', type=int, default=0, + help='The minimum number of instance that will be retained for this group from available instances') + + + def handle(self, **options): + queuename = options.get('queuename') + if not queuename: + raise CommandError("Specify `--queuename` to use this command.") + ctrl = options.get('controller') + inst_per = options.get('instance_percent') + instance_min = options.get('instance_minimum') + hostname_list = [] + if options.get('hostnames'): + hostname_list = options.get('hostnames').split(",") + + rq = RegisterQueue(queuename, ctrl, inst_per, instance_min, hostname_list) + rq.register() + if rq.instance_not_found_err: + print(rq.instance_not_found_err.message) sys.exit(1) diff --git a/awx/main/managers.py b/awx/main/managers.py index 2076e7f0b0..f4b437d027 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -149,8 +149,11 @@ class InstanceManager(models.Manager): def get_or_register(self): if settings.AWX_AUTO_DEPROVISION_INSTANCES: + from awx.main.management.commands.register_queue import RegisterQueue pod_ip = os.environ.get('MY_POD_IP') - return self.register(ip_address=pod_ip) + registered = self.register(ip_address=pod_ip) + RegisterQueue('tower', None, 100, 0, []).register() + return registered else: return (False, self.me()) diff --git a/installer/roles/image_build/templates/launch_awx_task.sh.j2 b/installer/roles/image_build/templates/launch_awx_task.sh.j2 index edaf3bf362..dd54af5b3e 100755 --- a/installer/roles/image_build/templates/launch_awx_task.sh.j2 +++ b/installer/roles/image_build/templates/launch_awx_task.sh.j2 @@ -12,6 +12,8 @@ ANSIBLE_REMOTE_TEMP=/tmp ANSIBLE_LOCAL_TEMP=/tmp ansible -i "127.0.0.1," -c loca if [ -z "$AWX_SKIP_MIGRATIONS" ]; then awx-manage migrate --noinput + awx-manage provision_instance --hostname=$(hostname) + awx-manage register_queue --queuename=tower --instance_percent=100 fi if [ ! -z "$AWX_ADMIN_USER" ]&&[ ! -z "$AWX_ADMIN_PASSWORD" ]; then @@ -21,8 +23,6 @@ if [ ! -z "$AWX_ADMIN_USER" ]&&[ ! -z "$AWX_ADMIN_PASSWORD" ]; then {% endif %} fi echo 'from django.conf import settings; x = settings.AWX_TASK_ENV; x["HOME"] = "/var/lib/awx"; settings.AWX_TASK_ENV = x' | awx-manage shell -awx-manage provision_instance --hostname=$(hostname) -awx-manage register_queue --queuename=tower --instance_percent=100 unset $(cut -d = -f -1 /etc/tower/conf.d/environment.sh) From 460f31a05d55b7c957f89c7cde239ce419e94c13 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 4 Jun 2020 19:33:32 -0400 Subject: [PATCH 18/44] Fix up test that was only failing in dev env I'm not sure how this one slipped by. The default config is built into the image, but we were bind-mounting an empty directory on top of it. --- tools/docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index d4dee7f101..b1e8fb93c7 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -34,7 +34,6 @@ services: - "../awx/projects/:/var/lib/awx/projects/" - "./redis/redis_socket_standalone:/var/run/redis/" - "./memcached/:/var/run/memcached" - - "./rsyslog/:/var/lib/awx/rsyslog" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" privileged: true tty: true From 21abf5a788169df258566574a7e365f6e72dc94f Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Sat, 6 Jun 2020 20:11:46 -0500 Subject: [PATCH 19/44] updated to remove version and add docs --- awx_collection/plugins/modules/tower_job_template.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 37011a4502..75d16049a7 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -51,6 +51,7 @@ options: - Used to help lookup the object, cannot be modified using this module. - The Organization is inferred from the associated project - If not provided, will lookup by name only, which does not work with duplicates. + - Requires Tower Version 3.7.0 or AWX 10.0.0 IS NOT backwards compatible with earlier versions. type: str version_added: 2.10 project: @@ -338,10 +339,6 @@ from ..module_utils.tower_api import TowerModule import json -def versiontuple(v): - return tuple(map(int, (v.split(".")))) - - def update_survey(module, last_request): spec_endpoint = last_request.get('related', {}).get('survey_spec') if module.params.get('survey_spec') == {}: @@ -437,9 +434,7 @@ def main(): organization = module.params.get('organization') if organization: organization_id = module.resolve_name_to_id('organizations', organization) - tower_version = module.get_endpoint('ping')['json']['version'] - if versiontuple(tower_version) >= versiontuple("3.7.0"): - search_fields['organization'] = new_fields['organization'] = organization_id + search_fields['organization'] = new_fields['organization'] = organization_id # Attempt to look up an existing item based on the provided data existing_item = module.get_one('job_templates', **{'data': search_fields}) From 360352b78ea70cdec2f2d9bcde441e26eb74939e Mon Sep 17 00:00:00 2001 From: sean-m-sullivan Date: Sat, 6 Jun 2020 20:12:47 -0500 Subject: [PATCH 20/44] updated version --- awx_collection/plugins/modules/tower_job_template.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 75d16049a7..610a6fb8ab 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -53,7 +53,6 @@ options: - If not provided, will lookup by name only, which does not work with duplicates. - Requires Tower Version 3.7.0 or AWX 10.0.0 IS NOT backwards compatible with earlier versions. type: str - version_added: 2.10 project: description: - Name of the project to use for the job template. From 3b60529488be0725407197af2a3b364d182ae0c0 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 8 Jun 2020 06:42:33 -0400 Subject: [PATCH 21/44] Fixes in testing and parameter processing --- awx_collection/plugins/modules/tower_token.py | 13 ++++++++++--- awx_collection/test/awx/test_token.py | 5 +++-- .../targets/tower_token/tasks/main.yml | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 3ee2e2b504..3be5cebde5 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -143,7 +143,16 @@ def main(): ) # Create a module for ourselves - module = TowerModule(argument_spec=argument_spec) + module = TowerModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ('existing_token', 'existing_token_id'), + ], + # If we are state absent make sure one of existing_token or existing_token_id are present + required_if=[ + [ 'state', 'absent', ('existing_token', 'existing_token_id'), True,], + ], + ) # Extract our parameters description = module.params.get('description') @@ -155,8 +164,6 @@ def main(): if state == 'absent': if not existing_token: - if not existing_token_id: - module.fail_json(msg='When deleting a token you specify either the parameter existing_token or existing_token_id') existing_token = module.get_one('tokens', **{ 'data': { 'id': existing_token_id, diff --git a/awx_collection/test/awx/test_token.py b/awx_collection/test/awx/test_token.py index 107a833af6..e3f1d2a8c1 100644 --- a/awx_collection/test/awx/test_token.py +++ b/awx_collection/test/awx/test_token.py @@ -22,7 +22,8 @@ def test_create_token(run_module, admin_user): } result = run_module('tower_token', module_args, admin_user) - assert result, result.get('changed') + assert result.get('changed'), result tokens = OAuth2AccessToken.objects.filter(description='barfoo') - assert len(tokens) == 1, tokens[0].description == 'barfoo' + assert len(tokens) == 1, 'Rokens with description of barfoo != 0: {0}'.format(len(tokens)) + assert tokens[0].scope == 'read', 'Token was not given read access' diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 0c4d1031c1..2854d4e6ca 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -36,7 +36,22 @@ - assert: that: - results is failed - - '"When deleting a token you specify either the parameter existing_token or existing_token_id" == results.msg' + # We don't assert a message here because it handled by ansible + +- name: Try to delete a token with both existing_token or existing_token_id + tower_token: + existing_token: + id: 1234 + existing_token_id: 1234 + state: absent + register: results + ignore_errors: True + +- assert: + that: + - results is failed + # We don't assert a message here because it handled by ansible + - block: - name: Create a Token From 95b8bd63ea005e45e15c6cb5fc8600d0ff9ec0c0 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 8 Jun 2020 07:06:45 -0400 Subject: [PATCH 22/44] Fixing linting issues --- awx_collection/plugins/modules/tower_settings.py | 2 +- .../tests/integration/targets/tower_settings/tasks/main.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/tower_settings.py b/awx_collection/plugins/modules/tower_settings.py index a66acadf44..ef4dcd9b1b 100644 --- a/awx_collection/plugins/modules/tower_settings.py +++ b/awx_collection/plugins/modules/tower_settings.py @@ -83,7 +83,7 @@ except ImportError: def coerce_type(module, value): # If our value is already None we can just return directly - if value == None: + if value is None: return value yaml_ish = bool(( diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml index 6cebe979e2..16ee22e9e2 100644 --- a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -79,4 +79,3 @@ tower_settings: name: AWX_PROOT_BASE_PATH value: '{{ junk_var | default(omit) }}' - From 64846c3347fb3a6a7ef76ac15c1030b53fad3d2e Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 8 Jun 2020 07:09:45 -0400 Subject: [PATCH 23/44] Fixing truthy issues --- .../tests/integration/targets/tower_token/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml index 2854d4e6ca..355d5dd02f 100644 --- a/awx_collection/tests/integration/targets/tower_token/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -31,7 +31,7 @@ tower_token: state: absent register: results - ignore_errors: True + ignore_errors: true - assert: that: @@ -45,7 +45,7 @@ existing_token_id: 1234 state: absent register: results - ignore_errors: True + ignore_errors: true - assert: that: From 76dcd6d72a351468e55896604be60611948c7967 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 8 Jun 2020 07:49:58 -0400 Subject: [PATCH 24/44] Fixing scooby doo fingers --- awx_collection/plugins/modules/tower_token.py | 2 +- awx_collection/test/awx/test_token.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index 3be5cebde5..d8300a048e 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -98,7 +98,7 @@ EXAMPLES = ''' when: tower_token is defined - name: Delete a token by its id - tower_toekn: + tower_token: existing_token_id: 4 state: absent ''' diff --git a/awx_collection/test/awx/test_token.py b/awx_collection/test/awx/test_token.py index e3f1d2a8c1..442fa2e9fb 100644 --- a/awx_collection/test/awx/test_token.py +++ b/awx_collection/test/awx/test_token.py @@ -25,5 +25,5 @@ def test_create_token(run_module, admin_user): assert result.get('changed'), result tokens = OAuth2AccessToken.objects.filter(description='barfoo') - assert len(tokens) == 1, 'Rokens with description of barfoo != 0: {0}'.format(len(tokens)) + assert len(tokens) == 1, 'Tokens with description of barfoo != 0: {0}'.format(len(tokens)) assert tokens[0].scope == 'read', 'Token was not given read access' From b457c8f1336c36cefa0c72fd1e413c2feec0be26 Mon Sep 17 00:00:00 2001 From: beeankha Date: Mon, 8 Jun 2020 09:57:16 -0400 Subject: [PATCH 25/44] Update a task in tower_settings integration test playbook --- .../tests/integration/targets/tower_settings/tasks/main.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml index 16ee22e9e2..8a42f5768e 100644 --- a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -79,3 +79,9 @@ tower_settings: name: AWX_PROOT_BASE_PATH value: '{{ junk_var | default(omit) }}' + register: result + ignore_errors: true + +- assert: + that: + - "'Unable to update settings' in result.msg" From cf6235f6eab293074b74400a7f42a4c0da2560ad Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 8 Jun 2020 16:30:52 +0200 Subject: [PATCH 26/44] Send content-type with mattermost notifications, fixes #7264 --- awx/main/notifications/mattermost_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/main/notifications/mattermost_backend.py b/awx/main/notifications/mattermost_backend.py index 7a759d41a3..78a23c72d1 100644 --- a/awx/main/notifications/mattermost_backend.py +++ b/awx/main/notifications/mattermost_backend.py @@ -3,7 +3,6 @@ import logging import requests -import json from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -45,7 +44,7 @@ class MattermostBackend(AWXBaseEmailBackend, CustomNotificationBase): payload['text'] = m.subject r = requests.post("{}".format(m.recipients()[0]), - data=json.dumps(payload), verify=(not self.mattermost_no_verify_ssl)) + json=payload, verify=(not self.mattermost_no_verify_ssl)) if r.status_code >= 400: logger.error(smart_text(_("Error sending notification mattermost: {}").format(r.text))) if not self.fail_silently: From 0df4047d3dd6e1f94d3e40c3943b57f1b4ae3896 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 8 Jun 2020 11:55:21 -0400 Subject: [PATCH 27/44] Adds routing stubs for Applications --- awx/ui_next/src/routeConfig.js | 4 +- .../src/screens/Application/Applications.jsx | 26 ---------- .../Applications/Application/Application.jsx | 26 ++++++++++ .../screens/Applications/Application/index.js | 1 + .../ApplicationAdd/ApplicationAdd.jsx | 15 ++++++ .../Applications/ApplicationAdd/index.js | 1 + .../ApplicationDetails/ApplicationDetails.jsx | 11 +++++ .../Applications/ApplicationDetails/index.js | 1 + .../ApplicationEdit/ApplicationEdit.jsx | 11 +++++ .../Applications/ApplicationEdit/index.js | 1 + .../src/screens/Applications/Applications.jsx | 49 +++++++++++++++++++ .../Applications.test.jsx | 2 +- .../ApplicationsList/ApplicationsList.jsx | 15 ++++++ .../Applications/ApplicationsList/index.js | 1 + .../{Application => Applications}/index.js | 0 15 files changed, 135 insertions(+), 29 deletions(-) delete mode 100644 awx/ui_next/src/screens/Application/Applications.jsx create mode 100644 awx/ui_next/src/screens/Applications/Application/Application.jsx create mode 100644 awx/ui_next/src/screens/Applications/Application/index.js create mode 100644 awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx create mode 100644 awx/ui_next/src/screens/Applications/ApplicationAdd/index.js create mode 100644 awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx create mode 100644 awx/ui_next/src/screens/Applications/ApplicationDetails/index.js create mode 100644 awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx create mode 100644 awx/ui_next/src/screens/Applications/ApplicationEdit/index.js create mode 100644 awx/ui_next/src/screens/Applications/Applications.jsx rename awx/ui_next/src/screens/{Application => Applications}/Applications.test.jsx (94%) create mode 100644 awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx create mode 100644 awx/ui_next/src/screens/Applications/ApplicationsList/index.js rename awx/ui_next/src/screens/{Application => Applications}/index.js (100%) diff --git a/awx/ui_next/src/routeConfig.js b/awx/ui_next/src/routeConfig.js index 37f1732cb0..9ef1167ffa 100644 --- a/awx/ui_next/src/routeConfig.js +++ b/awx/ui_next/src/routeConfig.js @@ -1,6 +1,6 @@ import { t } from '@lingui/macro'; -import Applications from './screens/Application'; +import Applications from './screens/Applications'; import Credentials from './screens/Credential'; import CredentialTypes from './screens/CredentialType'; import Dashboard from './screens/Dashboard'; @@ -138,7 +138,7 @@ function getRouteConfig(i18n) { screen: InstanceGroups, }, { - title: i18n._(t`Integrations`), + title: i18n._(t`Applications`), path: '/applications', screen: Applications, }, diff --git a/awx/ui_next/src/screens/Application/Applications.jsx b/awx/ui_next/src/screens/Application/Applications.jsx deleted file mode 100644 index 47a5e3250a..0000000000 --- a/awx/ui_next/src/screens/Application/Applications.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { - PageSection, - PageSectionVariants, - Title, -} from '@patternfly/react-core'; - -class Applications extends Component { - render() { - const { i18n } = this.props; - const { light } = PageSectionVariants; - - return ( - - - {i18n._(t`Applications`)} - - - - ); - } -} - -export default withI18n()(Applications); diff --git a/awx/ui_next/src/screens/Applications/Application/Application.jsx b/awx/ui_next/src/screens/Applications/Application/Application.jsx new file mode 100644 index 0000000000..5a002f2990 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/Application/Application.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Route, Switch, Redirect } from 'react-router-dom'; +import ApplicationEdit from '../ApplicationEdit'; +import ApplicationDetails from '../ApplicationDetails'; + +function Application() { + return ( + <> + + + + + + + + + + + ); +} + +export default Application; diff --git a/awx/ui_next/src/screens/Applications/Application/index.js b/awx/ui_next/src/screens/Applications/Application/index.js new file mode 100644 index 0000000000..f76f133dd5 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/Application/index.js @@ -0,0 +1 @@ +export { default } from './Application'; diff --git a/awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx b/awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx new file mode 100644 index 0000000000..a25c690a7b --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Card, PageSection } from '@patternfly/react-core'; + +function ApplicatonAdd() { + return ( + <> + + +
Applications Add
+
+
+ + ); +} +export default ApplicatonAdd; diff --git a/awx/ui_next/src/screens/Applications/ApplicationAdd/index.js b/awx/ui_next/src/screens/Applications/ApplicationAdd/index.js new file mode 100644 index 0000000000..54101fc16b --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationAdd/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationAdd'; diff --git a/awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx b/awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx new file mode 100644 index 0000000000..c8051841bb --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Card, PageSection } from '@patternfly/react-core'; + +function ApplicationDetails() { + return ( + + Application Details + + ); +} +export default ApplicationDetails; diff --git a/awx/ui_next/src/screens/Applications/ApplicationDetails/index.js b/awx/ui_next/src/screens/Applications/ApplicationDetails/index.js new file mode 100644 index 0000000000..fc3261983b --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationDetails/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationDetails'; diff --git a/awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx b/awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx new file mode 100644 index 0000000000..e72f93b681 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Card, PageSection } from '@patternfly/react-core'; + +function ApplicationEdit() { + return ( + + Application Edit + + ); +} +export default ApplicationEdit; diff --git a/awx/ui_next/src/screens/Applications/ApplicationEdit/index.js b/awx/ui_next/src/screens/Applications/ApplicationEdit/index.js new file mode 100644 index 0000000000..2ab4beb8d4 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationEdit/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationEdit'; diff --git a/awx/ui_next/src/screens/Applications/Applications.jsx b/awx/ui_next/src/screens/Applications/Applications.jsx new file mode 100644 index 0000000000..19a23be08e --- /dev/null +++ b/awx/ui_next/src/screens/Applications/Applications.jsx @@ -0,0 +1,49 @@ +import React, { useState, useCallback } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Route, Switch } from 'react-router-dom'; + +import ApplicationsList from './ApplicationsList'; +import ApplicationAdd from './ApplicationAdd'; +import Application from './Application'; +import Breadcrumbs from '../../components/Breadcrumbs'; + +function Applications({ i18n }) { + const [breadcrumbConfig, setBreadcrumbConfig] = useState({ + '/applications': i18n._(t`Applications`), + '/applications/add': i18n._(t`Create New Application`), + }); + + const buildBreadcrumbConfig = useCallback( + application => { + if (!application) { + return; + } + + setBreadcrumbConfig({ + '/applications': i18n._(t`Applications`), + '/applications/add': i18n._(t`Create New Application`), + [`/application/${application.id}`]: `${application.name}`, + }); + }, + [i18n] + ); + return ( + <> + + + + + + + + + + + + + + ); +} + +export default withI18n()(Applications); diff --git a/awx/ui_next/src/screens/Application/Applications.test.jsx b/awx/ui_next/src/screens/Applications/Applications.test.jsx similarity index 94% rename from awx/ui_next/src/screens/Application/Applications.test.jsx rename to awx/ui_next/src/screens/Applications/Applications.test.jsx index cb747a920b..7915523720 100644 --- a/awx/ui_next/src/screens/Application/Applications.test.jsx +++ b/awx/ui_next/src/screens/Applications/Applications.test.jsx @@ -21,7 +21,7 @@ describe('', () => { test('initially renders without crashing', () => { expect(pageWrapper.length).toBe(1); - expect(pageSections.length).toBe(2); + expect(pageSections.length).toBe(1); expect(title.length).toBe(1); expect(title.props().size).toBe('2xl'); expect(pageSections.first().props().variant).toBe('light'); diff --git a/awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx b/awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx new file mode 100644 index 0000000000..6fcf16bb73 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Card, PageSection } from '@patternfly/react-core'; + +function ApplicationsList() { + return ( + <> + + +
Applications List
+
+
+ + ); +} +export default ApplicationsList; diff --git a/awx/ui_next/src/screens/Applications/ApplicationsList/index.js b/awx/ui_next/src/screens/Applications/ApplicationsList/index.js new file mode 100644 index 0000000000..34f1107076 --- /dev/null +++ b/awx/ui_next/src/screens/Applications/ApplicationsList/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationsList'; diff --git a/awx/ui_next/src/screens/Application/index.js b/awx/ui_next/src/screens/Applications/index.js similarity index 100% rename from awx/ui_next/src/screens/Application/index.js rename to awx/ui_next/src/screens/Applications/index.js From 3f8bc0d7c8bec351e303106707564f9904e0188d Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Tue, 21 Apr 2020 12:09:17 -0400 Subject: [PATCH 28/44] Adds SAML Attribute Mapping to Teams and Orgs Signed-off-by: Marcelo Moreira de Mello --- awx/sso/fields.py | 2 ++ awx/sso/pipeline.py | 15 ++++++++++++--- awx/sso/tests/functional/test_pipeline.py | 19 +++++++++++++++++++ awx/sso/tests/unit/test_fields.py | 8 ++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/awx/sso/fields.py b/awx/sso/fields.py index dddd1ee6a1..78f750fbbd 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -740,7 +740,9 @@ class SAMLOrgAttrField(HybridDictField): class SAMLTeamAttrTeamOrgMapField(HybridDictField): team = fields.CharField(required=True, allow_null=False) + team_alias = fields.CharField(required=False, allow_null=True) organization = fields.CharField(required=True, allow_null=False) + organization_alias = fields.CharField(required=False, allow_null=True) child = _Forbidden() diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 212e3824ba..6d7e05da90 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -187,13 +187,22 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs) team_ids = [] for team_name_map in team_map.get('team_org_map', []): - team_name = team_name_map.get('team', '') + team_name = team_name_map.get('team', None) + team_alias = team_name_map.get('team_alias', None) + organization_name = team_name_map.get('organization', None) + organization_alias = team_name_map.get('organization_alias', None) if team_name in saml_team_names: - if not team_name_map.get('organization', ''): + if not organization_name: # Settings field validation should prevent this. logger.error("organization name invalid for team {}".format(team_name)) continue - org = Organization.objects.get_or_create(name=team_name_map['organization'])[0] + + if organization_alias: + organization_name = organization_alias + org = Organization.objects.get_or_create(name=organization_name)[0] + + if team_alias: + team_name = team_alias team = Team.objects.get_or_create(name=team_name, organization=org)[0] team_ids.append(team.id) diff --git a/awx/sso/tests/functional/test_pipeline.py b/awx/sso/tests/functional/test_pipeline.py index 78a04a0481..06d5503db8 100644 --- a/awx/sso/tests/functional/test_pipeline.py +++ b/awx/sso/tests/functional/test_pipeline.py @@ -193,6 +193,10 @@ class TestSAMLAttr(): {'team': 'Red', 'organization': 'Default1'}, {'team': 'Green', 'organization': 'Default1'}, {'team': 'Green', 'organization': 'Default3'}, + { + 'team': 'Yellow', 'team_alias': 'Yellow_Alias', + 'organization': 'Default4', 'organization_alias': 'Default4_Alias' + }, ] } return MockSettings() @@ -285,3 +289,18 @@ class TestSAMLAttr(): assert Team.objects.get(name='Green', organization__name='Default1').member_role.members.count() == 3 assert Team.objects.get(name='Green', organization__name='Default3').member_role.members.count() == 3 + def test_update_user_teams_alias_by_saml_attr(self, orgs, users, kwargs, mock_settings): + with mock.patch('django.conf.settings', mock_settings): + u1 = users[0] + + # Test getting teams from attribute with team->org mapping + kwargs['response']['attributes']['groups'] = ['Yellow'] + + # Ensure team and org will be created + update_user_teams_by_saml_attr(None, None, u1, **kwargs) + + assert Team.objects.filter(name='Yellow', organization__name='Default4').count() == 0 + assert Team.objects.filter(name='Yellow_Alias', organization__name='Default4_Alias').count() == 1 + assert Team.objects.get( + name='Yellow_Alias', organization__name='Default4_Alias').member_role.members.count() == 1 + diff --git a/awx/sso/tests/unit/test_fields.py b/awx/sso/tests/unit/test_fields.py index c63e137776..6d7505e022 100644 --- a/awx/sso/tests/unit/test_fields.py +++ b/awx/sso/tests/unit/test_fields.py @@ -71,6 +71,14 @@ class TestSAMLTeamAttrField(): {'team': 'Engineering', 'organization': 'Ansible2'}, {'team': 'Engineering2', 'organization': 'Ansible'}, ]}, + {'remove': True, 'saml_attr': 'foobar', 'team_org_map': [ + { + 'team': 'Engineering', 'team_alias': 'Engineering Team', + 'organization': 'Ansible', 'organization_alias': 'Awesome Org' + }, + {'team': 'Engineering', 'organization': 'Ansible2'}, + {'team': 'Engineering2', 'organization': 'Ansible'}, + ]}, ]) def test_internal_value_valid(self, data): field = SAMLTeamAttrField() From 142c0da9f0fcc9cba77de24e2509790d6adba0a3 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 8 Jun 2020 14:14:59 -0400 Subject: [PATCH 29/44] Manually bumps websocket-extensions to 0.1.14 --- awx/ui_next/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 57577d86d9..e9635701b6 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -16320,9 +16320,9 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, "whatwg-encoding": { "version": "1.0.5", From 57a6c8d6934099a8f885165d818fc383afbae61b Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 8 Jun 2020 14:29:52 -0400 Subject: [PATCH 30/44] Manually bumps websocket-extensions to 0.1.14 in awx/ui --- awx/ui/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/package-lock.json b/awx/ui/package-lock.json index 6719e512e4..3e072d5294 100644 --- a/awx/ui/package-lock.json +++ b/awx/ui/package-lock.json @@ -14435,9 +14435,9 @@ } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "whet.extend": { From 2784409c46c9e629cac2170262dd4ada07733978 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Mon, 8 Jun 2020 15:22:11 -0400 Subject: [PATCH 31/44] Fixes folder name --- awx/ui_next/src/routeConfig.js | 2 +- .../{Applications => Application}/Application/Application.jsx | 0 .../{Applications => Application}/Application/index.js | 0 .../ApplicationAdd/ApplicationAdd.jsx | 0 .../{Applications => Application}/ApplicationAdd/index.js | 0 .../ApplicationDetails/ApplicationDetails.jsx | 0 .../{Applications => Application}/ApplicationDetails/index.js | 0 .../ApplicationEdit/ApplicationEdit.jsx | 0 .../{Applications => Application}/ApplicationEdit/index.js | 0 .../screens/{Applications => Application}/Applications.jsx | 0 .../{Applications => Application}/Applications.test.jsx | 4 ---- .../ApplicationsList/ApplicationsList.jsx | 0 .../{Applications => Application}/ApplicationsList/index.js | 0 .../src/screens/{Applications => Application}/index.js | 0 14 files changed, 1 insertion(+), 5 deletions(-) rename awx/ui_next/src/screens/{Applications => Application}/Application/Application.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/Application/index.js (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationAdd/ApplicationAdd.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationAdd/index.js (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationDetails/ApplicationDetails.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationDetails/index.js (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationEdit/ApplicationEdit.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationEdit/index.js (100%) rename awx/ui_next/src/screens/{Applications => Application}/Applications.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/Applications.test.jsx (82%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationsList/ApplicationsList.jsx (100%) rename awx/ui_next/src/screens/{Applications => Application}/ApplicationsList/index.js (100%) rename awx/ui_next/src/screens/{Applications => Application}/index.js (100%) diff --git a/awx/ui_next/src/routeConfig.js b/awx/ui_next/src/routeConfig.js index 9ef1167ffa..4b43dc4993 100644 --- a/awx/ui_next/src/routeConfig.js +++ b/awx/ui_next/src/routeConfig.js @@ -1,6 +1,6 @@ import { t } from '@lingui/macro'; -import Applications from './screens/Applications'; +import Applications from './screens/Application'; import Credentials from './screens/Credential'; import CredentialTypes from './screens/CredentialType'; import Dashboard from './screens/Dashboard'; diff --git a/awx/ui_next/src/screens/Applications/Application/Application.jsx b/awx/ui_next/src/screens/Application/Application/Application.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/Application/Application.jsx rename to awx/ui_next/src/screens/Application/Application/Application.jsx diff --git a/awx/ui_next/src/screens/Applications/Application/index.js b/awx/ui_next/src/screens/Application/Application/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/Application/index.js rename to awx/ui_next/src/screens/Application/Application/index.js diff --git a/awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx b/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationAdd/ApplicationAdd.jsx rename to awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.jsx diff --git a/awx/ui_next/src/screens/Applications/ApplicationAdd/index.js b/awx/ui_next/src/screens/Application/ApplicationAdd/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationAdd/index.js rename to awx/ui_next/src/screens/Application/ApplicationAdd/index.js diff --git a/awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx b/awx/ui_next/src/screens/Application/ApplicationDetails/ApplicationDetails.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationDetails/ApplicationDetails.jsx rename to awx/ui_next/src/screens/Application/ApplicationDetails/ApplicationDetails.jsx diff --git a/awx/ui_next/src/screens/Applications/ApplicationDetails/index.js b/awx/ui_next/src/screens/Application/ApplicationDetails/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationDetails/index.js rename to awx/ui_next/src/screens/Application/ApplicationDetails/index.js diff --git a/awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationEdit/ApplicationEdit.jsx rename to awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx diff --git a/awx/ui_next/src/screens/Applications/ApplicationEdit/index.js b/awx/ui_next/src/screens/Application/ApplicationEdit/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationEdit/index.js rename to awx/ui_next/src/screens/Application/ApplicationEdit/index.js diff --git a/awx/ui_next/src/screens/Applications/Applications.jsx b/awx/ui_next/src/screens/Application/Applications.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/Applications.jsx rename to awx/ui_next/src/screens/Application/Applications.jsx diff --git a/awx/ui_next/src/screens/Applications/Applications.test.jsx b/awx/ui_next/src/screens/Application/Applications.test.jsx similarity index 82% rename from awx/ui_next/src/screens/Applications/Applications.test.jsx rename to awx/ui_next/src/screens/Application/Applications.test.jsx index 7915523720..f309a2b60a 100644 --- a/awx/ui_next/src/screens/Applications/Applications.test.jsx +++ b/awx/ui_next/src/screens/Application/Applications.test.jsx @@ -7,12 +7,10 @@ import Applications from './Applications'; describe('', () => { let pageWrapper; let pageSections; - let title; beforeEach(() => { pageWrapper = mountWithContexts(); pageSections = pageWrapper.find('PageSection'); - title = pageWrapper.find('Title'); }); afterEach(() => { @@ -22,8 +20,6 @@ describe('', () => { test('initially renders without crashing', () => { expect(pageWrapper.length).toBe(1); expect(pageSections.length).toBe(1); - expect(title.length).toBe(1); - expect(title.props().size).toBe('2xl'); expect(pageSections.first().props().variant).toBe('light'); }); }); diff --git a/awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx b/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationsList/ApplicationsList.jsx rename to awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx diff --git a/awx/ui_next/src/screens/Applications/ApplicationsList/index.js b/awx/ui_next/src/screens/Application/ApplicationsList/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/ApplicationsList/index.js rename to awx/ui_next/src/screens/Application/ApplicationsList/index.js diff --git a/awx/ui_next/src/screens/Applications/index.js b/awx/ui_next/src/screens/Application/index.js similarity index 100% rename from awx/ui_next/src/screens/Applications/index.js rename to awx/ui_next/src/screens/Application/index.js From e768d5e7fc9be142dee8a31d942b4711d4a093f8 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Mon, 8 Jun 2020 17:51:15 -0400 Subject: [PATCH 32/44] Make all_parents_must_converge settable when creating node When targeting, ../workflow_job_templates/id#/workflow_nodes/ endpoint, user could not set all_parents_must_converge to true. awx issue #7063 --- awx/api/serializers.py | 2 +- awx/main/tests/functional/api/test_workflow_node.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 75531f6e8a..15fb77f848 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3600,7 +3600,7 @@ class LaunchConfigurationBaseSerializer(BaseSerializer): ujt = self.instance.unified_job_template if ujt is None: ret = {} - for fd in ('workflow_job_template', 'identifier'): + for fd in ('workflow_job_template', 'identifier', 'all_parents_must_converge'): if fd in attrs: ret[fd] = attrs[fd] return ret diff --git a/awx/main/tests/functional/api/test_workflow_node.py b/awx/main/tests/functional/api/test_workflow_node.py index 64c22898df..ec70716f94 100644 --- a/awx/main/tests/functional/api/test_workflow_node.py +++ b/awx/main/tests/functional/api/test_workflow_node.py @@ -71,6 +71,18 @@ def test_node_accepts_prompted_fields(inventory, project, workflow_job_template, user=admin_user, expect=201) +@pytest.mark.django_db +@pytest.mark.parametrize("field_name, field_value", [ + ('all_parents_must_converge', True), + ('all_parents_must_converge', False), +]) +def test_create_node_with_field(field_name, field_value, workflow_job_template, post, admin_user): + url = reverse('api:workflow_job_template_workflow_nodes_list', + kwargs={'pk': workflow_job_template.pk}) + res = post(url, {field_name: field_value}, user=admin_user, expect=201) + assert res.data[field_name] == field_value + + @pytest.mark.django_db class TestApprovalNodes(): def test_approval_node_creation(self, post, approval_node, admin_user): From 71640490621db31c29221c985b2b183f324e7247 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 8 Jun 2020 18:07:33 -0400 Subject: [PATCH 33/44] don't block on log aggregator socket.send() calls see: https://github.com/ansible/tower/issues/4391 --- awx/main/utils/handlers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index ae0e83a9c5..c5e0014f8e 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -13,6 +13,10 @@ class RSysLogHandler(logging.handlers.SysLogHandler): append_nul = False + def _connect_unixsocket(self, address): + super(RSysLogHandler, self)._connect_unixsocket(address) + self.socket.setblocking(False) + def emit(self, msg): if not settings.LOG_AGGREGATOR_ENABLED: return @@ -26,6 +30,14 @@ class RSysLogHandler(logging.handlers.SysLogHandler): # unfortunately, we can't log that because...rsyslogd is down (and # would just us back ddown this code path) pass + except BlockingIOError: + # for , rsyslogd is no longer reading from the domain socket, and + # we're unable to write any more to it without blocking (we've seen this behavior + # from time to time when logging is totally misconfigured; + # in this scenario, it also makes more sense to just drop the messages, + # because the alternative is blocking the socket.send() in the + # Python process, which we definitely don't want to do) + pass ColorHandler = logging.StreamHandler From 9dec35989875f7ae9fa9da53cb17d487897389e4 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 8 Jun 2020 20:11:11 -0400 Subject: [PATCH 34/44] fix lint failures --- awx_collection/plugins/modules/tower_token.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py index d8300a048e..165590520d 100644 --- a/awx_collection/plugins/modules/tower_token.py +++ b/awx_collection/plugins/modules/tower_token.py @@ -150,7 +150,7 @@ def main(): ], # If we are state absent make sure one of existing_token or existing_token_id are present required_if=[ - [ 'state', 'absent', ('existing_token', 'existing_token_id'), True,], + ['state', 'absent', ('existing_token', 'existing_token_id'), True, ], ], ) From d2bbe7aa1acd6e97138ec736bcde5cfadcf151ae Mon Sep 17 00:00:00 2001 From: Rebeccah Date: Wed, 3 Jun 2020 14:18:19 -0400 Subject: [PATCH 35/44] remove memcache from everywhere and add djagno-redis to cover it --- CONTRIBUTING.md | 1 - awx/settings/defaults.py | 4 +-- docs/clustering.md | 1 - installer/roles/kubernetes/defaults/main.yml | 6 ---- .../kubernetes/templates/deployment.yml.j2 | 34 ------------------- .../roles/local_docker/defaults/main.yml | 3 -- .../roles/local_docker/tasks/compose.yml | 6 ---- .../templates/docker-compose.yml.j2 | 18 +--------- requirements/requirements.in | 2 +- requirements/requirements.txt | 2 +- tools/docker-compose-cluster.yml | 11 +----- tools/docker-compose.yml | 10 +----- 12 files changed, 7 insertions(+), 91 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1aeffb1ba..dc15b0f0f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -157,7 +157,6 @@ If you start a second terminal session, you can take a look at the running conta $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 44251b476f98 gcr.io/ansible-tower-engineering/awx_devel:devel "/entrypoint.sh /bin…" 27 seconds ago Up 23 seconds 0.0.0.0:6899->6899/tcp, 0.0.0.0:7899-7999->7899-7999/tcp, 0.0.0.0:8013->8013/tcp, 0.0.0.0:8043->8043/tcp, 0.0.0.0:8080->8080/tcp, 22/tcp, 0.0.0.0:8888->8888/tcp tools_awx_run_9e820694d57e -b049a43817b4 memcached:alpine "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:11211->11211/tcp tools_memcached_1 40de380e3c2e redis:latest "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:6379->6379/tcp tools_redis_1 b66a506d3007 postgres:10 "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:5432->5432/tcp tools_postgres_1 ``` diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index b300579471..b737a1d0c4 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -441,8 +441,8 @@ CELERYBEAT_SCHEDULE = { # Django Caching Configuration CACHES = { 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': 'unix:/var/run/memcached/memcached.sock' + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'unix:/var/run/redis/redis.sock' }, } diff --git a/docs/clustering.md b/docs/clustering.md index 7b0221beea..ecf9a97dee 100644 --- a/docs/clustering.md +++ b/docs/clustering.md @@ -218,7 +218,6 @@ Each Tower instance is made up of several different services working collaborati * **Callback Receiver** - Receives job events that result from running Ansible jobs. * **Celery** - The worker queue that processes and runs all jobs. * **Redis** - this is used as a queue for AWX to process ansible playbook callback events. -* **Memcached** - A local caching service for the instance it lives on. Tower is configured in such a way that if any of these services or their components fail, then all services are restarted. If these fail sufficiently (often in a short span of time), then the entire instance will be placed offline in an automated fashion in order to allow remediation without causing unexpected behavior. diff --git a/installer/roles/kubernetes/defaults/main.yml b/installer/roles/kubernetes/defaults/main.yml index d300bd7829..5a15a8929a 100644 --- a/installer/roles/kubernetes/defaults/main.yml +++ b/installer/roles/kubernetes/defaults/main.yml @@ -27,12 +27,6 @@ kubernetes_redis_image: "redis" kubernetes_redis_image_tag: "latest" kubernetes_redis_config_mount_path: "/usr/local/etc/redis/redis.conf" -memcached_mem_request: 1 -memcached_cpu_request: 500 - -kubernetes_memcached_version: "latest" -kubernetes_memcached_image: "memcached" - openshift_pg_emptydir: false openshift_pg_pvc_name: postgresql diff --git a/installer/roles/kubernetes/templates/deployment.yml.j2 b/installer/roles/kubernetes/templates/deployment.yml.j2 index c915f5c77f..22ce12153a 100644 --- a/installer/roles/kubernetes/templates/deployment.yml.j2 +++ b/installer/roles/kubernetes/templates/deployment.yml.j2 @@ -161,9 +161,6 @@ spec: - name: {{ kubernetes_deployment_name }}-redis-socket mountPath: "/var/run/redis" - - name: {{ kubernetes_deployment_name }}-memcached-socket - mountPath: "/var/run/memcached" - resources: requests: memory: "{{ web_mem_request }}Gi" @@ -236,9 +233,6 @@ spec: - name: {{ kubernetes_deployment_name }}-redis-socket mountPath: "/var/run/redis" - - - name: {{ kubernetes_deployment_name }}-memcached-socket - mountPath: "/var/run/memcached" env: - name: SUPERVISOR_WEB_CONFIG_PATH value: "/etc/supervisord.conf" @@ -293,31 +287,6 @@ spec: {% endif %} {% if redis_cpu_limit is defined %} cpu: "{{ redis_cpu_limit }}m" -{% endif %} - - name: {{ kubernetes_deployment_name }}-memcached - image: "{{ kubernetes_memcached_image }}:{{ kubernetes_memcached_version }}" - imagePullPolicy: Always - command: - - 'memcached' - - '-s' - - '/var/run/memcached/memcached.sock' - - '-a' - - '0666' - volumeMounts: - - name: {{ kubernetes_deployment_name }}-memcached-socket - mountPath: "/var/run/memcached" - resources: - requests: - memory: "{{ memcached_mem_request }}Gi" - cpu: "{{ memcached_cpu_request }}m" -{% if memcached_mem_limit is defined or memcached_cpu_limit is defined %} - limits: -{% endif %} -{% if memcached_mem_limit is defined %} - memory: "{{ memcached_mem_limit }}Gi" -{% endif %} -{% if memcached_cpu_limit is defined %} - cpu: "{{ memcached_cpu_limit }}m" {% endif %} {% if tolerations is defined %} tolerations: @@ -424,9 +393,6 @@ spec: - name: {{ kubernetes_deployment_name }}-redis-socket emptyDir: {} - - name: {{ kubernetes_deployment_name }}-memcached-socket - emptyDir: {} - --- apiVersion: v1 kind: Service diff --git a/installer/roles/local_docker/defaults/main.yml b/installer/roles/local_docker/defaults/main.yml index 490e1e8fcf..f8e1304702 100644 --- a/installer/roles/local_docker/defaults/main.yml +++ b/installer/roles/local_docker/defaults/main.yml @@ -7,7 +7,4 @@ redis_image: "redis" postgresql_version: "10" postgresql_image: "postgres:{{postgresql_version}}" -memcached_image: "memcached" -memcached_version: "alpine" - compose_start_containers: true diff --git a/installer/roles/local_docker/tasks/compose.yml b/installer/roles/local_docker/tasks/compose.yml index 3212732283..120b81cc1a 100644 --- a/installer/roles/local_docker/tasks/compose.yml +++ b/installer/roles/local_docker/tasks/compose.yml @@ -10,12 +10,6 @@ state: directory mode: 0777 -- name: Create Memcached socket directory - file: - path: "{{ docker_compose_dir }}/memcached_socket" - state: directory - mode: 0777 - - name: Create Docker Compose Configuration template: src: "{{ item }}.j2" diff --git a/installer/roles/local_docker/templates/docker-compose.yml.j2 b/installer/roles/local_docker/templates/docker-compose.yml.j2 index a6cf121593..edfc5e6493 100644 --- a/installer/roles/local_docker/templates/docker-compose.yml.j2 +++ b/installer/roles/local_docker/templates/docker-compose.yml.j2 @@ -7,7 +7,7 @@ services: container_name: awx_web depends_on: - redis - - memcached + - django-redis {% if pg_hostname is not defined %} - postgres {% endif %} @@ -32,7 +32,6 @@ services: - "{{ docker_compose_dir }}/credentials.py:/etc/tower/conf.d/credentials.py" - "{{ docker_compose_dir }}/nginx.conf:/etc/nginx/nginx.conf:ro" - "{{ docker_compose_dir }}/redis_socket:/var/run/redis/:rw" - - "{{ docker_compose_dir }}/memcached_socket:/var/run/memcached/:rw" {% if project_data_dir is defined %} - "{{ project_data_dir +':/var/lib/awx/projects:rw' }}" {% endif %} @@ -76,7 +75,6 @@ services: container_name: awx_task depends_on: - redis - - memcached - web {% if pg_hostname is not defined %} - postgres @@ -93,7 +91,6 @@ services: - "{{ docker_compose_dir }}/environment.sh:/etc/tower/conf.d/environment.sh" - "{{ docker_compose_dir }}/credentials.py:/etc/tower/conf.d/credentials.py" - "{{ docker_compose_dir }}/redis_socket:/var/run/redis/:rw" - - "{{ docker_compose_dir }}/memcached_socket:/var/run/memcached/:rw" {% if project_data_dir is defined %} - "{{ project_data_dir +':/var/lib/awx/projects:rw' }}" {% endif %} @@ -142,19 +139,6 @@ services: volumes: - "{{ docker_compose_dir }}/redis.conf:/usr/local/etc/redis/redis.conf:ro" - "{{ docker_compose_dir }}/redis_socket:/var/run/redis/:rw" - - "{{ docker_compose_dir }}/memcached_socket:/var/run/memcached/:rw" - - memcached: - image: "{{ memcached_image }}:{{ memcached_version }}" - container_name: awx_memcached - command: ["-s", "/var/run/memcached/memcached.sock", "-a", "0666"] - restart: unless-stopped - environment: - http_proxy: {{ http_proxy | default('') }} - https_proxy: {{ https_proxy | default('') }} - no_proxy: {{ no_proxy | default('') }} - volumes: - - "{{ docker_compose_dir }}/memcached_socket:/var/run/memcached/:rw" {% if pg_hostname is not defined %} postgres: diff --git a/requirements/requirements.in b/requirements/requirements.in index c847496844..b03e163e3c 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -17,6 +17,7 @@ django-polymorphic django-pglocks django-qsstats-magic django-radius==1.3.3 # FIX auth does not work with later versions +django-redis django-solo django-split-settings django-taggit @@ -33,7 +34,6 @@ prometheus_client psycopg2 pygerduty pyparsing -python-memcached python-radius python3-saml pyyaml>=5.3.1 # minimum version to pull in new pyyaml for CVE-2017-18342 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 00fbee267d..0d364a4922 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -32,6 +32,7 @@ django-oauth-toolkit==1.1.3 # via -r /awx_devel/requirements/requirements.in django-pglocks==1.0.4 # via -r /awx_devel/requirements/requirements.in django-polymorphic==2.1.2 # via -r /awx_devel/requirements/requirements.in django-qsstats-magic==1.1.0 # via -r /awx_devel/requirements/requirements.in +django-redis==4.5.0 django-radius==1.3.3 # via -r /awx_devel/requirements/requirements.in django-solo==1.1.3 # via -r /awx_devel/requirements/requirements.in django-split-settings==1.0.0 # via -r /awx_devel/requirements/requirements.in @@ -93,7 +94,6 @@ pyrsistent==0.15.7 # via jsonschema python-daemon==2.2.4 # via ansible-runner python-dateutil==2.8.1 # via adal, kubernetes python-ldap==3.2.0 # via django-auth-ldap -python-memcached==1.59 # via -r /awx_devel/requirements/requirements.in python-radius==1.0 # via -r /awx_devel/requirements/requirements.in python-string-utils==1.0.0 # via openshift python3-openid==3.1.0 # via social-auth-core diff --git a/tools/docker-compose-cluster.yml b/tools/docker-compose-cluster.yml index 95f7f5aaa8..9b9bac1fdd 100644 --- a/tools/docker-compose-cluster.yml +++ b/tools/docker-compose-cluster.yml @@ -30,7 +30,6 @@ services: volumes: - "../:/awx_devel" - "./redis/redis_socket_ha_1:/var/run/redis/" - - "./memcached/:/var/run/memcached" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" ports: - "5899-5999:5899-5999" @@ -50,7 +49,6 @@ services: volumes: - "../:/awx_devel" - "./redis/redis_socket_ha_2:/var/run/redis/" - - "./memcached/:/var/run/memcached" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" ports: - "7899-7999:7899-7999" @@ -70,7 +68,6 @@ services: volumes: - "../:/awx_devel" - "./redis/redis_socket_ha_3:/var/run/redis/" - - "./memcached/:/var/run/memcached" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" ports: - "8899-8999:8899-8999" @@ -107,10 +104,4 @@ services: postgres: image: postgres:10 container_name: tools_postgres_1 - memcached: - user: ${CURRENT_UID} - image: memcached:alpine - container_name: tools_memcached_1 - command: ["memcached", "-s", "/var/run/memcached/memcached.sock", "-a", "0666"] - volumes: - - "./memcached/:/var/run/memcached" + diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index b1e8fb93c7..aadc953f6e 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -23,7 +23,6 @@ services: - "7899-7999:7899-7999" # default port range for sdb-listen links: - postgres - - memcached - redis # - sync # volumes_from: @@ -33,7 +32,7 @@ services: - "../:/awx_devel" - "../awx/projects/:/var/lib/awx/projects/" - "./redis/redis_socket_standalone:/var/run/redis/" - - "./memcached/:/var/run/memcached" + - "./rsyslog/:/var/lib/awx/rsyslog" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" privileged: true tty: true @@ -54,13 +53,6 @@ services: POSTGRES_HOST_AUTH_METHOD: trust volumes: - "awx_db:/var/lib/postgresql/data" - memcached: - user: ${CURRENT_UID} - image: memcached:alpine - container_name: tools_memcached_1 - command: ["memcached", "-s", "/var/run/memcached/memcached.sock", "-a", "0666"] - volumes: - - "./memcached/:/var/run/memcached" redis: image: redis:latest container_name: tools_redis_1 From b09d9cbe41318591d07809561e2b432585c74bc1 Mon Sep 17 00:00:00 2001 From: Rebeccah Hunter Date: Thu, 4 Jun 2020 12:05:03 -0400 Subject: [PATCH 36/44] removed django-redis as a dependency Co-authored-by: Shane McDonald --- installer/roles/local_docker/templates/docker-compose.yml.j2 | 1 - 1 file changed, 1 deletion(-) diff --git a/installer/roles/local_docker/templates/docker-compose.yml.j2 b/installer/roles/local_docker/templates/docker-compose.yml.j2 index edfc5e6493..eaa166a0ab 100644 --- a/installer/roles/local_docker/templates/docker-compose.yml.j2 +++ b/installer/roles/local_docker/templates/docker-compose.yml.j2 @@ -7,7 +7,6 @@ services: container_name: awx_web depends_on: - redis - - django-redis {% if pg_hostname is not defined %} - postgres {% endif %} From 669d4535b1dd11526fabe5806999fbfe859d6b79 Mon Sep 17 00:00:00 2001 From: Rebeccah Date: Thu, 4 Jun 2020 14:38:06 -0400 Subject: [PATCH 37/44] adding isolate db location and ingore for django_redis exceptions --- awx/settings/defaults.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index b737a1d0c4..8df6d4f440 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -439,10 +439,11 @@ CELERYBEAT_SCHEDULE = { } # Django Caching Configuration +DJANGO_REDIS_IGNORE_EXCEPTIONS = True CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': 'unix:/var/run/redis/redis.sock' + 'LOCATION': 'unix:/var/run/redis/redis.sock?db=1' }, } From 02cf4585f845fe41b0cf5bfc8450631000bf34e2 Mon Sep 17 00:00:00 2001 From: Rebeccah Date: Thu, 4 Jun 2020 18:15:54 -0400 Subject: [PATCH 38/44] remove memcache license file --- docs/licenses/python-memcached.txt | 556 ----------------------------- 1 file changed, 556 deletions(-) delete mode 100644 docs/licenses/python-memcached.txt diff --git a/docs/licenses/python-memcached.txt b/docs/licenses/python-memcached.txt deleted file mode 100644 index 89b9a159ca..0000000000 --- a/docs/licenses/python-memcached.txt +++ /dev/null @@ -1,556 +0,0 @@ -PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 --------------------------------------------- - -1. This LICENSE AGREEMENT is between the Python Software Foundation -("PSF"), and the Individual or Organization ("Licensee") accessing and -otherwise using this software ("Python") in source or binary form and -its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, PSF -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python -alone or in any derivative version, provided, however, that PSF's -License Agreement and PSF's notice of copyright, i.e., "Copyright (c) -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Python Software Foundation; -All Rights Reserved" are retained in Python alone or in any derivative -version prepared by Licensee. - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python. - -4. PSF is making Python available to Licensee on an "AS IS" -basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. Nothing in this License Agreement shall be deemed to create any -relationship of agency, partnership, or joint venture between PSF and -Licensee. This License Agreement does not grant permission to use PSF -trademarks or trade name in a trademark sense to endorse or promote -products or services of Licensee, or any third party. - -8. By copying, installing or otherwise using Python, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 -------------------------------------------- - -BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 - -1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an -office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the -Individual or Organization ("Licensee") accessing and otherwise using -this software in source or binary form and its associated -documentation ("the Software"). - -2. Subject to the terms and conditions of this BeOpen Python License -Agreement, BeOpen hereby grants Licensee a non-exclusive, -royalty-free, world-wide license to reproduce, analyze, test, perform -and/or display publicly, prepare derivative works, distribute, and -otherwise use the Software alone or in any derivative version, -provided, however, that the BeOpen Python License is retained in the -Software, alone or in any derivative version prepared by Licensee. - -3. BeOpen is making the Software available to Licensee on an "AS IS" -basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE -SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS -AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY -DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -5. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -6. This License Agreement shall be governed by and interpreted in all -respects by the law of the State of California, excluding conflict of -law provisions. Nothing in this License Agreement shall be deemed to -create any relationship of agency, partnership, or joint venture -between BeOpen and Licensee. This License Agreement does not grant -permission to use BeOpen trademarks or trade names in a trademark -sense to endorse or promote products or services of Licensee, or any -third party. As an exception, the "BeOpen Python" logos available at -http://www.pythonlabs.com/logos.html may be used according to the -permissions granted on that web page. - -7. By copying, installing or otherwise using the software, Licensee -agrees to be bound by the terms and conditions of this License -Agreement. - - -CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 ---------------------------------------- - -1. This LICENSE AGREEMENT is between the Corporation for National -Research Initiatives, having an office at 1895 Preston White Drive, -Reston, VA 20191 ("CNRI"), and the Individual or Organization -("Licensee") accessing and otherwise using Python 1.6.1 software in -source or binary form and its associated documentation. - -2. Subject to the terms and conditions of this License Agreement, CNRI -hereby grants Licensee a nonexclusive, royalty-free, world-wide -license to reproduce, analyze, test, perform and/or display publicly, -prepare derivative works, distribute, and otherwise use Python 1.6.1 -alone or in any derivative version, provided, however, that CNRI's -License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) -1995-2001 Corporation for National Research Initiatives; All Rights -Reserved" are retained in Python 1.6.1 alone or in any derivative -version prepared by Licensee. Alternately, in lieu of CNRI's License -Agreement, Licensee may substitute the following text (omitting the -quotes): "Python 1.6.1 is made available subject to the terms and -conditions in CNRI's License Agreement. This Agreement together with -Python 1.6.1 may be located on the Internet using the following -unique, persistent identifier (known as a handle): 1895.22/1013. This -Agreement may also be obtained from a proxy server on the Internet -using the following URL: http://hdl.handle.net/1895.22/1013". - -3. In the event Licensee prepares a derivative work that is based on -or incorporates Python 1.6.1 or any part thereof, and wants to make -the derivative work available to others as provided herein, then -Licensee hereby agrees to include in any such work a brief summary of -the changes made to Python 1.6.1. - -4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" -basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR -IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND -DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS -FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT -INFRINGE ANY THIRD PARTY RIGHTS. - -5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON -1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS -A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, -OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. - -6. This License Agreement will automatically terminate upon a material -breach of its terms and conditions. - -7. This License Agreement shall be governed by the federal -intellectual property law of the United States, including without -limitation the federal copyright law, and, to the extent such -U.S. federal law does not apply, by the law of the Commonwealth of -Virginia, excluding Virginia's conflict of law provisions. -Notwithstanding the foregoing, with regard to derivative works based -on Python 1.6.1 that incorporate non-separable material that was -previously distributed under the GNU General Public License (GPL), the -law of the Commonwealth of Virginia shall govern this License -Agreement only as to issues arising under or with respect to -Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this -License Agreement shall be deemed to create any relationship of -agency, partnership, or joint venture between CNRI and Licensee. This -License Agreement does not grant permission to use CNRI trademarks or -trade name in a trademark sense to endorse or promote products or -services of Licensee, or any third party. - -8. By clicking on the "ACCEPT" button where indicated, or by copying, -installing or otherwise using Python 1.6.1, Licensee agrees to be -bound by the terms and conditions of this License Agreement. - - ACCEPT - - -CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 --------------------------------------------------- - -Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, -The Netherlands. All rights reserved. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of Stichting Mathematisch -Centrum or CWI not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior -permission. - -STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO -THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE -FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT -OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -This copy of Python includes a copy of bzip2, which is licensed under the following terms: - - -This program, "bzip2", the associated library "libbzip2", and all -documentation, are copyright (C) 1996-2005 Julian R Seward. All -rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. The origin of this software must not be misrepresented; you must - not claim that you wrote the original software. If you use this - software in a product, an acknowledgment in the product - documentation would be appreciated but is not required. - -3. Altered source versions must be plainly marked as such, and must - not be misrepresented as being the original software. - -4. The name of the author may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Julian Seward, Cambridge, UK. -jseward@acm.org -bzip2/libbzip2 version 1.0.3 of 15 February 2005 - - -This copy of Python includes a copy of db, which is licensed under the following terms: - -/*- - * $Id: LICENSE,v 12.1 2005/06/16 20:20:10 bostic Exp $ - */ - -The following is the license that applies to this copy of the Berkeley DB -software. For a license to use the Berkeley DB software under conditions -other than those described here, or to purchase support for this software, -please contact Sleepycat Software by email at info@sleepycat.com, or on -the Web at http://www.sleepycat.com. - -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -/* - * Copyright (c) 1990-2005 - * Sleepycat Software. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Redistributions in any form must be accompanied by information on - * how to obtain complete source code for the DB software and any - * accompanying software that uses the DB software. The source code - * must either be included in the distribution or be available for no - * more than the cost of distribution plus a nominal fee, and must be - * freely redistributable under reasonable conditions. For an - * executable file, complete source code means the source code for all - * modules it contains. It does not include source code for modules or - * files that typically accompany the major components of the operating - * system on which the executable file runs. - * - * THIS SOFTWARE IS PROVIDED BY SLEEPYCAT SOFTWARE ``AS IS'' AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR - * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SLEEPYCAT SOFTWARE - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -/* - * Copyright (c) 1990, 1993, 1994, 1995 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -/* - * Copyright (c) 1995, 1996 - * The President and Fellows of Harvard University. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY HARVARD AND ITS CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL HARVARD OR ITS CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -This copy of Python includes a copy of openssl, which is licensed under the following terms: - - - LICENSE ISSUES - ============== - - The OpenSSL toolkit stays under a dual license, i.e. both the conditions of - the OpenSSL License and the original SSLeay license apply to the toolkit. - See below for the actual license texts. Actually both licenses are BSD-style - Open Source licenses. In case of any license issues related to OpenSSL - please contact openssl-core@openssl.org. - - OpenSSL License - --------------- - -/* ==================================================================== - * Copyright (c) 1998-2005 The OpenSSL Project. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - - Original SSLeay License - ----------------------- - -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - - -This copy of Python includes a copy of tcl, which is licensed under the following terms: - -This software is copyrighted by the Regents of the University of -California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState -Corporation and other parties. The following terms apply to all files -associated with the software unless explicitly disclaimed in -individual files. - -The authors hereby grant permission to use, copy, modify, distribute, -and license this software and its documentation for any purpose, provided -that existing copyright notices are retained in all copies and that this -notice is included verbatim in any distributions. No written agreement, -license, or royalty fee is required for any of the authorized uses. -Modifications to this software may be copyrighted by their authors -and need not follow the licensing terms described here, provided that -the new terms are clearly indicated on the first page of each file where -they apply. - -IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY -FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY -DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE -IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE -NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR -MODIFICATIONS. - -GOVERNMENT USE: If you are acquiring this software on behalf of the -U.S. government, the Government shall have only "Restricted Rights" -in the software and related documentation as defined in the Federal -Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you -are acquiring the software on behalf of the Department of Defense, the -software shall be classified as "Commercial Computer Software" and the -Government shall have only "Restricted Rights" as defined in Clause -252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the -authors grant the U.S. Government and others acting in its behalf -permission to use and distribute the software in accordance with the -terms specified in this license. - -This copy of Python includes a copy of tk, which is licensed under the following terms: - -This software is copyrighted by the Regents of the University of -California, Sun Microsystems, Inc., and other parties. The following -terms apply to all files associated with the software unless explicitly -disclaimed in individual files. - -The authors hereby grant permission to use, copy, modify, distribute, -and license this software and its documentation for any purpose, provided -that existing copyright notices are retained in all copies and that this -notice is included verbatim in any distributions. No written agreement, -license, or royalty fee is required for any of the authorized uses. -Modifications to this software may be copyrighted by their authors -and need not follow the licensing terms described here, provided that -the new terms are clearly indicated on the first page of each file where -they apply. - -IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY -FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY -DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - -THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE -IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE -NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR -MODIFICATIONS. - -GOVERNMENT USE: If you are acquiring this software on behalf of the -U.S. government, the Government shall have only "Restricted Rights" -in the software and related documentation as defined in the Federal -Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you -are acquiring the software on behalf of the Department of Defense, the -software shall be classified as "Commercial Computer Software" and the -Government shall have only "Restricted Rights" as defined in Clause -252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the -authors grant the U.S. Government and others acting in its behalf -permission to use and distribute the software in accordance with the -terms specified in this license. From 60800d6740e068a3104a6eb1f99c621b7a2a050c Mon Sep 17 00:00:00 2001 From: Rebeccah Date: Mon, 8 Jun 2020 17:12:59 -0400 Subject: [PATCH 39/44] add license file for django-redis --- docs/licenses/django-redis.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/licenses/django-redis.txt diff --git a/docs/licenses/django-redis.txt b/docs/licenses/django-redis.txt new file mode 100644 index 0000000000..5e1ae723df --- /dev/null +++ b/docs/licenses/django-redis.txt @@ -0,0 +1,26 @@ +Copyright (c) 2011-2016 Andrey Antukh +Copyright (c) 2011 Sean Bleier + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From d7f9e66710c9b1eaa71791f8035474da3c666ab4 Mon Sep 17 00:00:00 2001 From: Rebeccah Date: Mon, 8 Jun 2020 17:54:58 -0400 Subject: [PATCH 40/44] added changelog entry --- CHANGELOG.md | 1 + tools/docker-compose-cluster.yml | 1 - tools/docker-compose.yml | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e65baca3..4bd55f4f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ This is a list of high-level changes for each release of AWX. A full list of commits can be found at `https://github.com/ansible/awx/releases/tag/`. ## 12.0.0 (TBD) +- Removed memcached as a dependency of AWX (https://github.com/ansible/awx/pull/7240) - Moved to a single container image build instead of separate awx_web and awx_task images. The container image is just `awx` (https://github.com/ansible/awx/pull/7228) - Official AWX container image builds now use a two-stage container build process that notably reduces the size of our published images (https://github.com/ansible/awx/pull/7017) - Removed support for HipChat notifications ([EoL announcement](https://www.atlassian.com/partnerships/slack/faq#faq-98b17ca3-247f-423b-9a78-70a91681eff0)); all previously-created HipChat notification templates will be deleted due to this removal. diff --git a/tools/docker-compose-cluster.yml b/tools/docker-compose-cluster.yml index 9b9bac1fdd..ffd8cff101 100644 --- a/tools/docker-compose-cluster.yml +++ b/tools/docker-compose-cluster.yml @@ -104,4 +104,3 @@ services: postgres: image: postgres:10 container_name: tools_postgres_1 - diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index aadc953f6e..a7e174aced 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -32,7 +32,6 @@ services: - "../:/awx_devel" - "../awx/projects/:/var/lib/awx/projects/" - "./redis/redis_socket_standalone:/var/run/redis/" - - "./rsyslog/:/var/lib/awx/rsyslog" - "./docker-compose/supervisor.conf:/etc/supervisord.conf" privileged: true tty: true From fa1294922b56d9bf4c80e96166afec3de8bd8dbe Mon Sep 17 00:00:00 2001 From: nixocio Date: Tue, 12 May 2020 16:27:30 -0400 Subject: [PATCH 41/44] Add support Prompt on Launch for Workflow Job Template Add support Prompt on Launch for Workflow Job Template see: https://github.com/ansible/awx/issues/5819 --- .../WorkflowJobTemplateEdit.jsx | 2 +- .../shared/WorkflowJobTemplateForm.jsx | 105 ++++++++++++------ .../shared/WorkflowJobTemplateForm.test.jsx | 31 ++++-- 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx index 81478eb9a4..be61a0ee43 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.jsx @@ -19,7 +19,7 @@ function WorkflowJobTemplateEdit({ template }) { webhook_key, ...templatePayload } = values; - templatePayload.inventory = inventory?.id; + templatePayload.inventory = inventory?.id || null; templatePayload.organization = organization?.id; templatePayload.webhook_credential = webhook_credential?.id || null; diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx index 50aca155b6..8306b849cf 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.jsx @@ -1,13 +1,13 @@ import React, { useState } from 'react'; import { t } from '@lingui/macro'; - import PropTypes, { shape } from 'prop-types'; import { withI18n } from '@lingui/react'; import { useField, withFormik } from 'formik'; -import { Form, FormGroup, Checkbox } from '@patternfly/react-core'; +import { Form, FormGroup, Checkbox, TextInput } from '@patternfly/react-core'; import { required } from '../../../util/validators'; +import FieldWithPrompt from '../../../components/FieldWithPrompt'; import FormField, { FieldTooltip, FormSubmitError, @@ -36,19 +36,20 @@ function WorkflowJobTemplateForm({ i18n, submitError, }) { - const [hasContentError, setContentError] = useState(null); - - const [organizationField, organizationMeta, organizationHelpers] = useField( - 'organization' + const [enableWebhooks, setEnableWebhooks] = useState( + Boolean(template.webhook_service) ); + const [hasContentError, setContentError] = useState(null); + const [askInventoryOnLaunchField] = useField('ask_inventory_on_launch'); const [inventoryField, inventoryMeta, inventoryHelpers] = useField( 'inventory' ); const [labelsField, , labelsHelpers] = useField('labels'); - - const [enableWebhooks, setEnableWebhooks] = useState( - Boolean(template.webhook_service) + const [limitField, limitMeta, limitHelpers] = useField('limit'); + const [organizationField, organizationMeta, organizationHelpers] = useField( + 'organization' ); + const [scmField, , scmHelpers] = useField('scm_branch'); if (hasContentError) { return ; @@ -79,39 +80,74 @@ function WorkflowJobTemplateForm({ value={organizationField.value} isValid={!organizationMeta.error} /> - - + + inventoryHelpers.setTouched()} onChange={value => { - inventoryHelpers.setValue(value || null); + inventoryHelpers.setValue(value); + }} + required={askInventoryOnLaunchField.value} + touched={inventoryMeta.touched} + error={inventoryMeta.error} + /> + {(inventoryMeta.touched || askInventoryOnLaunchField.value) && + inventoryMeta.error && ( +
+ {inventoryMeta.error} +
+ )} +
+ + + { + limitHelpers.setValue(value); }} /> -
- - + + + > + { + scmHelpers.setValue(value); + }} + /> + @@ -133,6 +169,7 @@ function WorkflowJobTemplateForm({ id="wfjt-variables" name="extra_vars" label={i18n._(t`Variables`)} + promptId="template-ask-variables-on-launch" tooltip={i18n._( t`Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Tower documentation for example syntax.` )} diff --git a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx index 4cddaec8e7..f572c320c4 100644 --- a/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx +++ b/awx/ui_next/src/screens/Template/shared/WorkflowJobTemplateForm.test.jsx @@ -114,9 +114,9 @@ describe('', () => { 'FormField[name="name"]', 'FormField[name="description"]', 'FormGroup[label="Organization"]', - 'FormGroup[label="Inventory"]', - 'FormField[name="limit"]', - 'FormField[name="scm_branch"]', + 'FieldWithPrompt[label="Inventory"]', + 'FieldWithPrompt[label="Limit"]', + 'FieldWithPrompt[label="Source control branch"]', 'FormGroup[label="Labels"]', 'VariablesField', ]; @@ -137,11 +137,6 @@ describe('', () => { element: 'wfjt-description', value: { value: 'new bar', name: 'description' }, }, - { element: 'wfjt-limit', value: { value: 1234567890, name: 'limit' } }, - { - element: 'wfjt-scm_branch', - value: { value: 'new branch', name: 'scm_branch' }, - }, ]; const changeInputs = async ({ element, value }) => { wrapper.find(`input#${element}`).simulate('change', { @@ -177,6 +172,26 @@ describe('', () => { inputsToChange.map(input => assertChanges(input)); }); + test('test changes in FieldWithPrompt', async () => { + await act(async () => { + wrapper.find('TextInputBase#text-wfjt-scm-branch').prop('onChange')( + 'main' + ); + wrapper.find('TextInputBase#text-wfjt-limit').prop('onChange')( + 1234567890 + ); + }); + + wrapper.update(); + + expect(wrapper.find('input#text-wfjt-scm-branch').prop('value')).toEqual( + 'main' + ); + expect(wrapper.find('input#text-wfjt-limit').prop('value')).toEqual( + 1234567890 + ); + }); + test('webhooks and enable concurrent jobs functions properly', async () => { act(() => { wrapper.find('Checkbox[aria-label="Enable Webhook"]').invoke('onChange')( From 2bbbb04499629ef1be269116a525ed31d229bf60 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 9 Jun 2020 10:19:53 -0400 Subject: [PATCH 42/44] Bump version to 12.0.0 --- CHANGELOG.md | 2 +- VERSION | 2 +- awxkit/VERSION | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bd55f4f58..5d248c8173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This is a list of high-level changes for each release of AWX. A full list of commits can be found at `https://github.com/ansible/awx/releases/tag/`. -## 12.0.0 (TBD) +## 12.0.0 (Jun 9, 2020) - Removed memcached as a dependency of AWX (https://github.com/ansible/awx/pull/7240) - Moved to a single container image build instead of separate awx_web and awx_task images. The container image is just `awx` (https://github.com/ansible/awx/pull/7228) - Official AWX container image builds now use a two-stage container build process that notably reduces the size of our published images (https://github.com/ansible/awx/pull/7017) diff --git a/VERSION b/VERSION index b85c6c7b03..4044f90867 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -11.2.0 +12.0.0 diff --git a/awxkit/VERSION b/awxkit/VERSION index b85c6c7b03..4044f90867 100644 --- a/awxkit/VERSION +++ b/awxkit/VERSION @@ -1 +1 @@ -11.2.0 +12.0.0 From 0ad78874ce36234a964b7971770811229936652e Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 9 Jun 2020 12:29:33 -0400 Subject: [PATCH 43/44] remove TCP ports for redis (it only listens on a unix domain socket) --- CONTRIBUTING.md | 2 +- tools/docker-compose-cluster.yml | 6 ------ tools/docker-compose.yml | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc15b0f0f1..42516b9a15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -157,7 +157,7 @@ If you start a second terminal session, you can take a look at the running conta $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 44251b476f98 gcr.io/ansible-tower-engineering/awx_devel:devel "/entrypoint.sh /bin…" 27 seconds ago Up 23 seconds 0.0.0.0:6899->6899/tcp, 0.0.0.0:7899-7999->7899-7999/tcp, 0.0.0.0:8013->8013/tcp, 0.0.0.0:8043->8043/tcp, 0.0.0.0:8080->8080/tcp, 22/tcp, 0.0.0.0:8888->8888/tcp tools_awx_run_9e820694d57e -40de380e3c2e redis:latest "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:6379->6379/tcp tools_redis_1 +40de380e3c2e redis:latest "docker-entrypoint.s…" 28 seconds ago Up 26 seconds b66a506d3007 postgres:10 "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:5432->5432/tcp tools_postgres_1 ``` **NOTE** diff --git a/tools/docker-compose-cluster.yml b/tools/docker-compose-cluster.yml index ffd8cff101..7aec34d8e4 100644 --- a/tools/docker-compose-cluster.yml +++ b/tools/docker-compose-cluster.yml @@ -79,8 +79,6 @@ services: volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/redis_socket_ha_1:/var/run/redis/" - ports: - - "63791:63791" redis_2: user: ${CURRENT_UID} image: redis:latest @@ -89,8 +87,6 @@ services: volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/redis_socket_ha_2:/var/run/redis/" - ports: - - "63792:63792" redis_3: user: ${CURRENT_UID} image: redis:latest @@ -99,8 +95,6 @@ services: volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" - "./redis/redis_socket_ha_3:/var/run/redis/" - ports: - - "63793:63793" postgres: image: postgres:10 container_name: tools_postgres_1 diff --git a/tools/docker-compose.yml b/tools/docker-compose.yml index a7e174aced..9c5808bd41 100644 --- a/tools/docker-compose.yml +++ b/tools/docker-compose.yml @@ -55,8 +55,6 @@ services: redis: image: redis:latest container_name: tools_redis_1 - ports: - - "6379:6379" user: ${CURRENT_UID} volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf" From 123346241965f9e496a7de70f893ff38968b29cf Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 9 Jun 2020 12:39:10 -0400 Subject: [PATCH 44/44] add some new changelog entries for 12.0.0 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d248c8173..d0d4a1b10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ This is a list of high-level changes for each release of AWX. A full list of com - Fixed a bug that caused CyberArk AIM credential plugin looks to hang forever in some environments (https://github.com/ansible/awx/issues/6986) - Fixed a bug that caused ANY/ALL converage settings not to properly save when editing approval nodes in the UI (https://github.com/ansible/awx/issues/6998) - Fixed a bug that broke support for the satellite6_group_prefix source variable (https://github.com/ansible/awx/issues/7031) +- Fixed a bug that prevented changes to workflow node convergence settings when approval nodes were in use (https://github.com/ansible/awx/issues/7063) +- Fixed a bug that caused notifications to fail on newer version of Mattermost (https://github.com/ansible/awx/issues/7264) +- Fixed a bug (by upgrading to 0.8.1 of the foreman collection) that prevented host_filters from working properly with Foreman-based inventory (https://github.com/ansible/awx/issues/7225) - Fixed a bug that prevented the usage of the Conjur credential plugin with secrets that contain spaces (https://github.com/ansible/awx/issues/7191) - Fixed a bug in awx-manage run_wsbroadcast --status in kubernetes (https://github.com/ansible/awx/pull/7009) - Fixed a bug that broke notification toggles for system jobs in the UI (https://github.com/ansible/awx/pull/7042)