diff --git a/Makefile b/Makefile index f5e5ef2ce0..4337c13c22 100644 --- a/Makefile +++ b/Makefile @@ -600,7 +600,6 @@ docker-compose-cluster-elk: docker-auth minishift-dev: ansible-playbook -i localhost, -e devtree_directory=$(CURDIR) tools/clusterdevel/start_minishift_dev.yml - clean-elk: docker stop tools_kibana_1 docker stop tools_logstash_1 diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 71250e6a0b..68da3df44a 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -2130,6 +2130,7 @@ class HostFactVersionsList(SystemTrackingEnforcementMixin, ParentMixin, ListAPIV serializer_class = FactVersionSerializer parent_model = Host search_fields = ('facts',) + deprecated = True def get_queryset(self): from_spec = self.request.query_params.get('from', None) @@ -2155,6 +2156,7 @@ class HostFactCompareView(SystemTrackingEnforcementMixin, SubDetailAPIView): model = Fact parent_model = Host serializer_class = FactSerializer + deprecated = True def retrieve(self, request, *args, **kwargs): datetime_spec = request.query_params.get('datetime', None) @@ -4175,6 +4177,11 @@ class JobRelaunch(RetrieveAPIView): 'Cannot relaunch because previous job had 0 {status_value} hosts.' ).format(status_value=retry_hosts)}, status=status.HTTP_400_BAD_REQUEST) copy_kwargs['limit'] = ','.join(retry_host_list) + limit_length = len(copy_kwargs['limit']) + if limit_length > 1024: + return Response({'limit': _( + 'Cannot relaunch because the limit length {limit_length} exceeds the max of {limit_max}.' + ).format(limit_length=limit_length, limit_max=1024)}, status=status.HTTP_400_BAD_REQUEST) new_job = obj.copy_unified_job(**copy_kwargs) result = new_job.signal_start(**serializer.validated_data['credential_passwords']) diff --git a/awx/conf/migrations/0006_v331_ldap_group_type.py b/awx/conf/migrations/0006_v331_ldap_group_type.py new file mode 100644 index 0000000000..8bfe3ef0e2 --- /dev/null +++ b/awx/conf/migrations/0006_v331_ldap_group_type.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +# AWX +from awx.conf.migrations._ldap_group_type import fill_ldap_group_type_params + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('conf', '0005_v330_rename_two_session_settings'), + ] + + operations = [ + migrations.RunPython(fill_ldap_group_type_params), + ] diff --git a/awx/conf/migrations/_ldap_group_type.py b/awx/conf/migrations/_ldap_group_type.py new file mode 100644 index 0000000000..479b12cdb7 --- /dev/null +++ b/awx/conf/migrations/_ldap_group_type.py @@ -0,0 +1,30 @@ + +import inspect + +from django.conf import settings +from django.utils.timezone import now + + +def fill_ldap_group_type_params(apps, schema_editor): + group_type = settings.AUTH_LDAP_GROUP_TYPE + Setting = apps.get_model('conf', 'Setting') + + group_type_params = {'name_attr': 'cn', 'member_attr': 'member'} + qs = Setting.objects.filter(key='AUTH_LDAP_GROUP_TYPE_PARAMS') + entry = None + if qs.exists(): + entry = qs[0] + group_type_params = entry.value + else: + entry = Setting(key='AUTH_LDAP_GROUP_TYPE_PARAMS', + value=group_type_params, + created=now(), + modified=now()) + + init_attrs = set(inspect.getargspec(group_type.__init__).args[1:]) + for k in group_type_params.keys(): + if k not in init_attrs: + del group_type_params[k] + + entry.value = group_type_params + entry.save() diff --git a/awx/main/management/commands/cleanup_facts.py b/awx/main/management/commands/cleanup_facts.py index f60dbc0f03..76bc4190fb 100644 --- a/awx/main/management/commands/cleanup_facts.py +++ b/awx/main/management/commands/cleanup_facts.py @@ -3,6 +3,7 @@ # Python import re +import sys from dateutil.relativedelta import relativedelta # Django @@ -129,6 +130,7 @@ class Command(BaseCommand): @transaction.atomic def handle(self, *args, **options): + sys.stderr.write("This command has been deprecated and will be removed in a future release.\n") if not feature_enabled('system_tracking'): raise CommandError("The System Tracking feature is not enabled for your instance") cleanup_facts = CleanupFacts() diff --git a/awx/main/queue.py b/awx/main/queue.py index 8f5b680f84..d8306c05ba 100644 --- a/awx/main/queue.py +++ b/awx/main/queue.py @@ -2,6 +2,7 @@ # All Rights Reserved. # Python +import json import logging import os @@ -12,10 +13,31 @@ from django.conf import settings # Kombu from kombu import Connection, Exchange, Producer +from kombu.serialization import registry __all__ = ['CallbackQueueDispatcher'] +# use a custom JSON serializer so we can properly handle !unsafe and !vault +# objects that may exist in events emitted by the callback plugin +# see: https://github.com/ansible/ansible/pull/38759 +class AnsibleJSONEncoder(json.JSONEncoder): + + def default(self, o): + if getattr(o, 'yaml_tag', None) == '!vault': + return o.data + return super(AnsibleJSONEncoder, self).default(o) + + +registry.register( + 'json-ansible', + lambda obj: json.dumps(obj, cls=AnsibleJSONEncoder), + lambda obj: json.loads(obj), + content_type='application/json', + content_encoding='utf-8' +) + + class CallbackQueueDispatcher(object): def __init__(self): @@ -41,7 +63,7 @@ class CallbackQueueDispatcher(object): producer = Producer(self.connection) producer.publish(obj, - serializer='json', + serializer='json-ansible', compression='bzip2', exchange=self.exchange, declare=[self.exchange], diff --git a/awx/ui/client/src/instance-groups/instances/instances.controller.js b/awx/ui/client/src/instance-groups/instances/instances.controller.js index 0c9e82228a..2f956ae634 100644 --- a/awx/ui/client/src/instance-groups/instances/instances.controller.js +++ b/awx/ui/client/src/instance-groups/instances/instances.controller.js @@ -51,7 +51,7 @@ function InstancesController ($scope, $state, $http, models, strings, Dataset, P }; vm.toggle = (toggled) => { - const instance = _.find(vm.instances, 'id', toggled.id); + const instance = _.find(vm.instances, ['id', toggled.id]); instance.enabled = !instance.enabled; const data = { diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index 4aa3044adf..0e27408c1f 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -243,6 +243,15 @@ function($filter, $state, $stateParams, Wait, $scope, moment, let jobTemplate = new JobTemplate(); + const codeMirrorExtraVars = () => { + ParseTypeChange({ + scope: $scope, + variable: 'extraVars', + parse_variable: 'parseType', + field_id: 'SchedulerForm-extraVars' + }); + }; + Rest.setUrl(scheduleResolve.related.credentials); $q.all([jobTemplate.optionsLaunch(ParentObject.id), jobTemplate.getLaunch(ParentObject.id), Rest.get()]) @@ -312,21 +321,10 @@ function($filter, $state, $stateParams, Wait, $scope, moment, prompts.credentials.value = defaultCredsWithoutOverrides.concat(scheduleCredentials); - if (launchConf.ask_variables_on_launch) { - // the extra vars codemirror is ONLY shown if the - // schedule is for a JT and the JT has - // ask_variables_on_launch = true. - $scope.extraVars = ParentObject.extra_vars === '' ? '---' : ParentObject.extra_vars; - $scope.noVars = false; - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - parse_variable: 'parseType', - field_id: 'SchedulerForm-extraVars' - }); - } else { - $scope.noVars = true; - } + // the extra vars codemirror is ONLY shown if the + // schedule is for a JT and the JT has + // ask_variables_on_launch = true + $scope.noVars = !launchConf.ask_variables_on_launch; if (!launchConf.survey_enabled && !launchConf.ask_inventory_on_launch && @@ -343,6 +341,10 @@ function($filter, $state, $stateParams, Wait, $scope, moment, launchConf.passwords_needed_to_start.length === 0 && launchConf.variables_needed_to_start.length === 0) { $scope.showPromptButton = false; + + if (launchConf.ask_variables_on_launch) { + codeMirrorExtraVars(); + } } else { $scope.showPromptButton = true; @@ -368,13 +370,7 @@ function($filter, $state, $stateParams, Wait, $scope, moment, $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - parse_variable: 'parseType', - field_id: 'SchedulerForm-extraVars', - readOnly: !$scope.schedule_obj.summary_fields.user_capabilities.edit - }); + codeMirrorExtraVars(); $scope.promptData = { launchConf: launchConf, @@ -397,6 +393,7 @@ function($filter, $state, $stateParams, Wait, $scope, moment, watchForPromptChanges(); }); } else { + codeMirrorExtraVars(); $scope.promptData = { launchConf: launchConf, launchOptions: launchOptions, diff --git a/awx/ui/client/src/shared/paginate/paginate.controller.js b/awx/ui/client/src/shared/paginate/paginate.controller.js index 28be44eeae..6da86f4880 100644 --- a/awx/ui/client/src/shared/paginate/paginate.controller.js +++ b/awx/ui/client/src/shared/paginate/paginate.controller.js @@ -15,12 +15,11 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q "\n"); if ($scope.querySet){ - let origQuerySet = _.cloneDeep($scope.querySet); - queryset = _.merge(origQuerySet, { page_size: pageSize }); + $scope.querySet = _.merge($scope.querySet, { page_size: `${pageSize}`}); } else { - queryset = _.merge($stateParams[`${$scope.iterator}_search`], { page_size: pageSize, page: 1 }); + $scope.querySet = _.merge($stateParams[`${$scope.iterator}_search`], { page_size: `${pageSize}`}); } - $scope.toPage(); + $scope.toPage(1); }; $scope.toPage = function(page) { diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index a7890c288b..61d04b6b82 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -499,7 +499,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p $scope.$watch('selectedTemplate', () => { $scope.job_templates.forEach(function(row, i) { - if(_.has($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { + if(_.hasIn($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { $scope.job_templates[i].checked = 1; } else { @@ -576,7 +576,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p $scope.$watch('selectedTemplate', () => { $scope.workflow_inventory_sources.forEach(function(row, i) { - if(_.has($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { + if(_.hasIn($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { $scope.workflow_inventory_sources[i].checked = 1; } else { @@ -653,7 +653,7 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p $scope.$watch('selectedTemplate', () => { $scope.projects.forEach(function(row, i) { - if(_.has($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { + if(_.hasIn($scope, 'selectedTemplate.id') && row.id === $scope.selectedTemplate.id) { $scope.projects[i].checked = 1; } else { diff --git a/tools/sosreport/tower.py b/tools/sosreport/tower.py index 20fef80ea1..6d4aec0c02 100644 --- a/tools/sosreport/tower.py +++ b/tools/sosreport/tower.py @@ -11,17 +11,22 @@ SOSREPORT_TOWER_COMMANDS = [ "supervisorctl status", # tower process status "rabbitmqctl status", "rabbitmqctl cluster_status", - "/var/lib/awx/venv/awx/bin/pip freeze", # pip package list - "/var/lib/awx/venv/ansible/bin/pip freeze", # pip package list + "/var/lib/awx/venv/awx/bin/pip freeze", # pip package list + "/var/lib/awx/venv/awx/bin/pip freeze -l", # pip package list without globally-installed packages + "/var/lib/awx/venv/ansible/bin/pip freeze", # pip package list + "/var/lib/awx/venv/ansible/bin/pip freeze -l", # pip package list without globally-installed packages "tree -d /var/lib/awx", # show me the dirs "ls -ll /var/lib/awx", # check permissions "ls -ll /var/lib/awx/venv", # list all venvs - "ls -ll /etc/tower" + "ls -ll /etc/tower", + "umask -p" # check current umask ] SOSREPORT_TOWER_DIRS = [ "/etc/tower/", "/etc/ansible/", + "/etc/supervisord.d/", + "/etc/nginx/", "/var/log/tower", "/var/log/nginx", "/var/log/rabbitmq",