diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e65baca3..d0d4a1b10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ 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) - 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. @@ -11,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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1aeffb1ba..42516b9a15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -157,8 +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 -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 +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/VERSION b/VERSION index b85c6c7b03..4044f90867 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -11.2.0 +12.0.0 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/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/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/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: 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") 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): 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 diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index b300579471..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.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': 'unix:/var/run/memcached/memcached.sock' + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'unix:/var/run/redis/redis.sock?db=1' }, } 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() 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": { 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", diff --git a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx index 8fcac00468..f2f8b855d6 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 { + copyTip, + entryDelay, + exitDelay, + copiedSuccessTip, + isDisabled, + } = this.props; const { copied } = this.state; return ( @@ -48,12 +54,13 @@ class ClipboardCopyButton extends React.Component { entryDelay={entryDelay} exitDelay={exitDelay} trigger="mouseenter focus click" - content={copied ? clickTip : hoverTip} + content={copied ? copiedSuccessTip : copyTip} > @@ -63,12 +70,13 @@ 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, }; ClipboardCopyButton.defaultProps = { diff --git a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx index 4ba32fb51d..35836c0634 100644 --- a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx +++ b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx @@ -13,6 +13,7 @@ describe('ClipboardCopyButton', () => { clickTip="foo" hoverTip="bar" stringToCopy="foobar!" + isDisabled={false} /> ); expect(wrapper).toHaveLength(1); @@ -23,6 +24,7 @@ describe('ClipboardCopyButton', () => { clickTip="foo" hoverTip="bar" stringToCopy="foobar!" + isDisabled={false} /> ).find('ClipboardCopyButton'); expect(wrapper.state('copied')).toBe(false); @@ -33,4 +35,15 @@ describe('ClipboardCopyButton', () => { wrapper.update(); expect(wrapper.state('copied')).toBe(false); }); + test('should render disabled button', () => { + const wrapper = mountWithContexts( + + ); + expect(wrapper.find('Button').prop('isDisabled')).toBe(true); + }); }); diff --git a/awx/ui_next/src/routeConfig.js b/awx/ui_next/src/routeConfig.js index 37f1732cb0..4b43dc4993 100644 --- a/awx/ui_next/src/routeConfig.js +++ b/awx/ui_next/src/routeConfig.js @@ -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/Application/Application.jsx b/awx/ui_next/src/screens/Application/Application/Application.jsx new file mode 100644 index 0000000000..5a002f2990 --- /dev/null +++ b/awx/ui_next/src/screens/Application/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/Application/Application/index.js b/awx/ui_next/src/screens/Application/Application/index.js new file mode 100644 index 0000000000..f76f133dd5 --- /dev/null +++ b/awx/ui_next/src/screens/Application/Application/index.js @@ -0,0 +1 @@ +export { default } from './Application'; diff --git a/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.jsx b/awx/ui_next/src/screens/Application/ApplicationAdd/ApplicationAdd.jsx new file mode 100644 index 0000000000..a25c690a7b --- /dev/null +++ b/awx/ui_next/src/screens/Application/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/Application/ApplicationAdd/index.js b/awx/ui_next/src/screens/Application/ApplicationAdd/index.js new file mode 100644 index 0000000000..54101fc16b --- /dev/null +++ b/awx/ui_next/src/screens/Application/ApplicationAdd/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationAdd'; diff --git a/awx/ui_next/src/screens/Application/ApplicationDetails/ApplicationDetails.jsx b/awx/ui_next/src/screens/Application/ApplicationDetails/ApplicationDetails.jsx new file mode 100644 index 0000000000..c8051841bb --- /dev/null +++ b/awx/ui_next/src/screens/Application/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/Application/ApplicationDetails/index.js b/awx/ui_next/src/screens/Application/ApplicationDetails/index.js new file mode 100644 index 0000000000..fc3261983b --- /dev/null +++ b/awx/ui_next/src/screens/Application/ApplicationDetails/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationDetails'; diff --git a/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx b/awx/ui_next/src/screens/Application/ApplicationEdit/ApplicationEdit.jsx new file mode 100644 index 0000000000..e72f93b681 --- /dev/null +++ b/awx/ui_next/src/screens/Application/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/Application/ApplicationEdit/index.js b/awx/ui_next/src/screens/Application/ApplicationEdit/index.js new file mode 100644 index 0000000000..2ab4beb8d4 --- /dev/null +++ b/awx/ui_next/src/screens/Application/ApplicationEdit/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationEdit'; diff --git a/awx/ui_next/src/screens/Application/Applications.jsx b/awx/ui_next/src/screens/Application/Applications.jsx index 47a5e3250a..19a23be08e 100644 --- a/awx/ui_next/src/screens/Application/Applications.jsx +++ b/awx/ui_next/src/screens/Application/Applications.jsx @@ -1,26 +1,49 @@ -import React, { Component, Fragment } from 'react'; +import React, { useState, useCallback } from 'react'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { - PageSection, - PageSectionVariants, - Title, -} from '@patternfly/react-core'; +import { Route, Switch } from 'react-router-dom'; -class Applications extends Component { - render() { - const { i18n } = this.props; - const { light } = PageSectionVariants; +import ApplicationsList from './ApplicationsList'; +import ApplicationAdd from './ApplicationAdd'; +import Application from './Application'; +import Breadcrumbs from '../../components/Breadcrumbs'; - return ( - - - {i18n._(t`Applications`)} - - - - ); - } +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/Application/Applications.test.jsx index cb747a920b..f309a2b60a 100644 --- a/awx/ui_next/src/screens/Application/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(() => { @@ -21,9 +19,7 @@ describe('', () => { test('initially renders without crashing', () => { expect(pageWrapper.length).toBe(1); - expect(pageSections.length).toBe(2); - expect(title.length).toBe(1); - expect(title.props().size).toBe('2xl'); + expect(pageSections.length).toBe(1); expect(pageSections.first().props().variant).toBe('light'); }); }); diff --git a/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx b/awx/ui_next/src/screens/Application/ApplicationsList/ApplicationsList.jsx new file mode 100644 index 0000000000..6fcf16bb73 --- /dev/null +++ b/awx/ui_next/src/screens/Application/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/Application/ApplicationsList/index.js b/awx/ui_next/src/screens/Application/ApplicationsList/index.js new file mode 100644 index 0000000000..34f1107076 --- /dev/null +++ b/awx/ui_next/src/screens/Application/ApplicationsList/index.js @@ -0,0 +1 @@ +export { default } from './ApplicationsList'; diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 614062109f..d5651a130e 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -32,6 +32,11 @@ const DataListAction = styled(_DataListAction)` grid-gap: 16px; grid-template-columns: repeat(3, 40px); `; + +const Label = styled.span` + color: var(--pf-global--disabled-color--100); +`; + function ProjectListItem({ project, isSelected, @@ -121,13 +126,17 @@ function ProjectListItem({ , {project.scm_revision.substring(0, 7)} - {project.scm_revision ? ( - - ) : null} + {!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 9dfa8e8f1b..7866015703 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx @@ -218,4 +218,34 @@ describe('', () => { ); expect(wrapper.find('CopyButton').length).toBe(0); }); + test('should render disabled copy to clipboard button', () => { + const wrapper = mountWithContexts( + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: true, + }, + }, + }} + /> + ); + expect( + wrapper.find('span[aria-label="copy to clipboard disabled"]').text() + ).toBe('Sync for revision'); + expect(wrapper.find('ClipboardCopyButton').prop('isDisabled')).toBe(true); + }); }); 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')( diff --git a/awx_collection/plugins/doc_fragments/auth.py b/awx_collection/plugins/doc_fragments/auth.py index 21e8d07482..1e77a63b4b 100644 --- a/awx_collection/plugins/doc_fragments/auth.py +++ b/awx_collection/plugins/doc_fragments/auth.py @@ -31,8 +31,12 @@ 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: 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/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 5d4f221f4e..d836c457c0 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 @@ -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 = { @@ -96,6 +96,20 @@ 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'): + 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: + 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): self.host = "https://{0}".format(self.host) @@ -504,6 +518,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_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index b1e143e3bd..1f1d776f28 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -44,6 +44,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. + - Requires Tower Version 3.7.0 or AWX 10.0.0 IS NOT backwards compatible with earlier versions. + type: str project: description: - Name of the project to use for the job template. @@ -282,6 +290,7 @@ EXAMPLES = ''' tower_job_template: name: "Ping" job_type: "run" + organization: "Default" inventory: "Local" project: "Demo" playbook: "ping.yml" @@ -332,6 +341,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(), @@ -398,19 +408,24 @@ 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) + 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', @@ -437,7 +452,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) diff --git a/awx_collection/plugins/modules/tower_settings.py b/awx_collection/plugins/modules/tower_settings.py index c706c4f0f9..9db41d9975 100644 --- a/awx_collection/plugins/modules/tower_settings.py +++ b/awx_collection/plugins/modules/tower_settings.py @@ -80,6 +80,10 @@ except ImportError: def coerce_type(module, value): + # If our value is already None we can just return directly + if value is None: + return value + yaml_ish = bool(( value.startswith('{') and value.endswith('}') ) or ( diff --git a/awx_collection/plugins/modules/tower_token.py b/awx_collection/plugins/modules/tower_token.py new file mode 100644 index 0000000000..165590520d --- /dev/null +++ b/awx_collection/plugins/modules/tower_token.py @@ -0,0 +1,201 @@ +#!/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 or destroy Ansible Tower tokens. See + U(https://www.ansible.com/tower) for an overview. + - 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) + - 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: + - 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: 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. + choices: ["present", "absent"] + default: "present" + type: str +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +- block: + - name: Create a new token using an existing token + tower_token: + description: '{{ token_description }}' + scope: "write" + state: present + tower_oauthtoken: "{{ my_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: "{{ my_username }}" + tower_password: "{{ my_password }}" + + - name: Use our new token to make another call + tower_job_list: + tower_oauthtoken: "{{ tower_token }}" + + always: + - name: Delete our Token with the token we created + tower_token: + existing_token: "{{ tower_token }}" + state: absent + when: tower_token is defined + +- name: Delete a token by its id + tower_token: + existing_token_id: 4 + state: absent +''' + +RETURN = ''' +tower_token: + type: dict + 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 + id: + description: The numeric ID of the token created + type: str + 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['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'), + existing_token_id=dict(), + state=dict(choices=['present', 'absent'], default='present'), + ) + + # Create a module for ourselves + 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') + 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: + 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) + + # 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..442fa2e9fb --- /dev/null +++ b/awx_collection/test/awx/test_token.py @@ -0,0 +1,29 @@ +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.get('changed'), result + + tokens = OAuth2AccessToken.objects.filter(description='barfoo') + 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' 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..8a42f5768e 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,14 @@ - assert: that: - "result is changed" + +- name: Handle an omit value + 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" 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..355d5dd02f --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_token/tasks/main.yml @@ -0,0 +1,110 @@ +--- +- name: Generate names + 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). 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 + # 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 + 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 }}" + 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 + tower_token: + existing_token: "{{ tower_token }}" + 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 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 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/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 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. 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) diff --git a/installer/roles/kubernetes/defaults/main.yml b/installer/roles/kubernetes/defaults/main.yml index d455afc8e6..0e7a9d55f9 100644 --- a/installer/roles/kubernetes/defaults/main.yml +++ b/installer/roles/kubernetes/defaults/main.yml @@ -38,15 +38,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 -memcached_security_context_enabled: true -memcached_security_context_privileged: false -memcached_security_context_user: 1001 - -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 7ab8a2804c..7e3d16f859 100644 --- a/installer/roles/kubernetes/templates/deployment.yml.j2 +++ b/installer/roles/kubernetes/templates/deployment.yml.j2 @@ -231,9 +231,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" @@ -310,9 +307,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" @@ -376,40 +370,6 @@ spec: {% endif %} {% if redis_cpu_limit is defined %} cpu: "{{ redis_cpu_limit }}m" -{% endif %} - - name: {{ kubernetes_deployment_name }}-memcached -{% if memcached_security_context_enabled is defined and memcached_security_context_enabled | bool %} - securityContext: -{% if memcached_security_context_privileged is defined %} - privileged: {{ memcached_security_context_privileged }} -{% endif %} -{% if memcached_security_context_user is defined %} - runAsUser: {{ memcached_security_context_user }} -{% endif %} -{% endif %} - 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: @@ -516,9 +476,6 @@ spec: - name: {{ kubernetes_deployment_name }}-redis-socket emptyDir: {} - - name: {{ kubernetes_deployment_name }}-memcached-socket - emptyDir: {} - --- apiVersion: v1 kind: Service @@ -591,4 +548,4 @@ spec: name: {{ kubernetes_deployment_name }}-web-svc weight: 100 wildcardPolicy: None -{% endif %} \ No newline at end of file +{% endif %} 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..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 - - memcached {% if pg_hostname is not defined %} - postgres {% endif %} @@ -32,7 +31,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 +74,6 @@ services: container_name: awx_task depends_on: - redis - - memcached - web {% if pg_hostname is not defined %} - postgres @@ -93,7 +90,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 +138,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..7aec34d8e4 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" @@ -82,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 @@ -92,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 @@ -102,15 +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 - 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 d4dee7f101..9c5808bd41 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,8 +32,6 @@ 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 @@ -55,18 +52,9 @@ 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 - ports: - - "6379:6379" user: ${CURRENT_UID} volumes: - "./redis/redis.conf:/usr/local/etc/redis/redis.conf"