From f0d68f429c410d8cefbd7c60232d6c31d172d1dc Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Sat, 22 Jul 2017 00:06:44 -0400 Subject: [PATCH 001/342] Updating some dev environment tooling for version numbers --- tools/docker-compose/Dockerfile | 4 ++-- .../{ansible-tower.egg-link => ansible-awx.egg-link} | 0 .../PKG-INFO | 10 +++++----- .../SOURCES.txt | 0 .../dependency_links.txt | 0 .../entry_points.txt | 0 .../not-zip-safe | 0 .../top_level.txt | 0 tools/docker-compose/awx-manage | 6 +++--- tools/docker-compose/start_development.sh | 6 ++++-- 10 files changed, 14 insertions(+), 12 deletions(-) rename tools/docker-compose/{ansible-tower.egg-link => ansible-awx.egg-link} (100%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/PKG-INFO (73%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/SOURCES.txt (100%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/dependency_links.txt (100%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/entry_points.txt (100%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/not-zip-safe (100%) rename tools/docker-compose/{ansible_tower.egg-info => ansible_awx.egg-info}/top_level.txt (100%) diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index 5a5ca22cbb..b9e5298901 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -20,9 +20,9 @@ RUN /usr/bin/ssh-keygen -q -t rsa -N "" -f /root/.ssh/id_rsa RUN mkdir -p /data/db RUN pip2 install honcho RUN pip2 install supervisor -ADD tools/docker-compose/ansible-tower.egg-link /tmp/ansible-tower.egg-link +ADD tools/docker-compose/ansible-awx.egg-link /tmp/ansible-awx.egg-link ADD tools/docker-compose/awx-manage /usr/local/bin/awx-manage -ADD tools/docker-compose/ansible_tower.egg-info /tmp/ansible_tower.egg-info +ADD tools/docker-compose/ansible_awx.egg-info /tmp/ansible_awx.egg-info RUN ln -Ffs /awx_devel/tools/docker-compose/nginx.conf /etc/nginx/nginx.conf RUN ln -Ffs /awx_devel/tools/docker-compose/nginx.vh.default.conf /etc/nginx/conf.d/nginx.vh.default.conf RUN ln -s /awx_devel/tools/docker-compose/start_development.sh /start_development.sh diff --git a/tools/docker-compose/ansible-tower.egg-link b/tools/docker-compose/ansible-awx.egg-link similarity index 100% rename from tools/docker-compose/ansible-tower.egg-link rename to tools/docker-compose/ansible-awx.egg-link diff --git a/tools/docker-compose/ansible_tower.egg-info/PKG-INFO b/tools/docker-compose/ansible_awx.egg-info/PKG-INFO similarity index 73% rename from tools/docker-compose/ansible_tower.egg-info/PKG-INFO rename to tools/docker-compose/ansible_awx.egg-info/PKG-INFO index 0d78373ace..eb633cdebb 100644 --- a/tools/docker-compose/ansible_tower.egg-info/PKG-INFO +++ b/tools/docker-compose/ansible_awx.egg-info/PKG-INFO @@ -1,12 +1,12 @@ Metadata-Version: 1.1 -Name: ansible-tower -Version: 3.0.0-0.devel -Summary: ansible-tower: API, UI and Task Engine for Ansible -Home-page: http://github.com/ansible/ansible-tower +Name: ansible-awx +Version: placeholder +Summary: ansible-awx: API, UI and Task Engine for Ansible +Home-page: http://github.com/ansible/ansible-awx Author: Ansible, Inc. Author-email: info@ansible.com License: Proprietary -Description: Ansible Tower provides a web-based user interface, REST API and task engine built on top of Ansible +Description: Ansible AWXprovides a web-based user interface, REST API and task engine built on top of Ansible Keywords: ansible Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable diff --git a/tools/docker-compose/ansible_tower.egg-info/SOURCES.txt b/tools/docker-compose/ansible_awx.egg-info/SOURCES.txt similarity index 100% rename from tools/docker-compose/ansible_tower.egg-info/SOURCES.txt rename to tools/docker-compose/ansible_awx.egg-info/SOURCES.txt diff --git a/tools/docker-compose/ansible_tower.egg-info/dependency_links.txt b/tools/docker-compose/ansible_awx.egg-info/dependency_links.txt similarity index 100% rename from tools/docker-compose/ansible_tower.egg-info/dependency_links.txt rename to tools/docker-compose/ansible_awx.egg-info/dependency_links.txt diff --git a/tools/docker-compose/ansible_tower.egg-info/entry_points.txt b/tools/docker-compose/ansible_awx.egg-info/entry_points.txt similarity index 100% rename from tools/docker-compose/ansible_tower.egg-info/entry_points.txt rename to tools/docker-compose/ansible_awx.egg-info/entry_points.txt diff --git a/tools/docker-compose/ansible_tower.egg-info/not-zip-safe b/tools/docker-compose/ansible_awx.egg-info/not-zip-safe similarity index 100% rename from tools/docker-compose/ansible_tower.egg-info/not-zip-safe rename to tools/docker-compose/ansible_awx.egg-info/not-zip-safe diff --git a/tools/docker-compose/ansible_tower.egg-info/top_level.txt b/tools/docker-compose/ansible_awx.egg-info/top_level.txt similarity index 100% rename from tools/docker-compose/ansible_tower.egg-info/top_level.txt rename to tools/docker-compose/ansible_awx.egg-info/top_level.txt diff --git a/tools/docker-compose/awx-manage b/tools/docker-compose/awx-manage index 1d728c5949..d4dff5664a 100755 --- a/tools/docker-compose/awx-manage +++ b/tools/docker-compose/awx-manage @@ -1,10 +1,10 @@ #!/venv/awx/bin/python -# EASY-INSTALL-ENTRY-SCRIPT: 'ansible-tower==3.0.0-0.devel','console_scripts','awx-manage' -__requires__ = 'ansible-tower==3.0.0-0.devel' +# EASY-INSTALL-ENTRY-SCRIPT: 'ansible-awx==placeholder','console_scripts','awx-manage' +__requires__ = 'ansible-awx==placeholder' import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.exit( - load_entry_point('ansible-tower==3.0.0-0.devel', 'console_scripts', 'awx-manage')() + load_entry_point('ansible-awx==placeholder', 'console_scripts', 'awx-manage')() ) diff --git a/tools/docker-compose/start_development.sh b/tools/docker-compose/start_development.sh index cfe38868a7..c1032bf848 100755 --- a/tools/docker-compose/start_development.sh +++ b/tools/docker-compose/start_development.sh @@ -21,8 +21,10 @@ else echo "Failed to find awx source tree, map your development tree volume" fi -cp -nR /tmp/ansible_tower.egg-info /awx_devel/ || true -cp /tmp/ansible-tower.egg-link /venv/awx/lib/python2.7/site-packages/ansible-tower.egg-link +cp -R /tmp/ansible_awx.egg-info /awx_devel/ || true +sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/ansible_awx.egg-info/PKG-INFO +sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /usr/local/bin/awx-manage +cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link ln -s /awx_devel/tools/rdb.py /venv/awx/lib/python2.7/site-packages/rdb.py || true yes | cp -rf /awx_devel/tools/docker-compose/supervisor.conf /supervisor.conf From f175fbba233a434a99d1215fb24acad3c655ae17 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 24 Jul 2017 09:38:36 -0400 Subject: [PATCH 002/342] Fix up some tower-manage -> awx-manage commands --- awx/api/templates/api/system_job_template_launch.md | 2 +- awx/main/tasks.py | 4 ++-- tools/sosreport/tower.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/api/templates/api/system_job_template_launch.md b/awx/api/templates/api/system_job_template_launch.md index 3a5d2d3b7a..95d0f6b378 100644 --- a/awx/api/templates/api/system_job_template_launch.md +++ b/awx/api/templates/api/system_job_template_launch.md @@ -4,7 +4,7 @@ Make a POST request to this resource to launch the system job template. Variables specified inside of the parameter `extra_vars` are passed to the system job task as command line parameters. These tasks can be ran manually -on the host system via the `tower-manage` command. +on the host system via the `awx-manage` command. For example on `cleanup_jobs` and `cleanup_activitystream`: diff --git a/awx/main/tasks.py b/awx/main/tasks.py index bae065f88d..5b02e694fb 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1810,7 +1810,7 @@ class RunInventoryUpdate(BaseTask): inventory = inventory_source.inventory # Piece together the initial command to run via. the shell. - args = ['tower-manage', 'inventory_import'] + args = ['awx-manage', 'inventory_import'] args.extend(['--inventory-id', str(inventory.pk)]) # Add appropriate arguments for overwrite if the inventory_update @@ -2116,7 +2116,7 @@ class RunSystemJob(BaseTask): model = SystemJob def build_args(self, system_job, **kwargs): - args = ['tower-manage', system_job.job_type] + args = ['awx-manage', system_job.job_type] try: json_vars = json.loads(system_job.extra_vars) if 'days' in json_vars and system_job.job_type != 'cleanup_facts': diff --git a/tools/sosreport/tower.py b/tools/sosreport/tower.py index 8b61626d1c..9ca0915509 100644 --- a/tools/sosreport/tower.py +++ b/tools/sosreport/tower.py @@ -6,7 +6,7 @@ from distutils.version import LooseVersion SOSREPORT_TOWER_COMMANDS = [ "ansible --version", # ansible core version - "tower-manage --version", # tower version + "awx-manage --version", # tower version "supervisorctl status", # tower process status "/var/lib/awx/venv/tower/bin/pip freeze", # pip package list "/var/lib/awx/venv/ansible/bin/pip freeze", # pip package list From 268a183b3bf54d3b967a4a8dc7275b4054a6f9d8 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 10:14:33 -0400 Subject: [PATCH 003/342] Fixed workflow maker dialog styling --- .../workflows/workflow-maker/workflow-maker.block.less | 9 +++++++++ .../workflows/workflow-maker/workflow-maker.directive.js | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 0506c07a47..5892f9403b 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -1,5 +1,14 @@ @import "./client/src/shared/branding/colors.default.less"; + +.WorkflowMaker-dialog { + padding: 0px; + margin-bottom: 20px; + + .ui-dialog-buttonpane, .ui-dialog-titlebar { + display:none; + } +} .WorkflowMaker-header { display: flex; height: 34px; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js index bfac9c1469..eee0a0fb1c 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js @@ -29,7 +29,7 @@ export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window', width: availableWidth > minimumWidth ? availableWidth : minimumWidth, height: availableHeight > minimumHeight ? availableHeight : minimumHeight, draggable: false, - dialogClass: 'SurveyMaker-dialog', + dialogClass: 'WorkflowMaker-dialog', position: ['center', 20], onClose: function() { $('#workflow-modal-dialog').empty(); From 72a7e55c71caa86712098ae5711e9021cf58c30b Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 11:10:05 -0400 Subject: [PATCH 004/342] Check for inventory id before trying to build a link to it in job results --- .../client/src/job-results/job-results.controller.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 176e302205..52bd109ad2 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -65,11 +65,16 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } } else if(key === 'inventory') { - if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') { - return '/#/inventories/smart/' + $scope.job.summary_fields.inventory.id; + if($scope.job.summary_fields.inventory && $scope.job.summary_fields.inventory.id) { + if($scope.job.summary_fields.inventory.kind && $scope.job.summary_fields.inventory.kind === 'smart') { + return '/#/inventories/smart/' + $scope.job.summary_fields.inventory.id; + } + else { + return '/#/inventories/inventory/' + $scope.job.summary_fields.inventory.id; + } } else { - return '/#/inventories/inventory/' + $scope.job.summary_fields.inventory.id; + return null; } } else { From 7d5e2625b7920c2454a2c283a44fd780d35be51a Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 11:16:27 -0400 Subject: [PATCH 005/342] Allow for new lines in login info block --- awx/ui/client/src/login/loginModal/loginMotalNotice.block.less | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less b/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less index e1aa42d397..df7de83de6 100644 --- a/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less +++ b/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less @@ -14,6 +14,7 @@ color: @login-notice-text; overflow-y: scroll; overflow-x: visible; + white-space: pre-wrap; } .LoginModalNotice-title { From 6d201c44d97a46ee6627a596c9d6bea0bffa6306 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 24 Jul 2017 11:36:11 -0400 Subject: [PATCH 006/342] fix a busted unit test re: tower -> awx --- awx/main/tests/unit/isolated/test_expect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/tests/unit/isolated/test_expect.py b/awx/main/tests/unit/isolated/test_expect.py index 8c3116896f..adf43d4bb9 100644 --- a/awx/main/tests/unit/isolated/test_expect.py +++ b/awx/main/tests/unit/isolated/test_expect.py @@ -272,7 +272,7 @@ def test_check_isolated_job(private_data_dir, rsa_key): '-e', '{"src": "%s"}' % private_data_dir, '-vvvvv' ], - '/tower_devel/awx/playbooks', mgr.management_env, mock.ANY, + '/awx_devel/awx/playbooks', mgr.management_env, mock.ANY, cancelled_callback=None, idle_timeout=0, job_timeout=0, From 9f11c008d21134d94b857a1e8887450f83c29729 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 21 Jul 2017 16:46:51 -0400 Subject: [PATCH 007/342] don't allow boolean credential type fields that specify `secret` secret doesn't really make sense for boolean values; they can't store sensitive content because they're just true|false see: https://github.com/ansible/ansible-tower/issues/6776 --- awx/main/fields.py | 2 +- awx/main/tests/functional/test_credential.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index f7953f5830..7e2c3edfaf 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -624,7 +624,7 @@ class CredentialTypeInputField(JSONSchemaField): # If no type is specified, default to string field['type'] = 'string' - for key in ('choices', 'multiline', 'format'): + for key in ('choices', 'multiline', 'format', 'secret',): if key in field and field['type'] != 'string': raise django_exceptions.ValidationError( _('%s not allowed for %s type (%s)' % (key, field['type'], field['id'])), diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py index 25b4504687..cdb54c2265 100644 --- a/awx/main/tests/functional/test_credential.py +++ b/awx/main/tests/functional/test_credential.py @@ -72,6 +72,7 @@ def test_cloud_kind_uniqueness(): ({'fields': [{'id': 'ssh_key', 'label': 'SSH Key', 'type': 'string', 'format': 'ssh_private_key'}]}, True), # noqa ({'fields': [{'id': 'flag', 'label': 'Some Flag', 'type': 'boolean'}]}, True), ({'fields': [{'id': 'flag', 'label': 'Some Flag', 'type': 'boolean', 'choices': ['a', 'b']}]}, False), + ({'fields': [{'id': 'flag', 'label': 'Some Flag', 'type': 'boolean', 'secret': True}]}, False), ({'fields': [{'id': 'certificate', 'label': 'Cert', 'multiline': True}]}, True), ({'fields': [{'id': 'certificate', 'label': 'Cert', 'multiline': True, 'type': 'boolean'}]}, False), # noqa ({'fields': [{'id': 'certificate', 'label': 'Cert', 'multiline': 'bad'}]}, False), # noqa From b0f5d2f82d276a6e871084cf82338580ee367789 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Fri, 21 Jul 2017 16:18:40 -0400 Subject: [PATCH 008/342] allow Job Templates to launch with *only* a vault credential see: https://github.com/ansible/ansible-tower/issues/7252 --- awx/api/serializers.py | 7 +++- awx/main/models/jobs.py | 6 ++- .../functional/api/test_job_runtime_params.py | 41 +++++++++++++++++++ .../multi-credential.directive.js | 4 +- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 62a52ae703..63e6025211 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2460,12 +2460,17 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO inventory = get_field_from_model_or_attrs('inventory') credential = get_field_from_model_or_attrs('credential') + vault_credential = get_field_from_model_or_attrs('vault_credential') project = get_field_from_model_or_attrs('project') prompting_error_message = _("Must either set a default value or ask to prompt on launch.") if project is None: raise serializers.ValidationError({'project': _("Job types 'run' and 'check' must have assigned a project.")}) - elif credential is None and not get_field_from_model_or_attrs('ask_credential_on_launch'): + elif all([ + credential is None, + vault_credential is None, + not get_field_from_model_or_attrs('ask_credential_on_launch'), + ]): raise serializers.ValidationError({'credential': prompting_error_message}) elif inventory is None and not get_field_from_model_or_attrs('ask_inventory_on_launch'): raise serializers.ValidationError({'inventory': prompting_error_message}) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index a32cb1dae8..34141765eb 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -314,10 +314,12 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour resources_needed_to_start.append('inventory') if not self.ask_inventory_on_launch: validation_errors['inventory'] = [_("Job Template must provide 'inventory' or allow prompting for it."),] - if self.credential is None: + if self.credential is None and self.vault_credential is None: resources_needed_to_start.append('credential') if not self.ask_credential_on_launch: validation_errors['credential'] = [_("Job Template must provide 'credential' or allow prompting for it."),] + elif self.credential is None and self.ask_credential_on_launch: + resources_needed_to_start.append('credential') # Job type dependent checks if self.project is None: @@ -695,7 +697,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin): if not super(Job, self).can_start: return False - if not (self.credential): + if not (self.credential) and not (self.vault_credential): return False return True diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index b75ad32672..4d7f1e3ecd 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -316,6 +316,47 @@ def test_job_launch_JT_enforces_unique_extra_credential_kinds(machine_credential assert validated is False +@pytest.mark.django_db +def test_job_launch_with_no_credentials(deploy_jobtemplate): + deploy_jobtemplate.credential = None + deploy_jobtemplate.vault_credential = None + serializer = JobLaunchSerializer( + instance=deploy_jobtemplate, data={}, + context={'obj': deploy_jobtemplate, 'data': {}, 'passwords': {}}) + validated = serializer.is_valid() + assert validated is False + assert serializer.errors['credential'] == ["Job Template 'credential' is missing or undefined."] + + +@pytest.mark.django_db +def test_job_launch_with_only_vault_credential(vault_credential, deploy_jobtemplate): + deploy_jobtemplate.credential = None + deploy_jobtemplate.vault_credential = vault_credential + serializer = JobLaunchSerializer( + instance=deploy_jobtemplate, data={}, + context={'obj': deploy_jobtemplate, 'data': {}, 'passwords': {}}) + validated = serializer.is_valid() + assert validated + + prompted_fields, ignored_fields = deploy_jobtemplate._accept_or_ignore_job_kwargs(**{}) + job_obj = deploy_jobtemplate.create_unified_job(**prompted_fields) + + assert job_obj.vault_credential.pk == vault_credential.pk + + +@pytest.mark.django_db +def test_job_launch_with_vault_credential_ask_for_machine(vault_credential, deploy_jobtemplate): + deploy_jobtemplate.credential = None + deploy_jobtemplate.ask_credential_on_launch = True + deploy_jobtemplate.vault_credential = vault_credential + serializer = JobLaunchSerializer( + instance=deploy_jobtemplate, data={}, + context={'obj': deploy_jobtemplate, 'data': {}, 'passwords': {}}) + validated = serializer.is_valid() + assert validated is False + assert serializer.errors['credential'] == ["Job Template 'credential' is missing or undefined."] + + @pytest.mark.django_db def test_job_launch_JT_with_default_vault_credential(machine_credential, vault_credential, deploy_jobtemplate): deploy_jobtemplate.credential = machine_credential diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js index f575c7fd44..a2cf5ea85d 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js @@ -21,6 +21,7 @@ export default ['templateUrl', '$compile', if (!$scope.selectedCredentials) { $scope.selectedCredentials = { machine: null, + vault: null, extra: [] }; } @@ -39,7 +40,8 @@ export default ['templateUrl', '$compile', } $scope.credentialNotPresent = !$scope.prompt && - $scope.selectedCredentials.machine === null; + $scope.selectedCredentials.machine === null && + $scope.selectedCredentials.vault === null; }); $scope.removeCredential = function(credToRemove) { From 6e3978fe664c074b031e4442142c4c5862548cce Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 24 Jul 2017 12:13:46 -0400 Subject: [PATCH 009/342] update TOWER_VENV_PATH setting to new awx location --- awx/settings/development.py | 2 +- awx/settings/production.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/settings/development.py b/awx/settings/development.py index f098833ba1..dcb20566ff 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -110,7 +110,7 @@ include(optional('/etc/tower/settings.py'), scope=locals()) include(optional('/etc/tower/conf.d/*.py'), scope=locals()) ANSIBLE_VENV_PATH = "/venv/ansible" -TOWER_VENV_PATH = "/venv/tower" +TOWER_VENV_PATH = "/venv/awx" # If any local_*.py files are present in awx/settings/, use them to override # default settings for development. If not present, we can still run using diff --git a/awx/settings/production.py b/awx/settings/production.py index 84179f643a..144ec6d5bd 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -46,7 +46,7 @@ SCHEDULE_METADATA_LOCATION = '/var/lib/awx/.tower_cycle' ANSIBLE_VENV_PATH = "/var/lib/awx/venv/ansible" # Tower base virtualenv paths and enablement -TOWER_VENV_PATH = "/var/lib/awx/venv/tower" +TOWER_VENV_PATH = "/var/lib/awx/venv/awx" AWX_ISOLATED_USERNAME = 'awx' From eaeff7e2906901dd7a757e85285006693cef7fa4 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 24 Jul 2017 12:23:54 -0400 Subject: [PATCH 010/342] rename setting TOWER_VENV_PATH to AWX_VENV_PATH --- awx/main/tasks.py | 4 ++-- awx/main/utils/common.py | 2 +- awx/settings/development.py | 2 +- awx/settings/production.py | 2 +- tox.ini | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 5b02e694fb..6a7b97a861 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -550,8 +550,8 @@ class BaseTask(Task): return env def add_tower_venv(self, env): - env['VIRTUAL_ENV'] = settings.TOWER_VENV_PATH - env['PATH'] = os.path.join(settings.TOWER_VENV_PATH, "bin") + ":" + env['PATH'] + env['VIRTUAL_ENV'] = settings.AWX_VENV_PATH + env['PATH'] = os.path.join(settings.AWX_VENV_PATH, "bin") + ":" + env['PATH'] return env def build_env(self, instance, **kwargs): diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index e2d2264045..a3b402a92a 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -695,7 +695,7 @@ def wrap_args_with_proot(args, cwd, **kwargs): show_paths = [cwd, kwargs['private_data_dir']] else: show_paths = [cwd] - show_paths.extend([settings.ANSIBLE_VENV_PATH, settings.TOWER_VENV_PATH]) + show_paths.extend([settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH]) show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or []) for path in sorted(set(show_paths)): if not os.path.exists(path): diff --git a/awx/settings/development.py b/awx/settings/development.py index dcb20566ff..1dc7bb8688 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -110,7 +110,7 @@ include(optional('/etc/tower/settings.py'), scope=locals()) include(optional('/etc/tower/conf.d/*.py'), scope=locals()) ANSIBLE_VENV_PATH = "/venv/ansible" -TOWER_VENV_PATH = "/venv/awx" +AWX_VENV_PATH = "/venv/awx" # If any local_*.py files are present in awx/settings/, use them to override # default settings for development. If not present, we can still run using diff --git a/awx/settings/production.py b/awx/settings/production.py index 144ec6d5bd..1d93ad43e6 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -46,7 +46,7 @@ SCHEDULE_METADATA_LOCATION = '/var/lib/awx/.tower_cycle' ANSIBLE_VENV_PATH = "/var/lib/awx/venv/ansible" # Tower base virtualenv paths and enablement -TOWER_VENV_PATH = "/var/lib/awx/venv/awx" +AWX_VENV_PATH = "/var/lib/awx/venv/awx" AWX_ISOLATED_USERNAME = 'awx' diff --git a/tox.ini b/tox.ini index e29c4c3cf4..53178e1a4b 100644 --- a/tox.ini +++ b/tox.ini @@ -17,7 +17,7 @@ setenv = HOME = {homedir} USERPROFILE = {homedir} ANSIBLE_VENV_PATH = {toxworkdir} - TOWER_VENV_PATH = {toxworkdir} + AWX_VENV_PATH = {toxworkdir} SKIP_SLOW_TESTS = True [testenv:api-lint] From 525490a9a08706d26d5841ef6aad562f40b79fda Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 24 Jul 2017 12:31:35 -0400 Subject: [PATCH 011/342] all dependent jobs must finish before starting job related to https://github.com/ansible/ansible-tower/issues/6570 https://github.com/ansible/ansible-tower/issues/6489 * Ensure that all jobs dependent on a job have finished (i.e. error, success, failed) before starting the dependent job. * Fixes a bug where a smaller set of dependent jobs to fail upon a update_on_launch job failing is chosen. * This fixes the bug of jobs starting before dependent Project Updates created via update on launch. This also fixes similar bugs associated with inventory updates. --- awx/main/models/inventory.py | 20 ++------ awx/main/models/jobs.py | 14 +----- awx/main/models/mixins.py | 46 ++++++++++++++++++- awx/main/models/projects.py | 8 ++-- awx/main/models/unified_jobs.py | 15 ++++-- awx/main/scheduler/__init__.py | 25 ++++++---- awx/main/tasks.py | 23 ++++++---- .../task_management/test_rampart_groups.py | 25 +++++++--- .../task_management/test_scheduler.py | 31 ++++++------- awx/main/tests/unit/models/test_inventory.py | 14 +----- 10 files changed, 133 insertions(+), 88 deletions(-) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index e3b71ee594..c3c587dd91 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -29,8 +29,7 @@ from awx.main.fields import ( from awx.main.managers import HostManager from awx.main.models.base import * # noqa from awx.main.models.unified_jobs import * # noqa -from awx.main.models.jobs import Job -from awx.main.models.mixins import ResourceMixin +from awx.main.models.mixins import ResourceMixin, TaskManagerInventoryUpdateMixin from awx.main.models.notifications import ( NotificationTemplate, JobNotificationMixin, @@ -1391,7 +1390,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): return source -class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): +class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin, TaskManagerInventoryUpdateMixin): ''' Internal job for tracking inventory updates from external sources. ''' @@ -1508,20 +1507,9 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): return self.global_instance_groups return selected_groups - def _build_job_explanation(self): - if not self.job_explanation: - return 'Previous Task Canceled: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \ - (self.model_to_str(), self.name, self.id) - return None - - def get_dependent_jobs(self): - return Job.objects.filter(dependent_jobs__in=[self.id]) - - def cancel(self, job_explanation=None): - - res = super(InventoryUpdate, self).cancel(job_explanation=job_explanation) + def cancel(self, job_explanation=None, is_chain=False): + res = super(InventoryUpdate, self).cancel(job_explanation=job_explanation, is_chain=is_chain) if res: - map(lambda x: x.cancel(job_explanation=self._build_job_explanation()), self.get_dependent_jobs()) if self.launch_type != 'scm' and self.source_project_update: self.source_project_update.cancel(job_explanation=job_explanation) return res diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index a32cb1dae8..7a95252c54 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -38,7 +38,7 @@ from awx.main.utils import ( parse_yaml_or_json, ) from awx.main.fields import ImplicitRoleField -from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin +from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin, TaskManagerJobMixin from awx.main.models.base import PERM_INVENTORY_SCAN from awx.main.fields import JSONField @@ -449,7 +449,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates)) -class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin): +class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskManagerJobMixin): ''' A job applies a project (with playbook) to an inventory source with a given credential. It represents a single invocation of ansible-playbook with the @@ -709,16 +709,6 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin): def get_notification_friendly_name(self): return "Job" - ''' - Canceling a job also cancels the implicit project update with launch_type - run. - ''' - def cancel(self, job_explanation=None): - res = super(Job, self).cancel(job_explanation=job_explanation) - if self.project_update: - self.project_update.cancel(job_explanation=job_explanation) - return res - @property def memcached_fact_key(self): return '{}'.format(self.inventory.id) diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index f767586eff..5ec4a4337d 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -16,7 +16,9 @@ from awx.main.utils import parse_yaml_or_json from awx.main.fields import JSONField -__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin'] +__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin', + 'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin', + 'TaskManagerInventoryUpdateMixin',] class ResourceMixin(models.Model): @@ -249,3 +251,45 @@ class SurveyJobMixin(models.Model): return json.dumps(extra_vars) else: return self.extra_vars + + +class TaskManagerUnifiedJobMixin(models.Model): + class Meta: + abstract = True + + def get_jobs_fail_chain(self): + return [] + + def dependent_jobs_finished(self): + return True + + +class TaskManagerJobMixin(TaskManagerUnifiedJobMixin): + class Meta: + abstract = True + + def dependent_jobs_finished(self): + for j in self.dependent_jobs.all(): + if j.status in ['pending', 'waiting', 'running']: + return False + return True + + +class TaskManagerUpdateOnLaunchMixin(TaskManagerUnifiedJobMixin): + class Meta: + abstract = True + + def get_jobs_fail_chain(self): + return list(self.dependent_jobs.all()) + + +class TaskManagerProjectUpdateMixin(TaskManagerUpdateOnLaunchMixin): + class Meta: + abstract = True + + +class TaskManagerInventoryUpdateMixin(TaskManagerUpdateOnLaunchMixin): + class Meta: + abstract = True + + diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index bc0d4d16ed..827b131aed 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -23,7 +23,7 @@ from awx.main.models.notifications import ( JobNotificationMixin, ) from awx.main.models.unified_jobs import * # noqa -from awx.main.models.mixins import ResourceMixin +from awx.main.models.mixins import ResourceMixin, TaskManagerProjectUpdateMixin from awx.main.utils import update_scm_url from awx.main.utils.ansible import skip_directory, could_be_inventory, could_be_playbook from awx.main.fields import ImplicitRoleField @@ -430,7 +430,7 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin): return reverse('api:project_detail', kwargs={'pk': self.pk}, request=request) -class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin): +class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManagerProjectUpdateMixin): ''' Internal job for tracking project updates from SCM. ''' @@ -512,8 +512,8 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin): update_fields.append('scm_delete_on_next_update') parent_instance.save(update_fields=update_fields) - def cancel(self, job_explanation=None): - res = super(ProjectUpdate, self).cancel(job_explanation=job_explanation) + def cancel(self, job_explanation=None, is_chain=False): + res = super(ProjectUpdate, self).cancel(job_explanation=job_explanation, is_chain=is_chain) if res and self.launch_type != 'sync': for inv_src in self.scm_inventory_updates.filter(status='running'): inv_src.cancel(job_explanation='Source project update `{}` was canceled.'.format(self.name)) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index a763239d15..dadb9d2b65 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -30,7 +30,7 @@ from djcelery.models import TaskMeta # AWX from awx.main.models.base import * # noqa from awx.main.models.schedules import Schedule -from awx.main.models.mixins import ResourceMixin +from awx.main.models.mixins import ResourceMixin, TaskManagerUnifiedJobMixin from awx.main.utils import ( decrypt_field, _inventory_updates, copy_model_by_class, copy_m2m_relationships @@ -414,7 +414,7 @@ class UnifiedJobTypeStringMixin(object): return UnifiedJobTypeStringMixin._camel_to_underscore(self.__class__.__name__) -class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin): +class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin): ''' Concrete base class for unified job run by the task engine. ''' @@ -1058,8 +1058,17 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique if settings.DEBUG: raise - def cancel(self, job_explanation=None): + def _build_job_explanation(self): + if not self.job_explanation: + return 'Previous Task Canceled: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \ + (self.model_to_str(), self.name, self.id) + return None + + def cancel(self, job_explanation=None, is_chain=False): if self.can_cancel: + if not is_chain: + map(lambda x: x.cancel(job_explanation=self._build_job_explanation(), is_chain=True), self.get_jobs_fail_chain()) + if not self.cancel_flag: self.cancel_flag = True cancel_fields = ['cancel_flag'] diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 8e0e1fc340..3e83c759d8 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -47,6 +47,10 @@ class TaskManager(): for g in self.graph: if self.graph[g]['graph'].is_job_blocked(task): return True + + if not task.dependent_jobs_finished(): + return True + return False def get_tasks(self, status_list=('pending', 'waiting', 'running')): @@ -262,11 +266,12 @@ class TaskManager(): return inventory_task def capture_chain_failure_dependencies(self, task, dependencies): - for dep in dependencies: - with disable_activity_stream(): - logger.info('Adding unified job {} to list of dependencies of {}.'.format(task.id, dep.id)) - dep.dependent_jobs.add(task.id) - dep.save() + with disable_activity_stream(): + task.dependent_jobs.add(*dependencies) + + for dep in dependencies: + # Add task + all deps except self + dep.dependent_jobs.add(*([task] + filter(lambda d: d != dep, dependencies))) def should_update_inventory_source(self, job, inventory_source): now = tz_now() @@ -342,7 +347,9 @@ class TaskManager(): if self.should_update_inventory_source(task, inventory_source): inventory_task = self.create_inventory_update(task, inventory_source) dependencies.append(inventory_task) - self.capture_chain_failure_dependencies(task, dependencies) + + if len(dependencies) > 0: + self.capture_chain_failure_dependencies(task, dependencies) return dependencies def process_dependencies(self, dependent_task, dependency_tasks): @@ -359,7 +366,9 @@ class TaskManager(): if not self.would_exceed_capacity(task, rampart_group.name): logger.debug("Starting dependent task {} in group {}".format(task, rampart_group.name)) self.graph[rampart_group.name]['graph'].add_job(task) - self.start_task(task, rampart_group, dependency_tasks) + tasks_to_fail = filter(lambda t: t != task, dependency_tasks) + tasks_to_fail += [dependent_task] + self.start_task(task, rampart_group, tasks_to_fail) found_acceptable_queue = True if not found_acceptable_queue: logger.debug("Dependent task {} couldn't be scheduled on graph, waiting for next cycle".format(task)) @@ -379,7 +388,7 @@ class TaskManager(): if not self.would_exceed_capacity(task, rampart_group.name): logger.debug("Starting task {} in group {}".format(task, rampart_group.name)) self.graph[rampart_group.name]['graph'].add_job(task) - self.start_task(task, rampart_group) + self.start_task(task, rampart_group, task.get_jobs_fail_chain()) found_acceptable_queue = True break if not found_acceptable_queue: diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 5b02e694fb..99d1e86af6 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -314,7 +314,7 @@ def handle_work_error(self, task_id, subtasks=None): first_instance = instance first_instance_type = each_task['type'] - if instance.celery_task_id != task_id: + if instance.celery_task_id != task_id and not instance.cancel_flag: instance.status = 'failed' instance.failed = True if not instance.job_explanation: @@ -1398,11 +1398,12 @@ class RunProjectUpdate(BaseTask): def get_stdout_handle(self, instance): stdout_handle = super(RunProjectUpdate, self).get_stdout_handle(instance) + pk = instance.pk def raw_callback(data): - instance_actual = ProjectUpdate.objects.get(pk=instance.pk) - instance_actual.result_stdout_text += data - instance_actual.save() + instance_actual = self.update_model(pk) + result_stdout_text = instance_actual.result_stdout_text + data + self.update_model(pk, result_stdout_text=result_stdout_text) return OutputEventFilter(stdout_handle, raw_callback=raw_callback) def _update_dependent_inventories(self, project_update, dependent_inventory_sources): @@ -1872,11 +1873,12 @@ class RunInventoryUpdate(BaseTask): def get_stdout_handle(self, instance): stdout_handle = super(RunInventoryUpdate, self).get_stdout_handle(instance) + pk = instance.pk def raw_callback(data): - instance_actual = InventoryUpdate.objects.get(pk=instance.pk) - instance_actual.result_stdout_text += data - instance_actual.save() + instance_actual = self.update_model(pk) + result_stdout_text = instance_actual.result_stdout_text + data + self.update_model(pk, result_stdout_text=result_stdout_text) return OutputEventFilter(stdout_handle, raw_callback=raw_callback) def build_cwd(self, inventory_update, **kwargs): @@ -2138,11 +2140,12 @@ class RunSystemJob(BaseTask): def get_stdout_handle(self, instance): stdout_handle = super(RunSystemJob, self).get_stdout_handle(instance) + pk = instance.pk def raw_callback(data): - instance_actual = SystemJob.objects.get(pk=instance.pk) - instance_actual.result_stdout_text += data - instance_actual.save() + instance_actual = self.update_model(pk) + result_stdout_text = instance_actual.result_stdout_text + data + self.update_model(pk, result_stdout_text=result_stdout_text) return OutputEventFilter(stdout_handle, raw_callback=raw_callback) def build_env(self, instance, **kwargs): diff --git a/awx/main/tests/functional/task_management/test_rampart_groups.py b/awx/main/tests/functional/task_management/test_rampart_groups.py index 3b5622d7fd..c81556e091 100644 --- a/awx/main/tests/functional/task_management/test_rampart_groups.py +++ b/awx/main/tests/functional/task_management/test_rampart_groups.py @@ -29,7 +29,7 @@ def test_multi_group_basic_job_launch(instance_factory, default_instance_group, mock_task_impact.return_value = 500 with mocker.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_has_calls([mock.call(j1, ig1), mock.call(j2, ig2)]) + TaskManager.start_task.assert_has_calls([mock.call(j1, ig1, []), mock.call(j2, ig2, [])]) @@ -63,13 +63,26 @@ def test_multi_group_with_shared_dependency(instance_factory, default_instance_g with mocker.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() pu = p.project_updates.first() - TaskManager.start_task.assert_called_once_with(pu, default_instance_group, [pu]) + TaskManager.start_task.assert_called_once_with(pu, default_instance_group, [j1]) pu.finished = pu.created + timedelta(seconds=1) pu.status = "successful" pu.save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_has_calls([mock.call(j1, ig1), mock.call(j2, ig2)]) + TaskManager.start_task.assert_called_once_with(j1, ig1, []) + j1.finished = j1.created + timedelta(seconds=2) + j1.status = "successful" + j1.save() + with mock.patch("awx.main.scheduler.TaskManager.start_task"): + TaskManager().schedule() + pu = p.project_updates.last() + TaskManager.start_task.assert_called_once_with(pu, default_instance_group, [j2]) + pu.finished = pu.created + timedelta(seconds=1) + pu.status = "successful" + pu.save() + with mock.patch("awx.main.scheduler.TaskManager.start_task"): + TaskManager().schedule() + TaskManager.start_task.assert_called_once_with(j2, ig2, []) @pytest.mark.django_db @@ -114,8 +127,8 @@ def test_overcapacity_blocking_other_groups_unaffected(instance_factory, default mock_task_impact.return_value = 500 with mock.patch.object(TaskManager, "start_task", wraps=tm.start_task) as mock_job: tm.schedule() - mock_job.assert_has_calls([mock.call(j1, ig1), mock.call(j1_1, ig1), - mock.call(j2, ig2)]) + mock_job.assert_has_calls([mock.call(j1, ig1, []), mock.call(j1_1, ig1, []), + mock.call(j2, ig2, [])]) assert mock_job.call_count == 3 @@ -146,5 +159,5 @@ def test_failover_group_run(instance_factory, default_instance_group, mocker, mock_task_impact.return_value = 500 with mock.patch.object(TaskManager, "start_task", wraps=tm.start_task) as mock_job: tm.schedule() - mock_job.assert_has_calls([mock.call(j1, ig1), mock.call(j1_1, ig2)]) + mock_job.assert_has_calls([mock.call(j1, ig1, []), mock.call(j1_1, ig2, [])]) assert mock_job.call_count == 2 diff --git a/awx/main/tests/functional/task_management/test_scheduler.py b/awx/main/tests/functional/task_management/test_scheduler.py index 15646dfe54..cb1d689577 100644 --- a/awx/main/tests/functional/task_management/test_scheduler.py +++ b/awx/main/tests/functional/task_management/test_scheduler.py @@ -17,8 +17,7 @@ def test_single_job_scheduler_launch(default_instance_group, job_template_factor j.save() with mocker.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - assert TaskManager.start_task.called - assert TaskManager.start_task.call_args == ((j, default_instance_group),) + TaskManager.start_task.assert_called_once_with(j, default_instance_group, []) @pytest.mark.django_db @@ -34,12 +33,12 @@ def test_single_jt_multi_job_launch_blocks_last(default_instance_group, job_temp j2.save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j1, default_instance_group) + TaskManager.start_task.assert_called_once_with(j1, default_instance_group, []) j1.status = "successful" j1.save() with mocker.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j2, default_instance_group) + TaskManager.start_task.assert_called_once_with(j2, default_instance_group, []) @pytest.mark.django_db @@ -60,8 +59,8 @@ def test_single_jt_multi_job_launch_allow_simul_allowed(default_instance_group, j2.save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_has_calls([mock.call(j1, default_instance_group), - mock.call(j2, default_instance_group)]) + TaskManager.start_task.assert_has_calls([mock.call(j1, default_instance_group, []), + mock.call(j2, default_instance_group, [])]) @pytest.mark.django_db @@ -83,12 +82,12 @@ def test_multi_jt_capacity_blocking(default_instance_group, job_template_factory mock_task_impact.return_value = 500 with mock.patch.object(TaskManager, "start_task", wraps=tm.start_task) as mock_job: tm.schedule() - mock_job.assert_called_once_with(j1, default_instance_group) + mock_job.assert_called_once_with(j1, default_instance_group, []) j1.status = "successful" j1.save() with mock.patch.object(TaskManager, "start_task", wraps=tm.start_task) as mock_job: tm.schedule() - mock_job.assert_called_once_with(j2, default_instance_group) + mock_job.assert_called_once_with(j2, default_instance_group, []) @@ -113,12 +112,12 @@ def test_single_job_dependencies_project_launch(default_instance_group, job_temp mock_pu.assert_called_once_with(j) pu = [x for x in p.project_updates.all()] assert len(pu) == 1 - TaskManager.start_task.assert_called_once_with(pu[0], default_instance_group, [pu[0]]) + TaskManager.start_task.assert_called_once_with(pu[0], default_instance_group, [j]) pu[0].status = "successful" pu[0].save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j, default_instance_group) + TaskManager.start_task.assert_called_once_with(j, default_instance_group, []) @pytest.mark.django_db @@ -143,12 +142,12 @@ def test_single_job_dependencies_inventory_update_launch(default_instance_group, mock_iu.assert_called_once_with(j, ii) iu = [x for x in ii.inventory_updates.all()] assert len(iu) == 1 - TaskManager.start_task.assert_called_once_with(iu[0], default_instance_group, [iu[0]]) + TaskManager.start_task.assert_called_once_with(iu[0], default_instance_group, [j]) iu[0].status = "successful" iu[0].save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j, default_instance_group) + TaskManager.start_task.assert_called_once_with(j, default_instance_group, []) @pytest.mark.django_db @@ -181,8 +180,8 @@ def test_shared_dependencies_launch(default_instance_group, job_template_factory TaskManager().schedule() pu = p.project_updates.first() iu = ii.inventory_updates.first() - TaskManager.start_task.assert_has_calls([mock.call(pu, default_instance_group, [pu, iu]), - mock.call(iu, default_instance_group, [pu, iu])]) + TaskManager.start_task.assert_has_calls([mock.call(pu, default_instance_group, [iu, j1]), + mock.call(iu, default_instance_group, [pu, j1])]) pu.status = "successful" pu.finished = pu.created + timedelta(seconds=1) pu.save() @@ -191,12 +190,12 @@ def test_shared_dependencies_launch(default_instance_group, job_template_factory iu.save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j1, default_instance_group) + TaskManager.start_task.assert_called_once_with(j1, default_instance_group, []) j1.status = "successful" j1.save() with mock.patch("awx.main.scheduler.TaskManager.start_task"): TaskManager().schedule() - TaskManager.start_task.assert_called_once_with(j2, default_instance_group) + TaskManager.start_task.assert_called_once_with(j2, default_instance_group, []) pu = [x for x in p.project_updates.all()] iu = [x for x in ii.inventory_updates.all()] assert len(pu) == 1 diff --git a/awx/main/tests/unit/models/test_inventory.py b/awx/main/tests/unit/models/test_inventory.py index 4f0d5eddd8..0fa390bceb 100644 --- a/awx/main/tests/unit/models/test_inventory.py +++ b/awx/main/tests/unit/models/test_inventory.py @@ -7,33 +7,23 @@ from django.core.exceptions import ValidationError from awx.main.models import ( UnifiedJob, InventoryUpdate, - Job, Inventory, Credential, CredentialType, ) -@pytest.fixture -def dependent_job(mocker): - j = Job(id=3, name='I_am_a_job') - j.cancel = mocker.MagicMock(return_value=True) - return [j] - - -def test_cancel(mocker, dependent_job): +def test_cancel(mocker): with mock.patch.object(UnifiedJob, 'cancel', return_value=True) as parent_cancel: iu = InventoryUpdate() - iu.get_dependent_jobs = mocker.MagicMock(return_value=dependent_job) iu.save = mocker.MagicMock() build_job_explanation_mock = mocker.MagicMock() iu._build_job_explanation = mocker.MagicMock(return_value=build_job_explanation_mock) iu.cancel() - parent_cancel.assert_called_with(job_explanation=None) - dependent_job[0].cancel.assert_called_with(job_explanation=build_job_explanation_mock) + parent_cancel.assert_called_with(is_chain=False, job_explanation=None) def test__build_job_explanation(): From 1a706b615476be3fa8513ebb3e0b46d0a2792e8e Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 24 Jul 2017 12:35:21 -0400 Subject: [PATCH 012/342] Commands to generate a python wheel for packaging --- Makefile | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index e831b00c91..a36cfee834 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ endif GIT_DATE := $(shell git log -n 1 --format="%ai") DATE := $(shell date -u +%Y%m%d%H%M) -NAME ?= awx +NAME ?= ansible_awx GIT_REMOTE_URL = $(shell git config --get remote.origin.url) ifeq ($(OFFICIAL),yes) RELEASE ?= 1 @@ -56,13 +56,17 @@ endif ifeq ($(OFFICIAL),yes) SETUP_TAR_NAME=$(NAME)-setup-$(RELEASE_VERSION) SDIST_TAR_NAME=$(NAME)-$(RELEASE_VERSION) + WHEEL_NAME=$(NAME)-$(RELEASE_VERSION) else SETUP_TAR_NAME=$(NAME)-setup-$(RELEASE_VERSION)-$(RELEASE) SDIST_TAR_NAME=$(NAME)-$(RELEASE_VERSION)-$(RELEASE) + WHEEL_NAME=$(NAME)-$(RELEASE_VERSION)_$(RELEASE) endif SDIST_COMMAND ?= sdist +WHEEL_COMMAND ?= bdist_wheel SDIST_TAR_FILE ?= $(SDIST_TAR_NAME).tar.gz +WHEEL_FILE ?= $(WHEEL_NAME)-py2-none-any.whl SETUP_TAR_FILE=$(SETUP_TAR_NAME).tar.gz SETUP_TAR_LINK=$(NAME)-setup-latest.tar.gz @@ -534,8 +538,8 @@ release_build: dist/$(SDIST_TAR_FILE): ui-release BUILD="$(BUILD)" $(PYTHON) setup.py $(SDIST_COMMAND) -dist/ansible-tower.tar.gz: ui-release - OFFICIAL="yes" $(PYTHON) setup.py sdist +dist/$(WHEEL_FILE): ui-release + BUILD="$(BUILD)" $(PYTHON) setup.py $(WHEEL_COMMAND) sdist: dist/$(SDIST_TAR_FILE) @echo "#############################################" @@ -543,6 +547,12 @@ sdist: dist/$(SDIST_TAR_FILE) @echo dist/$(SDIST_TAR_FILE) @echo "#############################################" +wheel: dist/$(WHEEL_FILE) + @echo "#############################################" + @echo "Artifacts:" + @echo dist/$(WHEEL_FILE) + @echo "#############################################" + # Build setup bundle tarball setup-bundle-build: mkdir -p $@ From e3450411730d1a02de12fb791750ab51419f0280 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Mon, 24 Jul 2017 12:59:41 -0400 Subject: [PATCH 013/342] Fixup prompt check for pmrun usage --- awx/main/tasks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 6a7b97a861..e86e4a915a 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1121,6 +1121,8 @@ class RunJob(BaseTask): d[re.compile(r'SU password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'PBRUN password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'pbrun password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'PMRUN password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'pmrun password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'PFEXEC password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'pfexec password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'RUNAS password.*:\s*?$', re.M)] = 'become_password' @@ -2070,6 +2072,8 @@ class RunAdHocCommand(BaseTask): d[re.compile(r'SU password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'PBRUN password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'pbrun password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'PMRUN password.*:\s*?$', re.M)] = 'become_password' + d[re.compile(r'pmrun password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'PFEXEC password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'pfexec password.*:\s*?$', re.M)] = 'become_password' d[re.compile(r'RUNAS password.*:\s*?$', re.M)] = 'become_password' From eaddafc920a9da82a85e04240943a0c3b118946f Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 24 Jul 2017 10:48:03 -0400 Subject: [PATCH 014/342] allow smart inventory to be created related to https://github.com/ansible/ansible-tower/issues/7261 --- awx/main/models/inventory.py | 2 +- awx/main/tests/unit/models/test_inventory.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index e3b71ee594..0e078615c3 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -370,7 +370,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): return self.groups.exclude(parents__pk__in=group_pks).distinct() def clean_insights_credential(self): - if self.kind == 'smart': + if self.kind == 'smart' and self.insights_credential: raise ValidationError(_("Assignment not allowed for Smart Inventory")) if self.insights_credential and self.insights_credential.credential_type.kind != 'insights': raise ValidationError(_("Credential kind must be 'insights'.")) diff --git a/awx/main/tests/unit/models/test_inventory.py b/awx/main/tests/unit/models/test_inventory.py index 4f0d5eddd8..79d77f247f 100644 --- a/awx/main/tests/unit/models/test_inventory.py +++ b/awx/main/tests/unit/models/test_inventory.py @@ -64,6 +64,12 @@ def test_invalid_clean_insights_credential(): assert json.dumps(str(e.value)) == json.dumps(str([u"Credential kind must be 'insights'."])) +def test_valid_kind_clean_insights_credential(): + inv = Inventory(kind='smart') + + inv.clean_insights_credential() + + def test_invalid_kind_clean_insights_credential(): cred_type = CredentialType.defaults['insights']() insights_cred = Credential(credential_type=cred_type) From ec212db83b0b507ae8f218fd83dff512c4518322 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 24 Jul 2017 13:48:20 -0400 Subject: [PATCH 015/342] fix api docs sample host_filter queries --- awx/api/templates/api/host_list.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/awx/api/templates/api/host_list.md b/awx/api/templates/api/host_list.md index 5ec42b6c72..d8e68c5930 100644 --- a/awx/api/templates/api/host_list.md +++ b/awx/api/templates/api/host_list.md @@ -1,15 +1,15 @@ {% include "api/list_api_view.md" %} -`host_filter` is available on this endpoint. The filter supports: relational queries, `AND` `OR` boolean logic, as well as expression grouping via `()`. +`host_filter` is available on this endpoint. The filter supports: relational queries, `and` `or` boolean logic, as well as expression grouping via `()`. ?host_filter=name=my_host - ?host_filter=name="my host" OR name=my_host + ?host_filter=name="my host" or name=my_host ?host_filter=groups__name="my group" - ?host_filter=name=my_host AND groups__name="my group" - ?host_filter=name=my_host AND groups__name="my group" - ?host_filter=(name=my_host AND groups__name="my group") OR (name=my_host2 AND groups__name=my_group2) + ?host_filter=name=my_host and groups__name="my group" + ?host_filter=name=my_host and groups__name="my group" + ?host_filter=(name=my_host and groups__name="my group") or (name=my_host2 and groups__name=my_group2) `host_filter` can also be used to query JSON data in the related `ansible_facts`. `__` may be used to traverse JSON dictionaries. `[]` may be used to traverse JSON arrays. ?host_filter=ansible_facts__ansible_processor_vcpus=8 - ?host_filter=ansible_facts__ansible_processor_vcpus=8 AND name="my_host" AND ansible_facts__ansible_lo__ipv6[]__scope=host + ?host_filter=ansible_facts__ansible_processor_vcpus=8 and name="my_host" and ansible_facts__ansible_lo__ipv6[]__scope=host From 4e5090f28ce3acbf841bea9d864776a16fac0f74 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 13:42:38 -0400 Subject: [PATCH 016/342] xss tooltip vulnerability fixes --- .../rbac-selected-list.directive.js | 40 ++++++++++++++++++- .../rbac-multiselect-list.directive.js | 2 - .../credential-types/credential-types.list.js | 2 +- .../src/credentials/credentials.list.js | 2 +- .../inventories/inventory.list.js | 2 +- .../inventory-scripts.list.js | 2 +- .../management-jobs/management-jobs.list.js | 2 +- .../notificationTemplates.list.js | 2 +- .../list/organizations-list.partial.html | 4 +- .../src/organizations/organizations.list.js | 2 +- .../src/partials/survey-maker-modal.html | 2 +- .../portal-mode/portal-job-templates.list.js | 2 +- awx/ui/client/src/projects/projects.list.js | 2 +- awx/ui/client/src/teams/teams.list.js | 2 +- awx/ui/client/src/templates/templates.list.js | 2 +- 15 files changed, 52 insertions(+), 18 deletions(-) diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js index 423db0eceb..6a3e1ba13e 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js @@ -7,10 +7,10 @@ /* jshint unused: vars */ export default ['$compile', 'i18n', 'generateList', 'ProjectList', 'TemplateList', 'InventoryList', 'CredentialList', - 'OrganizationList', + 'OrganizationList', '$window', function($compile, i18n, generateList, ProjectList, TemplateList, InventoryList, CredentialList, - OrganizationList) { + OrganizationList, $window) { return { restrict: 'E', scope: { @@ -60,6 +60,7 @@ export default ['$compile', 'i18n', 'generateList', name: list.fields.name }; list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.name.ngClick = 'linkoutResource("job_template", job_template)'; break; case 'workflow_templates': list.name = 'workflow_job_templates'; @@ -68,6 +69,7 @@ export default ['$compile', 'i18n', 'generateList', name: list.fields.name }; list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + list.fields.name.ngClick = 'linkoutResource("workflow_job_template", workflow_job_template)'; break; case 'credentials': case 'organizations': @@ -126,6 +128,40 @@ export default ['$compile', 'i18n', 'generateList', multiselect_scope[type][deselectedIdx].isSelected = false; }; + scope.linkoutResource = function(type, resource) { + + let url; + + switch(type){ + case 'project': + url = "/#/projects/" + resource.id; + break; + case 'inventory': + url = resource.kind && resource.kind === "smart" ? "/#/inventories/smart/" + resource.id : "/#/inventories/inventory/" + resource.id; + break; + case 'job_template': + url = "/#/templates/job_template/" + resource.id; + break; + case 'workflow_job_template': + url = "/#/templates/workflow_job_template/" + resource.id; + break; + case 'user': + url = "/#/users/" + resource.id; + break; + case 'team': + url = "/#/teams/" + resource.id; + break; + case 'organization': + url = "/#/organizations/" + resource.id; + break; + case 'credential': + url = "/#/credentials/" + resource.id; + break; + } + + $window.open(url,'_blank'); + }; + element.append(list_html); $compile(element.contents())(scope); } diff --git a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js index 02017f4c7f..1c280970b7 100644 --- a/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js +++ b/awx/ui/client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js @@ -71,7 +71,6 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL }; list.fields.name.ngClick = 'linkoutResource("job_template", job_template)'; list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; - list.fields.name.ngHref = '#/templates/job_template/{{job_template.id}}'; break; case 'WorkflowTemplates': @@ -83,7 +82,6 @@ export default ['addPermissionsTeamsList', 'addPermissionsUsersList', 'TemplateL }; list.fields.name.ngClick = 'linkoutResource("workflow_job_template", workflow_template)'; list.fields.name.columnClass = 'col-md-6 col-sm-6 col-xs-11'; - list.fields.name.ngHref = '#/templates/workflow_job_template/{{workflow_template.id}}'; break; case 'Users': list.fields = { diff --git a/awx/ui/client/src/credential-types/credential-types.list.js b/awx/ui/client/src/credential-types/credential-types.list.js index a32be91ece..2e70ad9280 100644 --- a/awx/ui/client/src/credential-types/credential-types.list.js +++ b/awx/ui/client/src/credential-types/credential-types.list.js @@ -21,7 +21,7 @@ export default ['i18n', function(i18n){ label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', modalColumnClass: 'col-md-8', - awToolTip: '{{credential_type.description}}', + awToolTip: '{{credential_type.description | sanitize}}', dataPlacement: 'top' }, kind: { diff --git a/awx/ui/client/src/credentials/credentials.list.js b/awx/ui/client/src/credentials/credentials.list.js index 2ca40d2840..36f87541a7 100644 --- a/awx/ui/client/src/credentials/credentials.list.js +++ b/awx/ui/client/src/credentials/credentials.list.js @@ -26,7 +26,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', modalColumnClass: 'col-md-12', - awToolTip: '{{credential.description}}', + awToolTip: '{{credential.description | sanitize}}', dataPlacement: 'top' }, kind: { diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js index f1175316f4..233dcaa7f9 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js +++ b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js @@ -47,7 +47,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: 'col-md-4 col-sm-3 col-xs-6 List-staticColumnAdjacent', modalColumnClass: 'col-md-12', - awToolTip: "{{ inventory.description }}", + awToolTip: "{{ inventory.description | sanitize }}", awTipPlacement: "top", ngClick: 'editInventory(inventory)' }, diff --git a/awx/ui/client/src/inventory-scripts/inventory-scripts.list.js b/awx/ui/client/src/inventory-scripts/inventory-scripts.list.js index 6c2a9a775b..d81977d49d 100644 --- a/awx/ui/client/src/inventory-scripts/inventory-scripts.list.js +++ b/awx/ui/client/src/inventory-scripts/inventory-scripts.list.js @@ -20,7 +20,7 @@ export default ['i18n', function(i18n){ label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', modalColumnClass: 'col-md-8', - awToolTip: '{{inventory_script.description}}', + awToolTip: '{{inventory_script.description | sanitize}}', dataPlacement: 'top' }, organization: { diff --git a/awx/ui/client/src/management-jobs/management-jobs.list.js b/awx/ui/client/src/management-jobs/management-jobs.list.js index 22474d0461..4d0b1eb573 100644 --- a/awx/ui/client/src/management-jobs/management-jobs.list.js +++ b/awx/ui/client/src/management-jobs/management-jobs.list.js @@ -16,7 +16,7 @@ export default function(){ name: { label: 'Name', columnClass: 'col-sm-4 col-xs-4', - awToolTip: '{{configure_job.description}}', + awToolTip: '{{configure_job.description | sanitize}}', dataPlacement: 'top' } }, diff --git a/awx/ui/client/src/notifications/notificationTemplates.list.js b/awx/ui/client/src/notifications/notificationTemplates.list.js index c90b93db5c..fc0b3dd484 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.list.js +++ b/awx/ui/client/src/notifications/notificationTemplates.list.js @@ -32,7 +32,7 @@ export default ['i18n', function(i18n){ label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', linkTo: '/#/notification_templates/{{notification_template.id}}', - awToolTip: '{{notification_template.description}}', + awToolTip: '{{notification_template.description | sanitize}}', dataPlacement: 'top' }, notification_type: { diff --git a/awx/ui/client/src/organizations/list/organizations-list.partial.html b/awx/ui/client/src/organizations/list/organizations-list.partial.html index d5c61e4370..3180fdc8d8 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.partial.html +++ b/awx/ui/client/src/organizations/list/organizations-list.partial.html @@ -52,9 +52,9 @@ ng-repeat="card in orgCards track by card.id">

- {{ card.name }} + {{ card.name | sanitize}}

diff --git a/awx/ui/client/src/portal-mode/portal-job-templates.list.js b/awx/ui/client/src/portal-mode/portal-job-templates.list.js index 80898f9ded..77866b768f 100644 --- a/awx/ui/client/src/portal-mode/portal-job-templates.list.js +++ b/awx/ui/client/src/portal-mode/portal-job-templates.list.js @@ -23,7 +23,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: 'col-lg-5 col-md-5 col-sm-9 col-xs-8', linkTo: '/#/templates/job_template/{{job_template.id}}', - awToolTip: '{{job_template.description}}', + awToolTip: '{{job_template.description | sanitize}}', dataPlacement: 'top' } }, diff --git a/awx/ui/client/src/projects/projects.list.js b/awx/ui/client/src/projects/projects.list.js index 52dae13757..ae066d4356 100644 --- a/awx/ui/client/src/projects/projects.list.js +++ b/awx/ui/client/src/projects/projects.list.js @@ -37,7 +37,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: "col-lg-4 col-md-4 col-sm-5 col-xs-7 List-staticColumnAdjacent", modalColumnClass: 'col-md-8', - awToolTip: '{{project.description}}', + awToolTip: '{{project.description | sanitize}}', dataPlacement: 'top' }, scm_type: { diff --git a/awx/ui/client/src/teams/teams.list.js b/awx/ui/client/src/teams/teams.list.js index 0a675be83a..93aab82848 100644 --- a/awx/ui/client/src/teams/teams.list.js +++ b/awx/ui/client/src/teams/teams.list.js @@ -23,7 +23,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: 'col-lg-3 col-md-4 col-sm-9 col-xs-9', modalColumnClass: 'col-md-8', - awToolTip: '{{team.description}}', + awToolTip: '{{team.description | sanitize}}', dataPlacement: 'top' }, organization: { diff --git a/awx/ui/client/src/templates/templates.list.js b/awx/ui/client/src/templates/templates.list.js index 15d5ddf9e9..2e7f60293a 100644 --- a/awx/ui/client/src/templates/templates.list.js +++ b/awx/ui/client/src/templates/templates.list.js @@ -24,7 +24,7 @@ export default ['i18n', function(i18n) { label: i18n._('Name'), columnClass: 'col-lg-2 col-md-2 col-sm-4 col-xs-9', ngHref: '#/templates/{{template.type}}/{{template.id}}', - awToolTip: '{{template.description}}', + awToolTip: '{{template.description | sanitize}}', dataPlacement: 'top' }, type: { From 4fe2f90689348f51b79ae60eb4b086e019d4c2fd Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 24 Jul 2017 13:58:00 -0400 Subject: [PATCH 017/342] update ssh to machine --- .../job-submission.directive.js | 2 +- .../job-sub-cred-list.controller.js | 12 +++++------ .../multi-credential-modal.directive.js | 20 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/awx/ui/client/src/job-submission/job-submission.directive.js b/awx/ui/client/src/job-submission/job-submission.directive.js index cdfb4c69d0..e8fe363c7a 100644 --- a/awx/ui/client/src/job-submission/job-submission.directive.js +++ b/awx/ui/client/src/job-submission/job-submission.directive.js @@ -27,7 +27,7 @@ export default [ 'templateUrl', 'CreateDialog', 'Wait', 'CreateSelect2', 'ParseT credentialTypesLookup() .then(kinds => { if(scope.ask_credential_on_launch) { - scope.credentialKind = "" + kinds.SSH; + scope.credentialKind = "" + kinds.Machine; scope.includeCredentialList = true; } }); diff --git a/awx/ui/client/src/job-submission/lists/credential/job-sub-cred-list.controller.js b/awx/ui/client/src/job-submission/lists/credential/job-sub-cred-list.controller.js index 6a458c04c7..1cd819e009 100644 --- a/awx/ui/client/src/job-submission/lists/credential/job-sub-cred-list.controller.js +++ b/awx/ui/client/src/job-submission/lists/credential/job-sub-cred-list.controller.js @@ -70,9 +70,9 @@ export default $scope.$watchCollection('selectedCredentials.extra', () => { if($scope.credentials && $scope.credentials.length > 0) { - if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && parseInt($scope.credentialKind) !== credentialKinds.SSH) { + if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && parseInt($scope.credentialKind) !== credentialKinds.Machine) { updateExtraCredentialsList(); - } else if (parseInt($scope.credentialKind) !== credentialKinds.SSH) { + } else if (parseInt($scope.credentialKind) !== credentialKinds.Machine) { uncheckAllCredentials(); } } @@ -80,7 +80,7 @@ export default $scope.$watch('selectedCredentials.machine', () => { if($scope.credentials && $scope.credentials.length > 0) { - if($scope.selectedCredentials.machine && parseInt($scope.credentialKind) === credentialKinds.SSH) { + if($scope.selectedCredentials.machine && parseInt($scope.credentialKind) === credentialKinds.Machine) { updateMachineCredentialList(); } else { @@ -91,10 +91,10 @@ export default $scope.$watchGroup(['credentials', 'selectedCredentials.machine'], () => { if($scope.credentials && $scope.credentials.length > 0) { - if($scope.selectedCredentials.machine && parseInt($scope.credentialKind) === credentialKinds.SSH) { + if($scope.selectedCredentials.machine && parseInt($scope.credentialKind) === credentialKinds.Machine) { updateMachineCredentialList(); } - else if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && parseInt($scope.credentialKind) !== credentialKinds.SSH) { + else if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && parseInt($scope.credentialKind) !== credentialKinds.Machine) { updateExtraCredentialsList(); } else { @@ -105,7 +105,7 @@ export default }; $scope.toggle_row = function(selectedRow) { - if(parseInt($scope.credentialKind) === credentialKinds.SSH) { + if(parseInt($scope.credentialKind) === credentialKinds.Machine) { $scope.selectedCredentials.machine = _.cloneDeep(selectedRow); } else { diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js index 2d82e3133d..db65ad3834 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js @@ -14,14 +14,14 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' .then(kinds => { scope.credentialKinds = kinds; - scope.credentialKind = "" + kinds.SSH; + scope.credentialKind = "" + kinds.Machine; scope.showModal = function() { $('#multi-credential-modal').modal('show'); }; scope.destroyModal = function() { - scope.credentialKind = kinds.SSH; + scope.credentialKind = kinds.Machine; $('#multi-credential-modal').modal('hide'); }; @@ -62,7 +62,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' let extraCredIds = $scope.selectedCredentials.extra .map(cred => cred.id); $scope.credentials.forEach(cred => { - if (cred.credential_type !== $scope.credentialKinds.SSH) { + if (cred.credential_type !== $scope.credentialKinds.Machine) { cred.checked = (extraCredIds .indexOf(cred.id) > - 1) ? 1 : 0; } @@ -75,7 +75,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' let updateMachineCredentialList = function() { $scope.credentials.forEach(cred => { - if (cred.credential_type === $scope.credentialKinds.SSH) { + if (cred.credential_type === $scope.credentialKinds.Machine) { cred.checked = ($scope.selectedCredentials .machine !== null && cred.id === $scope.selectedCredentials @@ -158,9 +158,9 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' if($scope.credentials && $scope.credentials.length > 0) { if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && - parseInt($scope.credentialKind) !== $scope.credentialKinds.SSH) { + parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { updateExtraCredentialsList(); - } else if (parseInt($scope.credentialKind) !== $scope.credentialKinds.SSH) { + } else if (parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { uncheckAllCredentials(); } } @@ -169,7 +169,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.$watch('selectedCredentials.machine', () => { if($scope.selectedCredentials && $scope.selectedCredentials.machine && - parseInt($scope.credentialKind) === $scope.credentialKinds.SSH) { + parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { updateMachineCredentialList(); } else { uncheckAllCredentials(); @@ -193,7 +193,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' $scope.credentials.length > 0) { if($scope.selectedCredentials && $scope.selectedCredentials.machine && - parseInt($scope.credentialKind) === $scope.credentialKinds.SSH) { + parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { updateMachineCredentialList(); } else if($scope.selectedCredentials && $scope.selectedCredentials.vault && @@ -202,7 +202,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' } else if($scope.selectedCredentials && $scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0 && - parseInt($scope.credentialKind) !== $scope.credentialKinds.SSH) { + parseInt($scope.credentialKind) !== $scope.credentialKinds.Machine) { updateExtraCredentialsList(); } else { uncheckAllCredentials(); @@ -216,7 +216,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile' }); $scope.toggle_row = function(selectedRow) { - if(parseInt($scope.credentialKind) === $scope.credentialKinds.SSH) { + if(parseInt($scope.credentialKind) === $scope.credentialKinds.Machine) { if($scope.selectedCredentials && $scope.selectedCredentials.machine && $scope.selectedCredentials.machine.id === selectedRow.id) { From 15b01b99f3eb2846219627f7a2501cdf5f24cd66 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 14:08:08 -0400 Subject: [PATCH 018/342] More linkout fixes for the permissions modal --- .../add-rbac-user-team/rbac-selected-list.directive.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js index 6a3e1ba13e..eb67bb30f5 100644 --- a/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js +++ b/awx/ui/client/src/access/add-rbac-user-team/rbac-selected-list.directive.js @@ -42,6 +42,7 @@ export default ['$compile', 'i18n', 'generateList', name: list.fields.name, scm_type: list.fields.scm_type }; + list.fields.name.ngClick = 'linkoutResource("project", project)'; list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; list.fields.scm_type.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; @@ -50,6 +51,7 @@ export default ['$compile', 'i18n', 'generateList', name: list.fields.name, organization: list.fields.organization }; + list.fields.name.ngClick = 'linkoutResource("inventory", inventory)'; list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; list.fields.organization.columnClass = 'col-md-5 col-sm-5 hidden-xs'; break; @@ -72,10 +74,17 @@ export default ['$compile', 'i18n', 'generateList', list.fields.name.ngClick = 'linkoutResource("workflow_job_template", workflow_job_template)'; break; case 'credentials': + list.fields = { + name: list.fields.name + }; + list.fields.name.ngClick = 'linkoutResource("credential", credential)'; + list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; + break; case 'organizations': list.fields = { name: list.fields.name }; + list.fields.name.ngClick = 'linkoutResource("organization", organization)'; list.fields.name.columnClass = 'col-md-5 col-sm-5 col-xs-10'; break; } From 084163173afc136a15195a174aa2414b0dd24f2f Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 14:10:17 -0400 Subject: [PATCH 019/342] Removed extra sanitize filters --- .../src/organizations/list/organizations-list.partial.html | 2 +- awx/ui/client/src/partials/survey-maker-modal.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/organizations/list/organizations-list.partial.html b/awx/ui/client/src/organizations/list/organizations-list.partial.html index 3180fdc8d8..f266d2c670 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.partial.html +++ b/awx/ui/client/src/organizations/list/organizations-list.partial.html @@ -54,7 +54,7 @@

- {{ card.name | sanitize}} + {{ card.name }}

From 8983351e99ea62e3ac151a525221b45829892765 Mon Sep 17 00:00:00 2001 From: Jim Ladd Date: Mon, 24 Jul 2017 14:24:38 -0400 Subject: [PATCH 020/342] Deprecate post to v2 jobs (#18) --- awx/api/views.py | 7 +++++++ awx/main/tests/unit/test_views.py | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/awx/api/views.py b/awx/api/views.py index 8b82aba242..0297c649d2 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -3719,6 +3719,13 @@ class JobList(ListCreateAPIView): metadata_class = JobTypeMetadata serializer_class = JobListSerializer + @property + def allowed_methods(self): + methods = super(JobList, self).allowed_methods + if get_request_version(self.request) > 1: + methods.remove('POST') + return methods + class JobDetail(RetrieveUpdateDestroyAPIView): diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py index 2204635eb6..dd7f63bb3a 100644 --- a/awx/main/tests/unit/test_views.py +++ b/awx/main/tests/unit/test_views.py @@ -6,6 +6,7 @@ from rest_framework import exceptions # AWX from awx.main.views import ApiErrorView +from awx.api.views import JobList HTTP_METHOD_NAMES = [ @@ -35,3 +36,11 @@ def test_exception_view_raises_exception(api_view_obj_fixture, method_name): request_mock = mock.MagicMock() with pytest.raises(exceptions.APIException): getattr(api_view_obj_fixture, method_name)(request_mock) + + +@pytest.mark.parametrize('version, supports_post', [(1, True), (2, False)]) +def test_disable_post_on_v2_jobs_list(version, supports_post): + job_list = JobList() + job_list.request = mock.MagicMock() + with mock.patch('awx.api.views.get_request_version', return_value=version): + assert ('POST' in job_list.allowed_methods) == supports_post From ce4eccc5136ace099bb4ac7923f50b7c1b6f8b2f Mon Sep 17 00:00:00 2001 From: gconsidine Date: Mon, 24 Jul 2017 14:27:40 -0400 Subject: [PATCH 021/342] Add assignment of on list change --- .../credentials/list/credentials-list.controller.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 244afe6cc5..a160bfa40e 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -21,16 +21,14 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' $scope.canAdd = params.canAdd; }); + $scope.$watch(list.name, assignCredentialKinds); + // search init $scope.list = list; $scope[`${list.iterator}_dataset`] = Dataset.data; $scope[list.name] = $scope[`${list.iterator}_dataset`].results; $scope.selected = []; - - $scope[list.name].forEach(credential => { - credential.kind = credentialType.getById(credential.credential_type).name; - }); } $scope.$on(`${list.iterator}_options`, function(event, data){ @@ -42,6 +40,12 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' optionsRequestDataProcessing(); }); + function assignCredentialKinds () { + $scope[list.name].forEach(credential => { + credential.kind = credentialType.getById(credential.credential_type).name; + }); + } + // iterate over the list and add fields like type label, after the // OPTIONS request returns, or the list is sorted/paginated/searched function optionsRequestDataProcessing(){ From deb8eaa982f8c71615e9ba70275b4f157b20d7d9 Mon Sep 17 00:00:00 2001 From: Jim Ladd Date: Mon, 24 Jul 2017 14:32:13 -0400 Subject: [PATCH 022/342] Disable POST to v1 inventory_sources --- awx/api/views.py | 7 +++++++ awx/main/tests/unit/test_views.py | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/awx/api/views.py b/awx/api/views.py index 0297c649d2..e783b94d0b 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2532,6 +2532,13 @@ class InventorySourceList(ListCreateAPIView): always_allow_superuser = False new_in_320 = True + @property + def allowed_methods(self): + methods = super(InventorySourceList, self).allowed_methods + if get_request_version(self.request) == 1: + methods.remove('POST') + return methods + class InventorySourceDetail(RetrieveUpdateDestroyAPIView): diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py index dd7f63bb3a..8252581a12 100644 --- a/awx/main/tests/unit/test_views.py +++ b/awx/main/tests/unit/test_views.py @@ -6,7 +6,7 @@ from rest_framework import exceptions # AWX from awx.main.views import ApiErrorView -from awx.api.views import JobList +from awx.api.views import JobList, InventorySourceList HTTP_METHOD_NAMES = [ @@ -44,3 +44,10 @@ def test_disable_post_on_v2_jobs_list(version, supports_post): job_list.request = mock.MagicMock() with mock.patch('awx.api.views.get_request_version', return_value=version): assert ('POST' in job_list.allowed_methods) == supports_post + +@pytest.mark.parametrize('version, supports_post', [(1, False), (2, True)]) +def test_disable_post_on_v1_inventory_source_list(version, supports_post): + inv_source_list = InventorySourceList() + inv_source_list.request = mock.MagicMock() + with mock.patch('awx.api.views.get_request_version', return_value=version): + assert ('POST' in inv_source_list.allowed_methods) == supports_post From cc547af18b5974e9375c631bff992d49cbca537f Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 24 Jul 2017 15:12:16 -0400 Subject: [PATCH 023/342] on initial SCM inv src creation update, dont update project rev --- awx/main/models/inventory.py | 2 +- awx/main/tasks.py | 2 +- awx/main/tests/functional/api/test_inventory.py | 2 +- awx/main/tests/functional/conftest.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 0e078615c3..9665f9ec84 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1312,7 +1312,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): # Schedule a new Project update if one is not already queued if self.source_project and not self.source_project.project_updates.filter( status__in=['new', 'pending', 'waiting']).exists(): - self.source_project.update() + self.update() if not getattr(_inventory_updates, 'is_updating', False): if self.inventory is not None: self.inventory.update_computed_fields(update_groups=False, update_hosts=False) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 6a7b97a861..d5c41684b0 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1421,7 +1421,7 @@ class RunProjectUpdate(BaseTask): if InventoryUpdate.objects.filter(inventory_source=inv_src, status__in=ACTIVE_STATES).exists(): logger.info('Skipping SCM inventory update for `{}` because ' - 'another update is already active.'.format(inv.name)) + 'another update is already active.'.format(inv_src.name)) continue local_inv_update = inv_src.create_inventory_update( launch_type='scm', diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index 331c6fab7a..192c72ad86 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -10,7 +10,7 @@ from awx.main.models import InventorySource @pytest.fixture def scm_inventory(inventory, project): - with mock.patch.object(project, 'update'): + with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'): inventory.inventory_sources.create( name='foobar', update_on_project_update=True, source='scm', source_project=project, scm_last_revision=project.scm_revision) diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 879e6ab3fd..67ea92ae62 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -314,7 +314,7 @@ def scm_inventory_source(inventory, project): update_on_project_update=True, inventory=inventory, scm_last_revision=project.scm_revision) - with mock.patch.object(inv_src.source_project, 'update'): + with mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.update'): inv_src.save() return inv_src From 1b2d0f9683749c50ade30e825895e1e508f46123 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Mon, 24 Jul 2017 15:12:24 -0400 Subject: [PATCH 024/342] Fix flake8 error --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index cd11ba86a5..a36e5be153 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ if os.getenv('OFFICIAL', 'no') == 'yes': else: build_tag = '-' + '0.git' + subprocess.Popen("git describe --long | cut -d - -f 2-2", shell=True, stdout=subprocess.PIPE).stdout.read().strip() + def get_version(): ver = subprocess.Popen("git describe --long | cut -f1-1 -d -", shell=True, stdout=subprocess.PIPE).stdout.read().strip() return re.sub(r'-([0-9]+)-.*', r'.\1', ver) From b6547fe97a1b091a191f56fe8238604d8a07bd09 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 15:12:54 -0400 Subject: [PATCH 025/342] Fixed xss vulnerabilities within the delete permissions modals --- .../src/access/permissions-list.controller.js | 14 ++++++++------ .../access/rbac-role-column/roleList.directive.js | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/access/permissions-list.controller.js b/awx/ui/client/src/access/permissions-list.controller.js index ebdbc394d2..92ed68de9c 100644 --- a/awx/ui/client/src/access/permissions-list.controller.js +++ b/awx/ui/client/src/access/permissions-list.controller.js @@ -4,8 +4,8 @@ * All Rights Reserved *************************************************/ -export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessErrors', 'Prompt', '$state', - function($scope, list, Dataset, Wait, Rest, ProcessErrors, Prompt, $state) { +export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessErrors', 'Prompt', '$state', '$filter', + function($scope, list, Dataset, Wait, Rest, ProcessErrors, Prompt, $state, $filter) { init(); function init() { @@ -15,6 +15,7 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE } $scope.deletePermissionFromUser = function(userId, userName, roleName, roleType, url) { + var action = function() { $('#prompt-modal').modal('hide'); Wait('start'); @@ -36,9 +37,9 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE hdr: `Remove role`, body: `
- Confirm the removal of the ${roleType} + Confirm the removal of the ${$filter('sanitize')(roleType)} ${roleName} - role associated with ${userName}. + role associated with ${$filter('sanitize')(userName)}.
`, action: action, @@ -47,6 +48,7 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE }; $scope.deletePermissionFromTeam = function(teamId, teamName, roleName, roleType, url) { + var action = function() { $('#prompt-modal').modal('hide'); Wait('start'); @@ -68,9 +70,9 @@ export default ['$scope', 'ListDefinition', 'Dataset', 'Wait', 'Rest', 'ProcessE hdr: `Remove role`, body: `
- Confirm the removal of the ${roleType} + Confirm the removal of the ${$filter('sanitize')(roleType)} ${roleName} - role associated with the ${teamName} team. + role associated with the ${$filter('sanitize')(teamName)} team.
`, action: action, diff --git a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js index 10b589cf7c..ec3b79754d 100644 --- a/awx/ui/client/src/access/rbac-role-column/roleList.directive.js +++ b/awx/ui/client/src/access/rbac-role-column/roleList.directive.js @@ -75,7 +75,7 @@ export default } else { Prompt({ hdr: `User access removal`, - body: `
Please confirm that you would like to remove ${entry.name} access from ${user.username}.
`, + body: `
Please confirm that you would like to remove ${entry.name} access from ${$filter('sanitize')(user.username)}.
`, action: action, actionText: 'REMOVE' }); From 4aa332d7e17a23c7c72e09d5587e5be3606681dd Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Mon, 24 Jul 2017 09:23:17 -0400 Subject: [PATCH 026/342] pull upstream changes for ansible-inventory backport --- awx/plugins/ansible_inventory/backport.py | 97 +++++++++++------------ 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/awx/plugins/ansible_inventory/backport.py b/awx/plugins/ansible_inventory/backport.py index 81bef170cc..4024341def 100755 --- a/awx/plugins/ansible_inventory/backport.py +++ b/awx/plugins/ansible_inventory/backport.py @@ -22,13 +22,9 @@ __metaclass__ = type import optparse from operator import attrgetter -from ansible import constants as C from ansible.cli import CLI from ansible.errors import AnsibleOptionsError -from ansible.inventory import Inventory from ansible.parsing.dataloader import DataLoader -from ansible.vars import VariableManager - try: from __main__ import display @@ -60,8 +56,12 @@ class InventoryCLI(CLI): def __init__(self, args): super(InventoryCLI, self).__init__(args) + self.args = args self.vm = None self.loader = None + self.inventory = None + + self._new_api = True def parse(self): @@ -71,6 +71,8 @@ class InventoryCLI(CLI): inventory_opts=True, vault_opts=True ) + self.parser.add_option("--optimize", action="store_true", default=False, dest='optimize', + help='Output variables on the group or host where they are defined') # Actions action_group = optparse.OptionGroup(self.parser, "Actions", "One of following must be used on invocation, ONLY ONE!") @@ -93,32 +95,6 @@ class InventoryCLI(CLI): raise # --- Start of 2.3+ super(InventoryCLI, self).parse() --- self.options, self.args = self.parser.parse_args(self.args[1:]) - if hasattr(self.options, 'tags') and not self.options.tags: - # optparse defaults does not do what's expected - self.options.tags = ['all'] - if hasattr(self.options, 'tags') and self.options.tags: - if not C.MERGE_MULTIPLE_CLI_TAGS: - if len(self.options.tags) > 1: - display.deprecated('Specifying --tags multiple times on the command line currently uses the last specified value. In 2.4, values will be merged instead. Set merge_multiple_cli_tags=True in ansible.cfg to get this behavior now.', version=2.5, removed=False) - self.options.tags = [self.options.tags[-1]] - - tags = set() - for tag_set in self.options.tags: - for tag in tag_set.split(u','): - tags.add(tag.strip()) - self.options.tags = list(tags) - - if hasattr(self.options, 'skip_tags') and self.options.skip_tags: - if not C.MERGE_MULTIPLE_CLI_TAGS: - if len(self.options.skip_tags) > 1: - display.deprecated('Specifying --skip-tags multiple times on the command line currently uses the last specified value. In 2.4, values will be merged instead. Set merge_multiple_cli_tags=True in ansible.cfg to get this behavior now.', version=2.5, removed=False) - self.options.skip_tags = [self.options.skip_tags[-1]] - - skip_tags = set() - for tag_set in self.options.skip_tags: - for tag in tag_set.split(u','): - skip_tags.add(tag.strip()) - self.options.skip_tags = list(skip_tags) # --- End of 2.3+ super(InventoryCLI, self).parse() --- display.verbosity = self.options.verbosity @@ -148,30 +124,38 @@ class InventoryCLI(CLI): super(InventoryCLI, self).run() # Initialize needed objects - self.loader = DataLoader() - self.vm = VariableManager() - - # use vault if needed - if self.options.vault_password_file: - vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=self.loader) - elif self.options.ask_vault_pass: - vault_pass = self.ask_vault_passwords() + if getattr(self, '_play_prereqs', False): + self.loader, self.inventory, self.vm = self._play_prereqs(self.options) else: - vault_pass = None + # fallback to pre 2.4 way of initialzing + from ansible.vars import VariableManager + from ansible.inventory import Inventory - if vault_pass: - self.loader.set_vault_password(vault_pass) + self._new_api = False + self.loader = DataLoader() + self.vm = VariableManager() - # actually get inventory and vars - self.inventory = Inventory(loader=self.loader, variable_manager=self.vm, host_list=self.options.inventory) - self.vm.set_inventory(self.inventory) + # use vault if needed + if self.options.vault_password_file: + vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=self.loader) + elif self.options.ask_vault_pass: + vault_pass = self.ask_vault_passwords() + else: + vault_pass = None + + if vault_pass: + self.loader.set_vault_password(vault_pass) + # actually get inventory and vars + + self.inventory = Inventory(loader=self.loader, variable_manager=self.vm, host_list=self.options.inventory) + self.vm.set_inventory(self.inventory) if self.options.host: hosts = self.inventory.get_hosts(self.options.host) if len(hosts) != 1: raise AnsibleOptionsError("You must pass a single valid host to --hosts parameter") - myvars = self.vm.get_vars(self.loader, host=hosts[0]) + myvars = self._get_host_variables(host=hosts[0]) self._remove_internal(myvars) # FIXME: should we template first? @@ -180,7 +164,7 @@ class InventoryCLI(CLI): elif self.options.graph: results = self.inventory_graph() elif self.options.list: - top = self.inventory.get_group('all') + top = self._get_group('all') if self.options.yaml: results = self.yaml_inventory(top) else: @@ -188,6 +172,7 @@ class InventoryCLI(CLI): results = self.dump(results) if results: + # FIXME: pager? display.display(results) exit(0) @@ -205,6 +190,20 @@ class InventoryCLI(CLI): return results + def _get_host_variables(self, host): + if self._new_api: + hostvars = self.vm.get_vars(host=host) + else: + hostvars = self.vm.get_vars(self.loader, host=host) + return hostvars + + def _get_group(self, gname): + if self._new_api: + group = self.inventory.groups.get(gname) + else: + group = self.inventory.get_group(gname) + return group + def _remove_internal(self, dump): for internal in INTERNAL_VARS: @@ -248,7 +247,7 @@ class InventoryCLI(CLI): def inventory_graph(self): - start_at = self.inventory.get_group(self.options.pattern) + start_at = self._get_group(self.options.pattern) if start_at: return '\n'.join(self._graph_group(start_at)) else: @@ -276,7 +275,7 @@ class InventoryCLI(CLI): results['_meta'] = {'hostvars': {}} hosts = self.inventory.get_hosts() for host in hosts: - results['_meta']['hostvars'][host.name] = self.vm.get_vars(self.loader, host=host) + results['_meta']['hostvars'][host.name] = self._get_host_variables(host=host) self._remove_internal(results['_meta']['hostvars'][host.name]) return results @@ -305,7 +304,7 @@ class InventoryCLI(CLI): myvars = {} if h.name not in seen: # avoid defining host vars more than once seen.append(h.name) - myvars = self.vm.get_vars(self.loader, host=h) + myvars = self._get_host_variables(host=h) self._remove_internal(myvars) results[group.name]['hosts'][h.name] = myvars From 8ce1421c6a6bb513238b85aebd7add36722556f6 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 24 Jul 2017 16:03:58 -0400 Subject: [PATCH 027/342] fix tower-expect -> awx-expect for isolated tower builds --- awx/main/isolated/isolated_manager.py | 2 +- awx/playbooks/check_isolated.yml | 2 +- awx/playbooks/clean_isolated.yml | 2 +- awx/playbooks/run_isolated.yml | 2 +- awx/plugins/isolated/tower_capacity.py | 2 +- awx/plugins/isolated/tower_isolated_cleanup.py | 2 +- setup.py | 2 +- tools/docker-isolated-override.yml | 8 ++++---- tools/docker-isolated/Dockerfile | 2 +- tools/docker-isolated/README.md | 8 ++++---- tools/docker-isolated/awx-expect | 3 +++ tools/docker-isolated/tower-expect | 3 --- tools/scripts/awx-expect | 4 ++++ tools/scripts/tower-expect | 4 ---- 14 files changed, 23 insertions(+), 23 deletions(-) create mode 100755 tools/docker-isolated/awx-expect delete mode 100755 tools/docker-isolated/tower-expect create mode 100755 tools/scripts/awx-expect delete mode 100755 tools/scripts/tower-expect diff --git a/awx/main/isolated/isolated_manager.py b/awx/main/isolated/isolated_manager.py index dfdbed34d1..c20e61f2f2 100644 --- a/awx/main/isolated/isolated_manager.py +++ b/awx/main/isolated/isolated_manager.py @@ -170,7 +170,7 @@ class IsolatedManager(object): # - sets up a temporary directory for proot/bwrap (if necessary) # - copies encrypted job data from the controlling host to the isolated host (with rsync) # - writes the encryption secret to a named pipe on the isolated host - # - launches the isolated playbook runner via `tower-expect start ` + # - launches the isolated playbook runner via `awx-expect start ` args = self._build_args('run_isolated.yml', '%s,' % self.host, extra_vars) if self.instance.verbosity: args.append('-%s' % ('v' * min(5, self.instance.verbosity))) diff --git a/awx/playbooks/check_isolated.yml b/awx/playbooks/check_isolated.yml index 60ccfd1ddf..69018a6ed2 100644 --- a/awx/playbooks/check_isolated.yml +++ b/awx/playbooks/check_isolated.yml @@ -10,7 +10,7 @@ tasks: - name: Determine if daemon process is alive. - shell: "tower-expect is-alive {{src}}" + shell: "awx-expect is-alive {{src}}" register: is_alive ignore_errors: true diff --git a/awx/playbooks/clean_isolated.yml b/awx/playbooks/clean_isolated.yml index 0945411318..e40e780396 100644 --- a/awx/playbooks/clean_isolated.yml +++ b/awx/playbooks/clean_isolated.yml @@ -11,7 +11,7 @@ tasks: - name: cancel the job - command: "tower-expect stop {{private_data_dir}}" + command: "awx-expect stop {{private_data_dir}}" ignore_errors: yes - name: remove build artifacts diff --git a/awx/playbooks/run_isolated.yml b/awx/playbooks/run_isolated.yml index 5233f63dd1..bdcc798339 100644 --- a/awx/playbooks/run_isolated.yml +++ b/awx/playbooks/run_isolated.yml @@ -29,7 +29,7 @@ command: "mkfifo {{src}}/env" - name: spawn the playbook - command: "tower-expect start {{src}}" + command: "awx-expect start {{src}}" - name: write the secret environment data mkfifo: diff --git a/awx/plugins/isolated/tower_capacity.py b/awx/plugins/isolated/tower_capacity.py index 9ef879b423..cc69d02a3b 100644 --- a/awx/plugins/isolated/tower_capacity.py +++ b/awx/plugins/isolated/tower_capacity.py @@ -26,7 +26,7 @@ def main(): ) try: version = subprocess.check_output( - ['tower-expect', '--version'], + ['awx-expect', '--version'], stderr=subprocess.STDOUT ).strip() except subprocess.CalledProcessError as e: diff --git a/awx/plugins/isolated/tower_isolated_cleanup.py b/awx/plugins/isolated/tower_isolated_cleanup.py index 529a24fd9d..ac9a0cd101 100644 --- a/awx/plugins/isolated/tower_isolated_cleanup.py +++ b/awx/plugins/isolated/tower_isolated_cleanup.py @@ -51,7 +51,7 @@ def main(): try: re_match = re.match(r'\/tmp\/ansible_tower_\d+_.+', path) if re_match is not None: - if subprocess.check_call(['tower-expect', 'is-alive', path]) == 0: + if subprocess.check_call(['awx-expect', 'is-alive', path]) == 0: continue else: module.debug('Deleting path {} its job has completed.'.format(path)) diff --git a/setup.py b/setup.py index a36e5be153..5aa7b36447 100755 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ class sdist_isolated(sdist): 'include Makefile', 'include awx/__init__.py', 'include awx/main/isolated/run.py', - 'include tools/scripts/tower-expect', + 'include tools/scripts/awx-expect', 'include requirements/requirements_isolated.txt', 'recursive-include awx/lib *.py', ] diff --git a/tools/docker-isolated-override.yml b/tools/docker-isolated-override.yml index 2e2613fb82..140f13de7a 100644 --- a/tools/docker-isolated-override.yml +++ b/tools/docker-isolated-override.yml @@ -1,17 +1,17 @@ version: '3' services: # Primary Tower Development Container link - tower: + awx: environment: EXTRA_GROUP_QUEUES: thepentagon links: - isolated # Isolated Rampart Container isolated: - image: gcr.io/ansible-tower-engineering/tower_isolated:${TAG} + image: gcr.io/ansible-tower-engineering/awx_isolated:${TAG} hostname: isolated volumes: - - "../awx/main/isolated:/tower_devel" - - "../awx/lib:/tower_lib" + - "../awx/main/isolated:/awx_devel" + - "../awx/lib:/awx_lib" - "/sys/fs/cgroup:/sys/fs/cgroup:ro" privileged: true diff --git a/tools/docker-isolated/Dockerfile b/tools/docker-isolated/Dockerfile index b9ab6ea047..b08a33e1ed 100644 --- a/tools/docker-isolated/Dockerfile +++ b/tools/docker-isolated/Dockerfile @@ -17,7 +17,7 @@ ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 WORKDIR / EXPOSE 22 -ADD tools/docker-isolated/tower-expect /usr/local/bin/tower-expect +ADD tools/docker-isolated/awx-expect /usr/local/bin/awx-expect RUN rm -f /etc/ssh/ssh_host_ecdsa_key /etc/ssh/ssh_host_rsa_key RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_ecdsa_key diff --git a/tools/docker-isolated/README.md b/tools/docker-isolated/README.md index 9a2ae470c2..e2574fd6ca 100644 --- a/tools/docker-isolated/README.md +++ b/tools/docker-isolated/README.md @@ -1,7 +1,7 @@ ## Instructions on using an isolated node The building of the isolated node is done in the `make docker-compose-build` -target. Its image uses a different tag from the tools_tower container. +target. Its image uses a different tag from the tools_awx container. Given that the images are built, you can run the combined docker compose target. This uses the base `docker-compose.yml` with modifications found in `docker-isolated-override.yml`. @@ -12,7 +12,7 @@ base branch is. For example: make docker-isolated COMPOSE_TAG=devel ``` -This will automatically exchange the keys in order for the `tools_tower_1` +This will automatically exchange the keys in order for the `tools_awx_1` container to access the `tools_isolated_1` container over ssh. After that, it will bring up all the containers like the normal docker-compose workflow. @@ -61,7 +61,7 @@ Example location of a private data directory: The following command would run the playbook corresponding to that job. ```bash -tower-expect start /tmp/ansible_tower_29_OM6Mnx/ +awx-expect start /tmp/ansible_tower_29_OM6Mnx/ ``` -Other tower-expect commands include `start`, `is-alive`, and `stop`. +Other awx-expect commands include `start`, `is-alive`, and `stop`. diff --git a/tools/docker-isolated/awx-expect b/tools/docker-isolated/awx-expect new file mode 100755 index 0000000000..0ceb7a629c --- /dev/null +++ b/tools/docker-isolated/awx-expect @@ -0,0 +1,3 @@ +#!/bin/bash +. /venv/awx/bin/activate +exec env TOWER_LIB_DIRECTORY=/awx_lib /awx_devel/run.py "$@" diff --git a/tools/docker-isolated/tower-expect b/tools/docker-isolated/tower-expect deleted file mode 100755 index bfe543dd85..0000000000 --- a/tools/docker-isolated/tower-expect +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -. /venv/tower/bin/activate -exec env TOWER_LIB_DIRECTORY=/tower_lib /tower_devel/run.py "$@" diff --git a/tools/scripts/awx-expect b/tools/scripts/awx-expect new file mode 100755 index 0000000000..bd3364e666 --- /dev/null +++ b/tools/scripts/awx-expect @@ -0,0 +1,4 @@ +#!/bin/bash +AWX_LIB=`/var/lib/awx/venv/awx/bin/python -c 'import os, awx; print os.path.dirname(awx.__file__)'` +. /var/lib/awx/venv/awx/bin/activate +exec env TOWER_LIB_DIRECTORY=$AWX_LIB/lib /var/lib/awx/venv/awx/bin/python $AWX_LIB/main/isolated/run.pyc "$@" diff --git a/tools/scripts/tower-expect b/tools/scripts/tower-expect deleted file mode 100755 index c682aa7e9f..0000000000 --- a/tools/scripts/tower-expect +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -AWX_LIB=`/var/lib/awx/venv/tower/bin/python -c 'import os, awx; print os.path.dirname(awx.__file__)'` -. /var/lib/awx/venv/tower/bin/activate -exec env TOWER_LIB_DIRECTORY=$AWX_LIB/lib /var/lib/awx/venv/tower/bin/python $AWX_LIB/main/isolated/run.pyc "$@" From c9f39d6306b0b7edcc2819cd638078d357280140 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 16:01:25 -0400 Subject: [PATCH 028/342] Fix inv source schedule title sanitizing --- .../related/sources/list/schedule/sources-schedule.route.js | 4 ++-- awx/ui/client/src/scheduler/schedules.list.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js index 73c200fa97..962263e673 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js @@ -44,9 +44,9 @@ export default { }, // target the un-named ui-view @ root level '@': { - templateProvider: function(ScheduleList, generateList, ParentObject) { + templateProvider: function(ScheduleList, generateList, ParentObject, $filter) { // include name of parent resource in listTitle - ScheduleList.listTitle = `${ParentObject.name}
` + N_('SCHEDULES'); + ScheduleList.listTitle = `${$filter('sanitize')(ParentObject.name)}
` + N_('SCHEDULES'); let html = generateList.build({ list: ScheduleList, mode: 'edit' diff --git a/awx/ui/client/src/scheduler/schedules.list.js b/awx/ui/client/src/scheduler/schedules.list.js index 5bf3341962..c282bbefeb 100644 --- a/awx/ui/client/src/scheduler/schedules.list.js +++ b/awx/ui/client/src/scheduler/schedules.list.js @@ -12,7 +12,7 @@ export default ['i18n', function(i18n) { iterator: 'schedule', selectTitle: '', editTitle: 'SCHEDULES', - listTitle: '{{parentObject}} || SCHEDULES', + listTitle: '{{parentObject | sanitize}} || SCHEDULES', index: false, hover: true, From a23f8fb21e4a5497e619b6d4513c3950607b018c Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Mon, 24 Jul 2017 16:14:02 -0400 Subject: [PATCH 029/342] UX Hit List: Inventories, Empty Lists, Lookup Buttons --- awx/ui/client/legacy-styles/ansible-ui.less | 1 - awx/ui/client/legacy-styles/forms.less | 1 - awx/ui/client/legacy-styles/jobs.less | 4 ++++ awx/ui/client/src/inventories-hosts/hosts/host.form.js | 5 ----- .../inventories/inventories.block.less | 9 +++++++++ .../related/completed-jobs/completed-jobs.list.js | 2 +- .../related/nested-hosts/group-nested-hosts.form.js | 5 ----- .../src/inventories-hosts/inventory-hosts.block.less | 10 ++++++++++ .../client/src/organizations/organizations.block.less | 5 +++++ awx/ui/client/src/shared/form-generator.js | 8 -------- 10 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 awx/ui/client/src/inventories-hosts/inventory-hosts.block.less create mode 100644 awx/ui/client/src/organizations/organizations.block.less diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 2af0228646..2f750ffef5 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -728,7 +728,6 @@ dd { } .error { - margin-top: 5px; font-size: 12px; line-height: normal; color: @red; diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 87d376779d..befb8d760a 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -464,7 +464,6 @@ .Form-lookupButton:hover { cursor: pointer; background-color: @field-lookup-btn-hov-bg; - border: 1px solid @field-border; color: @default-interface-txt; } diff --git a/awx/ui/client/legacy-styles/jobs.less b/awx/ui/client/legacy-styles/jobs.less index 86e14926e1..7f7c805eaa 100644 --- a/awx/ui/client/legacy-styles/jobs.less +++ b/awx/ui/client/legacy-styles/jobs.less @@ -59,6 +59,10 @@ background-color: @white; } + .List-noItems { + margin-top: 0; + } + } @media (min-width: 1201px) { diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.form.js b/awx/ui/client/src/inventories-hosts/hosts/host.form.js index 3fbf7aeb86..d64145be5d 100644 --- a/awx/ui/client/src/inventories-hosts/hosts/host.form.js +++ b/awx/ui/client/src/inventories-hosts/hosts/host.form.js @@ -79,11 +79,6 @@ function(i18n) { dataTitle: i18n._('Host Variables'), dataPlacement: 'right', dataContainer: 'body' - }, - inventory: { - type: 'hidden', - includeOnEdit: true, - includeOnAdd: true } }, diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less b/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less index 91727c2e7d..0121cae5a9 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less +++ b/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less @@ -1,3 +1,12 @@ .Inventories-hostStatus { margin-left: 5px; } +#inventories-panel { + .completed_jobsList.List-well { + margin: 0; + + .List-noItems { + margin: 0; + } + } +} \ No newline at end of file diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js index f8c90fa6f9..c0987adc2b 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/completed-jobs/completed-jobs.list.js @@ -19,7 +19,7 @@ export default ['i18n', function(i18n) { editTitle: i18n._('COMPLETED JOBS'), index: false, hover: true, - well: false, + well: true, emptyListText: i18n._('No completed jobs'), fields: { diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js index ebec902f5a..df84c40e33 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js @@ -79,11 +79,6 @@ function(i18n) { dataTitle: i18n._('Host Variables'), dataPlacement: 'right', dataContainer: 'body' - }, - inventory: { - type: 'hidden', - includeOnEdit: true, - includeOnAdd: true } }, diff --git a/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less b/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less new file mode 100644 index 0000000000..e4663663fb --- /dev/null +++ b/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less @@ -0,0 +1,10 @@ +#hosts-panel { + .List-noItems { + margin-top: 0px; + } + .groupsList { + .List-noItems { + margin-top: 52px; + } + } +} \ No newline at end of file diff --git a/awx/ui/client/src/organizations/organizations.block.less b/awx/ui/client/src/organizations/organizations.block.less new file mode 100644 index 0000000000..9075f485ca --- /dev/null +++ b/awx/ui/client/src/organizations/organizations.block.less @@ -0,0 +1,5 @@ +#organizations { + .List-noItems { + margin-top: 20px; + } +} \ No newline at end of file diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 91e704d401..fd4f16c45f 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -740,18 +740,10 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "
\n"; } - if (field.type === 'hidden') { - if ((options.mode === 'edit' && field.includeOnEdit) || - (options.mode === 'add' && field.includeOnAdd)) { - html += ""; - } - } - if ((!field.readonly) || (field.readonly && options.mode === 'edit')) { if((field.excludeMode === undefined || field.excludeMode !== options.mode) && field.type !== 'alertblock' && field.type !== 'workflow-chart') { - html += "
{ credential.kind = credentialType.getById(credential.credential_type).name; }); From 2db8b3208bfbdde39c97f3d146041a6b7caf4b46 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Mon, 24 Jul 2017 16:56:25 -0400 Subject: [PATCH 031/342] Close Inventory Scripts add/edit form on save --- awx/ui/client/src/inventory-scripts/add/add.controller.js | 2 +- awx/ui/client/src/inventory-scripts/edit/edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/inventory-scripts/add/add.controller.js b/awx/ui/client/src/inventory-scripts/add/add.controller.js index b0f598ffa3..15c4d301e9 100644 --- a/awx/ui/client/src/inventory-scripts/add/add.controller.js +++ b/awx/ui/client/src/inventory-scripts/add/add.controller.js @@ -44,7 +44,7 @@ export default ['Rest', 'Wait', script: $scope.script }) .success(function(data) { - $state.go('inventoryScripts.edit', { inventory_script_id: data.id }, { reload: true }); + $state.go('inventoryScripts', null, { reload: true }); Wait('stop'); }) .error(function(data, status) { diff --git a/awx/ui/client/src/inventory-scripts/edit/edit.controller.js b/awx/ui/client/src/inventory-scripts/edit/edit.controller.js index 61acb618c6..44b58fe197 100644 --- a/awx/ui/client/src/inventory-scripts/edit/edit.controller.js +++ b/awx/ui/client/src/inventory-scripts/edit/edit.controller.js @@ -60,7 +60,7 @@ export default ['Rest', 'Wait', script: $scope.script }) .success(function() { - $state.go($state.current, null, { reload: true }); + $state.go('inventoryScripts', null, { reload: true }); Wait('stop'); }) .error(function(data, status) { From d7f368ec6433a493725ebfde27c4f659f03c58ff Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 24 Jul 2017 18:08:59 -0400 Subject: [PATCH 032/342] Fixed inventory organization link bug --- awx/ui/client/src/notifications/notifications.list.js | 2 +- awx/ui/client/src/shared/directives.js | 2 +- .../src/shared/list-generator/list-generator.factory.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/notifications/notifications.list.js b/awx/ui/client/src/notifications/notifications.list.js index 24e1aa35c7..74a809444d 100644 --- a/awx/ui/client/src/notifications/notifications.list.js +++ b/awx/ui/client/src/notifications/notifications.list.js @@ -25,7 +25,7 @@ export default ['i18n', 'templateUrl', function(i18n, templateUrl){ key: true, label: i18n._('Name'), columnClass: 'col-md-3 col-sm-9 col-xs-9', - linkTo: '/#/notification_templates/{{notifier.id}}' + linkTo: '/#/notification_templates/{{notification.id}}' }, notification_type: { label: i18n._('Type'), diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 2967ffaba5..445352fef3 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -278,7 +278,7 @@ function(ConfigurationUtils, i18n, $rootScope) { restrict: 'A', link: function(scope, element, attrs) { element.bind('click', function(event) { - if (attrs.disableRow) { + if (scope.$eval(attrs.disableRow)) { event.preventDefault(); } return; diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js index 00e423a95b..64bd243d72 100644 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ b/awx/ui/client/src/shared/list-generator/list-generator.factory.js @@ -322,8 +322,8 @@ export default ['$compile', 'Attr', 'Icon', innerTable += "]\" "; innerTable += "id=\"{{ " + list.iterator + ".id }}\" "; innerTable += "class=\"List-tableRow " + list.iterator + "_class\" "; - innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name + "\""; - innerTable += (list.disableRow) ? " disable-row=" + list.disableRow + " " : ""; + innerTable += (list.disableRow) ? " disable-row=\"" + list.disableRow + "\" " : ""; + innerTable += "ng-repeat=\"" + list.iterator + " in " + list.name; innerTable += (list.trackBy) ? " track by " + list.trackBy : ""; innerTable += (list.orderBy) ? " | orderBy:'" + list.orderBy + "'" : ""; innerTable += (list.filterBy) ? " | filter: " + list.filterBy : ""; From 58392009fc74e23249774ccf851f8a542c05b987 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Mon, 24 Jul 2017 20:19:41 -0400 Subject: [PATCH 033/342] fix jenkins f8 error --- awx/main/tests/unit/test_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/main/tests/unit/test_views.py b/awx/main/tests/unit/test_views.py index 8252581a12..6e3572dc7d 100644 --- a/awx/main/tests/unit/test_views.py +++ b/awx/main/tests/unit/test_views.py @@ -45,6 +45,7 @@ def test_disable_post_on_v2_jobs_list(version, supports_post): with mock.patch('awx.api.views.get_request_version', return_value=version): assert ('POST' in job_list.allowed_methods) == supports_post + @pytest.mark.parametrize('version, supports_post', [(1, False), (2, True)]) def test_disable_post_on_v1_inventory_source_list(version, supports_post): inv_source_list = InventorySourceList() From eb64d6e80870d3eeede0e0b2343137129826e383 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 24 Jul 2017 21:02:48 -0400 Subject: [PATCH 034/342] Update cluster compose for repo migration --- tools/docker-compose-cluster.yml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tools/docker-compose-cluster.yml b/tools/docker-compose-cluster.yml index c6521d7011..9986e09972 100644 --- a/tools/docker-compose-cluster.yml +++ b/tools/docker-compose-cluster.yml @@ -5,19 +5,19 @@ services: context: ./docker-compose dockerfile: Dockerfile-haproxy depends_on: - - "tower_1" - - "tower_2" - - "tower_3" + - "awx_1" + - "awx_2" + - "awx_3" ports: - "8013:8013" - "8043:8043" - "1936:1936" - "5555:5555" - "15672:15672" - tower_1: + awx_1: privileged: true - image: gcr.io/ansible-tower-engineering/tower_devel:${TAG} - hostname: tower_1 + image: gcr.io/ansible-tower-engineering/awx_devel:${TAG} + hostname: awx_1 environment: RABBITMQ_HOST: rabbitmq_1 RABBITMQ_USER: guest @@ -26,12 +26,12 @@ services: CELERY_RDB_HOST: 0.0.0.0 EXTRA_GROUP_QUEUES: alpha volumes: - - "../:/tower_devel" + - "../:/awx_devel" - tower_2: + awx_2: privileged: true - image: gcr.io/ansible-tower-engineering/tower_devel:${TAG} - hostname: tower_2 + image: gcr.io/ansible-tower-engineering/awx_devel:${TAG} + hostname: awx_2 environment: RABBITMQ_HOST: rabbitmq_2 RABBITMQ_USER: guest @@ -40,11 +40,11 @@ services: CELERY_RDB_HOST: 0.0.0.0 EXTRA_GROUP_QUEUES: bravo volumes: - - "../:/tower_devel" - tower_3: + - "../:/awx_devel" + awx_3: privileged: true - image: gcr.io/ansible-tower-engineering/tower_devel:${TAG} - hostname: tower_3 + image: gcr.io/ansible-tower-engineering/awx_devel:${TAG} + hostname: awx_3 environment: RABBITMQ_HOST: rabbitmq_3 RABBITMQ_USER: guest @@ -53,7 +53,7 @@ services: CELERY_RDB_HOST: 0.0.0.0 EXTRA_GROUP_QUEUES: charlie volumes: - - "../:/tower_devel" + - "../:/awx_devel" rabbitmq_1: image: gcr.io/ansible-tower-engineering/rabbit_cluster_node:latest hostname: rabbitmq_1 From 86fb6116a051386202f774ad32db5bd52d8925c4 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 20 Jul 2017 17:28:51 -0400 Subject: [PATCH 035/342] Refactor all Less to manage import order itself --- awx/static/tower.vendor.css | 0 awx/ui/Gruntfile.js | 9 +- .../legacy-styles/angular-scheduler.less | 2 - awx/ui/client/legacy-styles/ansible-ui.less | 25 --- awx/ui/client/legacy-styles/breadcrumbs.less | 2 - awx/ui/client/legacy-styles/codemirror.less | 2 - awx/ui/client/legacy-styles/dashboard.less | 3 - awx/ui/client/legacy-styles/forms.less | 2 - awx/ui/client/legacy-styles/jPushMenu.less | 2 - awx/ui/client/legacy-styles/job-details.less | 2 - awx/ui/client/legacy-styles/jobs.less | 3 - .../legacy-styles/jquery-ui-overrides.less | 4 - awx/ui/client/legacy-styles/lists.less | 3 - awx/ui/client/legacy-styles/main-layout.less | 3 - awx/ui/client/legacy-styles/stdout.less | 3 - awx/ui/client/legacy-styles/text-label.less | 2 - ..._temporary-overrides.less => _resets.less} | 0 ...textual-variables.less => _variables.less} | 0 awx/ui/client/lib/theme/index.less | 175 +++++++++++++++++- awx/ui/client/src/about/about.block.less | 3 - awx/ui/client/src/access/add-rbac.block.less | 2 - .../rbac-role-column/roleList.block.less | 1 - .../streamDetailModal.block.less | 2 - .../src/bread-crumb/bread-crumb.block.less | 2 - .../configuration/configuration.block.less | 3 - .../src/credentials/ownerList.block.less | 3 - awx/ui/client/src/footer/footer.block.less | 1 - .../counts/dashboard-counts.block.less | 2 - .../src/home/dashboard/dashboard.block.less | 1 - .../graphs/dashboard-graphs.block.less | 2 - .../dashboard/lists/dashboard-list.block.less | 2 - .../capacity-bar/capacity-bar.block.less | 4 +- .../instance-groups/instance-group.block.less | 4 +- .../inventories/insights/insights.block.less | 2 - .../host-filter-modal.block.less | 2 - .../associate-groups.block.less | 2 - .../associate-hosts.block.less | 2 - .../host-event/host-event.block.less | 4 - .../host-status-bar.block.less | 2 - .../job-results-stdout.block.less | 2 - .../src/job-results/job-results.block.less | 4 - .../job-submission/job-submission.block.less | 3 - awx/ui/client/src/license/license.block.less | 3 - .../login/loginModal/loginModal.block.less | 3 - ...block.less => loginModalNotice.block.less} | 2 - .../thirdPartySignOn.block.less | 2 - .../client/src/main-menu/main-menu.block.less | 3 - .../management-jobs/card/mgmtcards.block.less | 3 - .../notifications/notifications.block.less | 2 - .../linkout/addUsers/addUsers.block.less | 2 - .../src/organizations/orgcards.block.less | 1 - .../src/portal-mode/portal-mode.block.less | 2 - .../repeatFrequencyOptions.block.less | 2 - .../src/scheduler/scheduleToggle.block.less | 2 - .../src/scheduler/schedulerForm.block.less | 2 - .../scheduler/schedulerFormDetail.block.less | 2 - .../src/scheduler/schedulertime.block.less | 2 - .../src/scheduler/spinnerInput.block.less | 2 - .../src/setup-menu/hover-icon.block.less | 2 - .../src/setup-menu/setup-item.block.less | 3 - .../src/setup-menu/setup-menu.block.less | 1 - .../client/src/shared/bootstrap-settings.less | 1 - awx/ui/client/src/shared/branding/colors.less | 2 - awx/ui/client/src/shared/button.block.less | 2 - .../shared/download-standard-out.block.less | 1 - .../instance-groups.block.less | 2 - awx/ui/client/src/shared/modal/modal.less | 2 - .../multi-select-preview.block.less | 2 - .../src/shared/paginate/paginate.block.less | 3 - awx/ui/client/src/shared/prompt/prompt.less | 2 - awx/ui/client/src/shared/utilities/icons.less | 2 - .../src/smart-status/smart-status.block.less | 2 - .../src/standard-out/standard-out.block.less | 3 - .../date-picker/date-picker.block.less | 3 - .../fact-data-table.block.less | 1 - .../fact-module-filter.block.less | 4 - .../fact-module-pickers.block.less | 2 - .../system-tracking-container.block.less | 1 - .../multi-credential.block.less | 2 - .../templates/labels/labelsList.block.less | 1 - .../workflow-chart/workflow-chart.block.less | 2 - .../workflow-controls.block.less | 2 - .../workflow-maker/workflow-maker.block.less | 4 +- awx/ui/client/src/tooltip/tooltip.block.less | 1 - .../workflow-results.block.less | 4 - .../workflow-status-bar.block.less | 2 - awx/ui/grunt-tasks/concat.js | 17 ++ awx/ui/grunt-tasks/concurrent.js | 2 +- awx/ui/grunt-tasks/copy.js | 27 +++ awx/ui/grunt-tasks/cssmin.js | 22 +++ awx/ui/grunt-tasks/less.js | 33 ++-- awx/ui/npm-shrinkwrap.json | 44 +++++ awx/ui/package.json | 1 + awx/ui/templates/ui/index.html | 13 +- 94 files changed, 307 insertions(+), 243 deletions(-) create mode 100644 awx/static/tower.vendor.css rename awx/ui/client/lib/theme/{_temporary-overrides.less => _resets.less} (100%) rename awx/ui/client/lib/theme/{_contextual-variables.less => _variables.less} (100%) rename awx/ui/client/src/login/loginModal/{loginMotalNotice.block.less => loginModalNotice.block.less} (90%) create mode 100644 awx/ui/grunt-tasks/concat.js create mode 100644 awx/ui/grunt-tasks/cssmin.js diff --git a/awx/static/tower.vendor.css b/awx/static/tower.vendor.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/awx/ui/Gruntfile.js b/awx/ui/Gruntfile.js index 8dc07e896a..5080357e30 100644 --- a/awx/ui/Gruntfile.js +++ b/awx/ui/Gruntfile.js @@ -30,7 +30,13 @@ module.exports = function(grunt) { 'clean:tmp', 'clean:static', 'concurrent:dev', - 'sync', + 'copy:icons', + 'copy:fonts', + 'concat:css', + 'cssmin:vendor', + 'less:dev', + 'cssmin:source', + 'sync' ]); grunt.registerTask('devNoSync', [ @@ -45,5 +51,4 @@ module.exports = function(grunt) { 'webpack:prod', 'concurrent:prod', ]); - }; diff --git a/awx/ui/client/legacy-styles/angular-scheduler.less b/awx/ui/client/legacy-styles/angular-scheduler.less index 1216aa2eb1..2dfe0aefe3 100644 --- a/awx/ui/client/legacy-styles/angular-scheduler.less +++ b/awx/ui/client/legacy-styles/angular-scheduler.less @@ -9,8 +9,6 @@ #schedules-form-container -inventory group add/edit dialog */ - @import './client/src/shared/branding/colors.less'; - #schedules-tab { position: relative; top: 0; diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 2f750ffef5..4f1df1be6d 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -21,31 +21,6 @@ src: url(/static/assets/OpenSans-Bold.ttf); } -@import "./client/src/shared/branding/colors.less"; -@import "fonts.less"; -@import "main-layout.less"; -@import "animations.less"; -@import "jquery-ui-overrides.less"; -@import "codemirror.less"; -@import "angular-scheduler.less"; -@import "log-viewer.less"; -@import "event-viewer.less"; -@import "job-details.less"; -@import "jobs.less"; -@import "inventory-edit.less"; -@import "breadcrumbs.less"; -@import "stdout.less"; -@import "lists.less"; -@import "forms.less"; -@import "dashboard.less"; -@import "jPushMenu.less"; -@import "survey-maker.less"; -@import "text-label.less"; -@import "./bootstrap-datepicker.less"; -@import "./client/src/shared/branding/colors.default.less"; -@import "./client/assets/variables.less"; -// Bootstrap default overrides -@import "./client/src/shared/bootstrap-settings.less"; /* Bootstrap fix that's causing a right margin to appear whenver a modal is opened */ body.modal-open { diff --git a/awx/ui/client/legacy-styles/breadcrumbs.less b/awx/ui/client/legacy-styles/breadcrumbs.less index 6863c2c211..a61c55dcbd 100644 --- a/awx/ui/client/legacy-styles/breadcrumbs.less +++ b/awx/ui/client/legacy-styles/breadcrumbs.less @@ -9,8 +9,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; - .ansible-breadcrumb { list-style: none; overflow: hidden; diff --git a/awx/ui/client/legacy-styles/codemirror.less b/awx/ui/client/legacy-styles/codemirror.less index 94230506c6..439291d4a6 100644 --- a/awx/ui/client/legacy-styles/codemirror.less +++ b/awx/ui/client/legacy-styles/codemirror.less @@ -5,8 +5,6 @@ * */ - @import "./client/src/shared/branding/colors.default.less"; - .CodeMirror { height: auto; overflow-x: auto; diff --git a/awx/ui/client/legacy-styles/dashboard.less b/awx/ui/client/legacy-styles/dashboard.less index 87a8126a80..2b1ee7c00e 100644 --- a/awx/ui/client/legacy-styles/dashboard.less +++ b/awx/ui/client/legacy-styles/dashboard.less @@ -7,9 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; - - .graph-wrapper { width: 100%; } diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index befb8d760a..0c3f397b98 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -7,8 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.default.less"; - .noselect { -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Chrome/Safari/Opera */ diff --git a/awx/ui/client/legacy-styles/jPushMenu.less b/awx/ui/client/legacy-styles/jPushMenu.less index 6ca4108fe8..7f919f5ef5 100644 --- a/awx/ui/client/legacy-styles/jPushMenu.less +++ b/awx/ui/client/legacy-styles/jPushMenu.less @@ -7,8 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; - .cbp-spmenu { background: #E8E8E8; position: fixed; diff --git a/awx/ui/client/legacy-styles/job-details.less b/awx/ui/client/legacy-styles/job-details.less index ed9b96fb57..5d79971bfc 100644 --- a/awx/ui/client/legacy-styles/job-details.less +++ b/awx/ui/client/legacy-styles/job-details.less @@ -7,8 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; - @failed-hosts-color: @red; @successful-hosts-color: @green; diff --git a/awx/ui/client/legacy-styles/jobs.less b/awx/ui/client/legacy-styles/jobs.less index 7f7c805eaa..887a979d5a 100644 --- a/awx/ui/client/legacy-styles/jobs.less +++ b/awx/ui/client/legacy-styles/jobs.less @@ -7,9 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; - - #jobs-page { .jobs-list-container { diff --git a/awx/ui/client/legacy-styles/jquery-ui-overrides.less b/awx/ui/client/legacy-styles/jquery-ui-overrides.less index 811b34ca28..c214c55979 100644 --- a/awx/ui/client/legacy-styles/jquery-ui-overrides.less +++ b/awx/ui/client/legacy-styles/jquery-ui-overrides.less @@ -7,10 +7,6 @@ * look closer to Twitter Bootstrap * */ - -@import "./client/src/shared/branding/colors.less"; -@import "./client/src/shared/branding/colors.default.less"; - table.ui-datepicker-calendar { background-color: @well; } diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index a0f22e53d5..92dabcc1d3 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -7,9 +7,6 @@ * */ - @import "./client/src/shared/branding/colors.default.less"; - - table, tbody { border-collapse: collapse; } diff --git a/awx/ui/client/legacy-styles/main-layout.less b/awx/ui/client/legacy-styles/main-layout.less index 038b9b329b..0c4e4a9148 100644 --- a/awx/ui/client/legacy-styles/main-layout.less +++ b/awx/ui/client/legacy-styles/main-layout.less @@ -7,9 +7,6 @@ * */ -@import "./client/src/shared/branding/colors.less"; -@import "./client/src/shared/branding/colors.default.less"; - html { height: 100%; } body { diff --git a/awx/ui/client/legacy-styles/stdout.less b/awx/ui/client/legacy-styles/stdout.less index 68ac91490c..061e90383a 100644 --- a/awx/ui/client/legacy-styles/stdout.less +++ b/awx/ui/client/legacy-styles/stdout.less @@ -5,9 +5,6 @@ * */ - @import "./client/src/shared/branding/colors.default.less"; - @import "./client/src/shared/branding/colors.less"; - #jobs-stdout { margin-bottom: 0px; diff --git a/awx/ui/client/legacy-styles/text-label.less b/awx/ui/client/legacy-styles/text-label.less index d4805c7459..eca6e83cf4 100644 --- a/awx/ui/client/legacy-styles/text-label.less +++ b/awx/ui/client/legacy-styles/text-label.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .host-disabled-label { &:after { display: inline-block; diff --git a/awx/ui/client/lib/theme/_temporary-overrides.less b/awx/ui/client/lib/theme/_resets.less similarity index 100% rename from awx/ui/client/lib/theme/_temporary-overrides.less rename to awx/ui/client/lib/theme/_resets.less diff --git a/awx/ui/client/lib/theme/_contextual-variables.less b/awx/ui/client/lib/theme/_variables.less similarity index 100% rename from awx/ui/client/lib/theme/_contextual-variables.less rename to awx/ui/client/lib/theme/_variables.less diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less index 14c6bc1890..3a5acefae2 100644 --- a/awx/ui/client/lib/theme/index.less +++ b/awx/ui/client/lib/theme/index.less @@ -1,16 +1,179 @@ -// App-wide styles +// Dependency Variables +@import '../../../node_modules/components-font-awesome/less/variables'; + +// App-specific Legacy Variables +@import '../../src/shared/branding/colors.default.less'; +@import '../../src/shared/branding/colors'; + +/** + * Override Variables + * + * NOTE: Used in conditional build scenarios and will need to persist after any refactoring effort. + */ +@import '../../assets/variables'; + +/** + * Legacy Styles + * + * NOTE: Styles below are a mix of 3rd-party dependencies and in-house code. For the 3rd-party + * stuff, we'd be better off managing them via npm where possible. + */ +@import '../../legacy-styles/fonts'; +@import '../../legacy-styles/main-layout'; +@import '../../legacy-styles/animations'; +@import '../../legacy-styles/jquery-ui-overrides'; +@import '../../legacy-styles/codemirror'; +@import '../../legacy-styles/angular-scheduler'; +@import '../../legacy-styles/log-viewer'; +@import '../../legacy-styles/event-viewer'; +@import '../../legacy-styles/job-details'; +@import '../../legacy-styles/jobs'; +@import '../../legacy-styles/inventory-edit'; +@import '../../legacy-styles/breadcrumbs'; +@import '../../legacy-styles/stdout'; +@import '../../legacy-styles/lists'; +@import '../../legacy-styles/forms'; +@import '../../legacy-styles/dashboard'; +@import '../../legacy-styles/jPushMenu'; +@import '../../legacy-styles/survey-maker'; +@import '../../legacy-styles/text-label'; +@import '../../legacy-styles/bootstrap-datepicker'; +@import '../../legacy-styles/ansible-ui'; + +// Dependency Style Overrides +@import '../../src/shared/bootstrap-settings'; + +// Legacy Utilities +@import '../../src/shared/utilities/alerts'; +@import '../../src/shared/utilities/hidden'; +@import '../../src/shared/utilities/icons'; +@import '../../src/shared/utilities/layer'; +@import '../../src/shared/utilities/truncated-text'; +@import '../../src/shared/utilities/unbold'; +@import '../../src/shared/utilities/wordwrap'; + +// Legacy Layout +@import '../../src/shared/layouts/one-plus-one'; +@import '../../src/shared/layouts/one-plus-two'; + +/** + * Legacy Features + * + * NOTE: "dot" namespacing interferes with Less' ability to infer the .less suffix, so it's + * explicitly added to the import statements below. + */ +@import '../../src/about/about.block.less'; +@import '../../src/access/rbac-role-column/roleList.block.less'; +@import '../../src/access/add-rbac.block.less'; +@import '../../src/activity-stream/streamDetailModal/streamDetailModal.block.less'; +@import '../../src/activity-stream/activitystream.block.less'; +@import '../../src/bread-crumb/bread-crumb.block.less'; +@import '../../src/configuration/configuration.block.less'; +@import '../../src/credentials/ownerList.block.less'; +@import '../../src/footer/footer.block.less'; +@import '../../src/home/dashboard/counts/dashboard-counts.block.less'; +@import '../../src/home/dashboard/graphs/dashboard-graphs.block.less'; +@import '../../src/home/dashboard/lists/dashboard-list.block.less'; +@import '../../src/home/dashboard/dashboard.block.less'; +@import '../../src/instance-groups/capacity-bar/capacity-bar.block.less'; +@import '../../src/instance-groups/instance-group.block.less'; +@import '../../src/inventories-hosts/inventories/insights/insights.block.less'; +@import '../../src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less'; +@import '../../src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less'; +@import '../../src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.block.less'; +@import '../../src/inventories-hosts/inventories/inventories.block.less'; +@import '../../src/inventories-hosts/shared/associate-groups/associate-groups.block.less'; +@import '../../src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less'; +@import '../../src/job-results/host-event/host-event.block.less'; +@import '../../src/job-results/host-status-bar/host-status-bar.block.less'; +@import '../../src/job-results/job-results-stdout/job-results-stdout.block.less'; +@import '../../src/job-results/job-results.block.less'; +@import '../../src/job-submission/job-submission.block.less'; +@import '../../src/license/license.block.less'; +@import '../../src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less'; +@import '../../src/login/loginModal/loginModal.block.less'; +@import '../../src/login/loginModal/loginModalNotice.block.less'; +@import '../../src/main-menu/main-menu.block.less'; +@import '../../src/management-jobs/card/mgmtcards.block.less'; +@import '../../src/notifications/notifications.block.less'; +@import '../../src/organizations/linkout/addUsers/addUsers.block.less'; +@import '../../src/organizations/orgcards.block.less'; +@import '../../src/portal-mode/portal-mode.block.less'; +@import '../../src/scheduler/repeatFrequencyOptions.block.less'; +@import '../../src/scheduler/schedulerForm.block.less'; +@import '../../src/scheduler/schedulerFormDetail.block.less'; +@import '../../src/scheduler/schedulertime.block.less'; +@import '../../src/scheduler/scheduleToggle.block.less'; +@import '../../src/scheduler/spinnerInput.block.less'; +@import '../../src/setup-menu/hover-icon.block.less'; +@import '../../src/setup-menu/setup-extra.block.less'; +@import '../../src/setup-menu/setup-item.block.less'; +@import '../../src/setup-menu/setup-menu.block.less'; +@import '../../src/shared/container/container.block.less'; +@import '../../src/shared/detail-nav/detail-nav.block.less'; +@import '../../src/shared/icon/icon.block.less'; +@import '../../src/shared/instance-groups-multiselect/instance-groups.block.less'; +@import '../../src/shared/lookup/lookup-modal.block.less'; +@import '../../src/shared/modal/modal'; +@import '../../src/shared/multi-select-preview/multi-select-preview.block.less'; +@import '../../src/shared/paginate/paginate.block.less'; +@import '../../src/shared/prompt/prompt'; +@import '../../src/shared/smart-search/smart-search.block.less'; +@import '../../src/shared/button.block.less'; +@import '../../src/shared/download-standard-out.block.less'; +@import '../../src/shared/media-object.block.less'; +@import '../../src/shared/text-label'; +@import '../../src/smart-status/smart-status.block.less'; +@import '../../src/standard-out/standard-out.block.less'; +@import '../../src/system-tracking/date-picker/date-picker.block.less'; +@import '../../src/system-tracking/fact-data-table/fact-data-table.block.less'; +@import '../../src/system-tracking/fact-module-filter.block.less'; +@import '../../src/system-tracking/fact-module-pickers.block.less'; +@import '../../src/system-tracking/system-tracking-container.block.less'; +@import '../../src/templates/job_templates/multi-credential/multi-credential.block.less'; +@import '../../src/templates/labels/labelsList.block.less'; +@import '../../src/templates/survey-maker/survey-maker.block.less'; +@import '../../src/templates/survey-maker/survey-maker.block.less'; +@import '../../src/templates/survey-maker/shared/survey-controls.block.less'; +@import '../../src/templates/survey-maker/survey-maker.block.less'; +@import '../../src/templates/workflows/workflow-chart/workflow-chart.block.less'; +@import '../../src/templates/workflows/workflow-controls/workflow-controls.block.less'; +@import '../../src/templates/workflows/workflow-maker/workflow-maker.block.less'; +@import '../../src/tooltip/tooltip.block.less'; +@import '../../src/workflow-results/workflow-status-bar/workflow-status-bar.block.less'; +@import '../../src/workflow-results/workflow-results.block.less'; + + +/** + * App-wide style + * + * NOTE: Variables, mixins, and classes below are useful in more than one place across the + * application. When working with Less, if the need for a variable, mixin, class, etc exists in + * more than one location, take a moment to move it to this more general location for easy reuse + * and to avoid duplication. + */ @import '_base-variables'; -@import '_contextual-variables'; +@import '_variables'; @import '_mixins'; @import '_utility'; @import '_global'; -// Aggregated component and feature specific styles +/** + * Component and Feature style + * + * NOTE: These index files are aggregation points for components and features. To view the more + * granular imports, view the contents of these files. Variables, classes, etc defined within + * these specific files ought to have no use elsewhere. As we shift to leverage components, very + * few feature-specific styles will exist. + */ @import '../components/_index'; @import '../../features/_index'; /* - * Temporary overrides used only during the transition away from old style - * structure to new style structure. Overrides unwanted/uneeded rules. + * Resets + * + * NOTE: In some cases, the legacy classes override dependency styles explicitly. In those cases, + * it's necessary to override the overrides. This particular file will only be relevant during + * the transition. */ -@import '_temporary-overrides'; +@import '_resets'; diff --git a/awx/ui/client/src/about/about.block.less b/awx/ui/client/src/about/about.block.less index 6acf5e76a5..d4a1788564 100644 --- a/awx/ui/client/src/about/about.block.less +++ b/awx/ui/client/src/about/about.block.less @@ -1,7 +1,4 @@ /** @define About */ -@import "./client/src/shared/branding/colors.default.less"; -@import "./client/assets/variables.less"; - .About-ansibleVersion, .About-cowsayCode { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; diff --git a/awx/ui/client/src/access/add-rbac.block.less b/awx/ui/client/src/access/add-rbac.block.less index 66b150501a..98d3e44eb4 100644 --- a/awx/ui/client/src/access/add-rbac.block.less +++ b/awx/ui/client/src/access/add-rbac.block.less @@ -1,5 +1,3 @@ -@import "../shared/branding/colors.default.less"; - /** @define AddPermissions */ .AddPermissions-backDrop { diff --git a/awx/ui/client/src/access/rbac-role-column/roleList.block.less b/awx/ui/client/src/access/rbac-role-column/roleList.block.less index 40b76717a3..98638802c3 100644 --- a/awx/ui/client/src/access/rbac-role-column/roleList.block.less +++ b/awx/ui/client/src/access/rbac-role-column/roleList.block.less @@ -1,5 +1,4 @@ /** @define RoleList */ -@import "../../shared/branding/colors.default.less"; .RoleList { display: flex; diff --git a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less index 9e0cc73720..dff4479904 100644 --- a/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less +++ b/awx/ui/client/src/activity-stream/streamDetailModal/streamDetailModal.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .StreamDetail-actionButton { padding: 4px 25px!important; } diff --git a/awx/ui/client/src/bread-crumb/bread-crumb.block.less b/awx/ui/client/src/bread-crumb/bread-crumb.block.less index f75894afeb..f0e9902a9b 100644 --- a/awx/ui/client/src/bread-crumb/bread-crumb.block.less +++ b/awx/ui/client/src/bread-crumb/bread-crumb.block.less @@ -1,5 +1,3 @@ -@import "../shared/branding/colors.default.less"; - /** @define BreadCrumb */ .BreadCrumb { diff --git a/awx/ui/client/src/configuration/configuration.block.less b/awx/ui/client/src/configuration/configuration.block.less index 43e7dcd637..2f32e9ed56 100644 --- a/awx/ui/client/src/configuration/configuration.block.less +++ b/awx/ui/client/src/configuration/configuration.block.less @@ -1,6 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; -@import "../shared/branding/colors.less"; - .Form-resetValue, .Form-resetAll { text-transform: uppercase; font-weight: normal; diff --git a/awx/ui/client/src/credentials/ownerList.block.less b/awx/ui/client/src/credentials/ownerList.block.less index 64f76db17b..47ce63124e 100644 --- a/awx/ui/client/src/credentials/ownerList.block.less +++ b/awx/ui/client/src/credentials/ownerList.block.less @@ -1,6 +1,3 @@ -/** @define OwnerList */ -@import "./client/src/shared/branding/colors.default.less"; - .OwnerList { display: flex; flex-wrap: wrap; diff --git a/awx/ui/client/src/footer/footer.block.less b/awx/ui/client/src/footer/footer.block.less index a00ef1e35a..b626f5d4e8 100644 --- a/awx/ui/client/src/footer/footer.block.less +++ b/awx/ui/client/src/footer/footer.block.less @@ -1,5 +1,4 @@ /** @define DashboardCounts */ -@import "./client/src/shared/branding/colors.default.less"; .Footer { height: 40px; diff --git a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less index 8d85f93bf5..4c548c20d3 100644 --- a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less +++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - /** @define DashboardCounts */ .DashboardCounts { diff --git a/awx/ui/client/src/home/dashboard/dashboard.block.less b/awx/ui/client/src/home/dashboard/dashboard.block.less index f1b6446fa6..c5bb5a2082 100644 --- a/awx/ui/client/src/home/dashboard/dashboard.block.less +++ b/awx/ui/client/src/home/dashboard/dashboard.block.less @@ -1,5 +1,4 @@ /** @define Dashboard */ -@import "../../shared/branding/colors.default.less"; .Dashboard { display: flex; diff --git a/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less index fb9c513852..c0821395bb 100644 --- a/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less +++ b/awx/ui/client/src/home/dashboard/graphs/dashboard-graphs.block.less @@ -1,7 +1,5 @@ /** @define DashboardGraphs */ -@import "../../../shared/branding/colors.default.less"; - .DashboardGraphs { margin-top: 20px; border: solid 1px @db-panel-border; diff --git a/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less index 749878527f..55b14f5f3f 100644 --- a/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less +++ b/awx/ui/client/src/home/dashboard/lists/dashboard-list.block.less @@ -1,7 +1,5 @@ /** @define DashboardList */ -@import "../../../shared/branding/colors.default.less"; - .DashboardList { flex: 1; } diff --git a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less index f8e9af45ee..06b595066e 100644 --- a/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less +++ b/awx/ui/client/src/instance-groups/capacity-bar/capacity-bar.block.less @@ -1,5 +1,3 @@ -@import "../../shared/branding/colors.default.less"; - capacity-bar { width: 50%; @@ -25,4 +23,4 @@ capacity-bar { .CapacityBar-consumed { flex: 0 0 auto; } -} \ No newline at end of file +} diff --git a/awx/ui/client/src/instance-groups/instance-group.block.less b/awx/ui/client/src/instance-groups/instance-group.block.less index 8588e0f2de..1feaff9b38 100644 --- a/awx/ui/client/src/instance-groups/instance-group.block.less +++ b/awx/ui/client/src/instance-groups/instance-group.block.less @@ -1,5 +1,3 @@ -@import "../shared/branding/colors.default.less"; - .InstanceGroups { .BreadCrumb-menuLinkImage:hover { @@ -53,4 +51,4 @@ .List-tableRow .List-titleBadge { margin: 0 0 0 5px; } -} \ No newline at end of file +} diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less index 8ccddf8c89..6f3684ef59 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less +++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - .InsightsLastCheck{ display: flex; justify-content: flex-end; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.block.less b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.block.less index aaa0d842d4..b2bd01bf05 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.block.less +++ b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.block.less @@ -1,5 +1,3 @@ -@import "../../../../../shared/branding/colors.default.less"; - .HostFilterModal-tableRow:hover { background-color: @default-bg; } diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less index 9f764b92a2..6aa733a6e2 100644 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less +++ b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - .AssociateGroups-modalBody { padding-top: 0px; } diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less index 4d176103cd..68ea703369 100644 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less +++ b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - .AssociateHosts-modalBody { padding-top: 0px; } diff --git a/awx/ui/client/src/job-results/host-event/host-event.block.less b/awx/ui/client/src/job-results/host-event/host-event.block.less index 3fcbd35655..e06ab5626a 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.block.less +++ b/awx/ui/client/src/job-results/host-event/host-event.block.less @@ -1,7 +1,3 @@ -@import "./client/src/shared/branding/colors.less"; -@import "./client/src/shared/branding/colors.default.less"; -@import "./client/src/shared/layouts/one-plus-two.less"; - .noselect { -webkit-touch-callout: none; /* iOS Safari */ -webkit-user-select: none; /* Chrome/Safari/Opera */ diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less index c35e905777..5eb948eaad 100644 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less @@ -1,5 +1,3 @@ -@import '../../shared/branding/colors.default.less'; - .HostStatusBar { display: flex; flex: 0 0 auto; diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index ad98ec351e..1149deb007 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -1,5 +1,3 @@ -@import '../../shared/branding/colors.default.less'; - @breakpoint-md: 1200px; .JobResultsStdOut { diff --git a/awx/ui/client/src/job-results/job-results.block.less b/awx/ui/client/src/job-results/job-results.block.less index 4ee845e811..47a6c594ae 100644 --- a/awx/ui/client/src/job-results/job-results.block.less +++ b/awx/ui/client/src/job-results/job-results.block.less @@ -1,7 +1,3 @@ -@import '../shared/branding/colors.less'; -@import '../shared/branding/colors.default.less'; -@import '../shared/layouts/one-plus-two.less'; - @breakpoint-md: 1200px; .JobResults { diff --git a/awx/ui/client/src/job-submission/job-submission.block.less b/awx/ui/client/src/job-submission/job-submission.block.less index c0570e30c3..350ffc4f86 100644 --- a/awx/ui/client/src/job-submission/job-submission.block.less +++ b/awx/ui/client/src/job-submission/job-submission.block.less @@ -1,6 +1,3 @@ -@import '../shared/branding/colors.less'; -@import '../shared/branding/colors.default.less'; - .JobSubmission { padding: 20px!important; display: none; diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less index d50d92afc7..aa306a1983 100644 --- a/awx/ui/client/src/license/license.block.less +++ b/awx/ui/client/src/license/license.block.less @@ -3,9 +3,6 @@ * .ModuleName-component-subComponent * Naming describes components of the view */ -@import "./client/src/shared/branding/colors.default.less"; -@import "./client/src/shared/layouts/one-plus-two.less"; - .License-container{ .OnePlusTwo-container; } diff --git a/awx/ui/client/src/login/loginModal/loginModal.block.less b/awx/ui/client/src/login/loginModal/loginModal.block.less index 4aae364bcc..6787e7acc2 100644 --- a/awx/ui/client/src/login/loginModal/loginModal.block.less +++ b/awx/ui/client/src/login/loginModal/loginModal.block.less @@ -1,6 +1,3 @@ -@import "../../shared/branding/colors.default.less"; -@import "./client/assets/variables.less"; - /** @define LoginModal */ .LoginModal-backDrop { width: 100vw; diff --git a/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less b/awx/ui/client/src/login/loginModal/loginModalNotice.block.less similarity index 90% rename from awx/ui/client/src/login/loginModal/loginMotalNotice.block.less rename to awx/ui/client/src/login/loginModal/loginModalNotice.block.less index df7de83de6..57cb035e8c 100644 --- a/awx/ui/client/src/login/loginModal/loginMotalNotice.block.less +++ b/awx/ui/client/src/login/loginModal/loginModalNotice.block.less @@ -1,5 +1,3 @@ -@import "../../shared/branding/colors.default.less"; - /** @define LoginModalNotice */ .LoginModalNotice { font-size: 12px; diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less index f3f50157c0..d61006bf50 100644 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less +++ b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - /** @define ThirdPartySignOn */ .ThirdPartySignOn { diff --git a/awx/ui/client/src/main-menu/main-menu.block.less b/awx/ui/client/src/main-menu/main-menu.block.less index 6dec896079..9a17da4dfc 100644 --- a/awx/ui/client/src/main-menu/main-menu.block.less +++ b/awx/ui/client/src/main-menu/main-menu.block.less @@ -1,6 +1,3 @@ -@import "../shared/branding/colors.default.less"; -@import "./client/assets/variables.less"; - /** @define MainMenu */ .MainMenu { diff --git a/awx/ui/client/src/management-jobs/card/mgmtcards.block.less b/awx/ui/client/src/management-jobs/card/mgmtcards.block.less index f79a0f3038..cc601f814e 100644 --- a/awx/ui/client/src/management-jobs/card/mgmtcards.block.less +++ b/awx/ui/client/src/management-jobs/card/mgmtcards.block.less @@ -1,6 +1,3 @@ -/** @define MgmtCards */ -@import './client/src/shared/branding/colors.default.less'; - .MgmtCards { display: flex; flex-flow: row wrap; diff --git a/awx/ui/client/src/notifications/notifications.block.less b/awx/ui/client/src/notifications/notifications.block.less index 54b71031d5..27ad2bbc56 100644 --- a/awx/ui/client/src/notifications/notifications.block.less +++ b/awx/ui/client/src/notifications/notifications.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .NotificationsForm-typeSelect{ flex: none; } diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less index 0e5c14d3d3..3ebf847364 100644 --- a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - /** @define AddUsers */ .AddUsers-backDrop { diff --git a/awx/ui/client/src/organizations/orgcards.block.less b/awx/ui/client/src/organizations/orgcards.block.less index bd3a1e8a29..370d1dd255 100644 --- a/awx/ui/client/src/organizations/orgcards.block.less +++ b/awx/ui/client/src/organizations/orgcards.block.less @@ -1,5 +1,4 @@ /** @define OrgCards */ -@import '../shared/branding/colors.default.less'; .OrgCards { display: flex; diff --git a/awx/ui/client/src/portal-mode/portal-mode.block.less b/awx/ui/client/src/portal-mode/portal-mode.block.less index 4c011125b9..747ceab325 100644 --- a/awx/ui/client/src/portal-mode/portal-mode.block.less +++ b/awx/ui/client/src/portal-mode/portal-mode.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/layouts/one-plus-one.less"; - .PortalMode-container{ display: flex; flex-direction: row; diff --git a/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less b/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less index 7f22ee8535..ae9dafb772 100644 --- a/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less +++ b/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less @@ -1,7 +1,5 @@ /** @define RepeatFrequencyOptions */ -@import "./client/src/shared/branding/colors.default.less"; - .RepeatFrequencyOptions { width: ~"calc(100% + 21px)"; padding: 20px; diff --git a/awx/ui/client/src/scheduler/scheduleToggle.block.less b/awx/ui/client/src/scheduler/scheduleToggle.block.less index 5ce3944f60..bb30e7e60f 100644 --- a/awx/ui/client/src/scheduler/scheduleToggle.block.less +++ b/awx/ui/client/src/scheduler/scheduleToggle.block.less @@ -1,7 +1,5 @@ /** @define ScheduleToggle */ -@import "./client/src/shared/branding/colors.default.less"; - .ScheduleToggle { border-radius: 5px; border: 1px solid @default-link; diff --git a/awx/ui/client/src/scheduler/schedulerForm.block.less b/awx/ui/client/src/scheduler/schedulerForm.block.less index 872dcf8904..6512efa0f6 100644 --- a/awx/ui/client/src/scheduler/schedulerForm.block.less +++ b/awx/ui/client/src/scheduler/schedulerForm.block.less @@ -1,7 +1,5 @@ /** @define SchedulerForm */ -@import "./client/src/shared/branding/colors.default.less"; - .SchedulerForm-formGroup { padding-right: 0px; } diff --git a/awx/ui/client/src/scheduler/schedulerFormDetail.block.less b/awx/ui/client/src/scheduler/schedulerFormDetail.block.less index 58aa88bd7c..0b6b63e2ad 100644 --- a/awx/ui/client/src/scheduler/schedulerFormDetail.block.less +++ b/awx/ui/client/src/scheduler/schedulerFormDetail.block.less @@ -1,7 +1,5 @@ /** @define SchedulerFormDetail */ -@import "./client/src/shared/branding/colors.default.less"; - .SchedulerFormDetail-container { padding: 15px; border: 1px solid @default-border; diff --git a/awx/ui/client/src/scheduler/schedulertime.block.less b/awx/ui/client/src/scheduler/schedulertime.block.less index 424f01c85d..64a87a46e7 100644 --- a/awx/ui/client/src/scheduler/schedulertime.block.less +++ b/awx/ui/client/src/scheduler/schedulertime.block.less @@ -1,7 +1,5 @@ /** @define SchedulerTime */ -@import "./client/src/shared/branding/colors.default.less"; - .SchedulerTime { display: flex; flex-wrap: wrap; diff --git a/awx/ui/client/src/scheduler/spinnerInput.block.less b/awx/ui/client/src/scheduler/spinnerInput.block.less index f13d628ff7..1f9a44437c 100644 --- a/awx/ui/client/src/scheduler/spinnerInput.block.less +++ b/awx/ui/client/src/scheduler/spinnerInput.block.less @@ -1,7 +1,5 @@ /** @define SpinnerInput */ -@import "./client/src/shared/branding/colors.default.less"; - .SpinnerInput { width: ~"calc(100% - 26px)"; } diff --git a/awx/ui/client/src/setup-menu/hover-icon.block.less b/awx/ui/client/src/setup-menu/hover-icon.block.less index b1042651f2..a5660141df 100644 --- a/awx/ui/client/src/setup-menu/hover-icon.block.less +++ b/awx/ui/client/src/setup-menu/hover-icon.block.less @@ -1,7 +1,5 @@ /** @define HoverIcon */ -@import '../shared/branding/colors.less'; - .HoverIcon { @media screen and (max-width: 571px) { &--onlyLarge { diff --git a/awx/ui/client/src/setup-menu/setup-item.block.less b/awx/ui/client/src/setup-menu/setup-item.block.less index ca74e302a7..092c40d171 100644 --- a/awx/ui/client/src/setup-menu/setup-item.block.less +++ b/awx/ui/client/src/setup-menu/setup-item.block.less @@ -1,8 +1,5 @@ /** @define SetupItem */ -@import '../shared/branding/colors.less'; -@import '../shared/branding/colors.default.less'; - .SetupItem { background-color: @panel-bg; border-radius: 5px; diff --git a/awx/ui/client/src/setup-menu/setup-menu.block.less b/awx/ui/client/src/setup-menu/setup-menu.block.less index b830582139..b869976209 100644 --- a/awx/ui/client/src/setup-menu/setup-menu.block.less +++ b/awx/ui/client/src/setup-menu/setup-menu.block.less @@ -1,6 +1,5 @@ /** @define SetupMenu */ -@import "../shared/branding/colors.less"; @menu-breakpoint: 710px; .SetupMenu { diff --git a/awx/ui/client/src/shared/bootstrap-settings.less b/awx/ui/client/src/shared/bootstrap-settings.less index 42e2291988..37cbdc9406 100644 --- a/awx/ui/client/src/shared/bootstrap-settings.less +++ b/awx/ui/client/src/shared/bootstrap-settings.less @@ -1,4 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; .btn-success{ background: @default-succ; border-color: transparent; diff --git a/awx/ui/client/src/shared/branding/colors.less b/awx/ui/client/src/shared/branding/colors.less index cb8a4cc098..f8de1fe955 100644 --- a/awx/ui/client/src/shared/branding/colors.less +++ b/awx/ui/client/src/shared/branding/colors.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - @active-color: #EDF2F2; // #c6e5e5; @black: #171717; @blue: #1778c3; /* logo blue */ diff --git a/awx/ui/client/src/shared/button.block.less b/awx/ui/client/src/shared/button.block.less index eb23cf99d7..039464309b 100644 --- a/awx/ui/client/src/shared/button.block.less +++ b/awx/ui/client/src/shared/button.block.less @@ -1,7 +1,5 @@ /** @define Button */ -@import '../shared/branding/colors.less'; - .Button { &--pseudo { // Make pseudo button diff --git a/awx/ui/client/src/shared/download-standard-out.block.less b/awx/ui/client/src/shared/download-standard-out.block.less index 770e07b447..0efdb3001a 100644 --- a/awx/ui/client/src/shared/download-standard-out.block.less +++ b/awx/ui/client/src/shared/download-standard-out.block.less @@ -1,5 +1,4 @@ /** @define DownloadStandardOut */ -@import "./client/src/shared/branding/colors.default.less"; .DownloadStandardOut { color: @default-bg !important; diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less index befe6c1061..bbfef9de99 100644 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less +++ b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less @@ -1,5 +1,3 @@ -@import "../../shared/branding/colors.default.less"; - #instance-groups-panel { table { overflow: hidden; diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less index 40aa1e6943..9c741f4131 100644 --- a/awx/ui/client/src/shared/modal/modal.less +++ b/awx/ui/client/src/shared/modal/modal.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .Modal-content { display:flex; flex-wrap:wrap; diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less index e0cdc87bcf..2a21ab3f2e 100644 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less +++ b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less @@ -1,5 +1,3 @@ -@import '../branding/colors.default.less'; - .MultiSelectPreview { display: flex; flex: 1 0 auto; diff --git a/awx/ui/client/src/shared/paginate/paginate.block.less b/awx/ui/client/src/shared/paginate/paginate.block.less index 4e33377687..7adcf7c813 100644 --- a/awx/ui/client/src/shared/paginate/paginate.block.less +++ b/awx/ui/client/src/shared/paginate/paginate.block.less @@ -1,8 +1,5 @@ - @import "./client/src/shared/branding/colors.default.less"; - @import "./client/src/shared/branding/colors.less"; // @todo cleanup these messy overrides for styles in ansible-ui.min.css - .Paginate-controls--first a, .Paginate-controls--previous a{ border-radius: 4px 0 0 4px; diff --git a/awx/ui/client/src/shared/prompt/prompt.less b/awx/ui/client/src/shared/prompt/prompt.less index ba6c960a10..18c655de99 100644 --- a/awx/ui/client/src/shared/prompt/prompt.less +++ b/awx/ui/client/src/shared/prompt/prompt.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .Prompt-bodyQuery { margin-bottom: 20px; color: @default-interface-txt; diff --git a/awx/ui/client/src/shared/utilities/icons.less b/awx/ui/client/src/shared/utilities/icons.less index 1c14e725dc..654f4f0800 100644 --- a/awx/ui/client/src/shared/utilities/icons.less +++ b/awx/ui/client/src/shared/utilities/icons.less @@ -1,5 +1,3 @@ -@import "./node_modules/components-font-awesome/less/variables.less"; - /* not bem */ .icon(@icon-var) { diff --git a/awx/ui/client/src/smart-status/smart-status.block.less b/awx/ui/client/src/smart-status/smart-status.block.less index b09429243d..1c9138e331 100644 --- a/awx/ui/client/src/smart-status/smart-status.block.less +++ b/awx/ui/client/src/smart-status/smart-status.block.less @@ -1,6 +1,4 @@ /** @define SmartStatus */ -@import "./client/legacy-styles/animations.less"; -@import "./client/src/shared/branding/colors.default.less"; .SmartStatus-container{ max-width: 165px; diff --git a/awx/ui/client/src/standard-out/standard-out.block.less b/awx/ui/client/src/standard-out/standard-out.block.less index 7867e4d94f..50e9f37468 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/standard-out/standard-out.block.less @@ -1,6 +1,3 @@ -@import "../shared/branding/colors.default.less"; -@import "../shared/layouts/one-plus-one.less"; - /** @define StandardOut */ @breakpoint-md: 1180px; diff --git a/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less b/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less index b5eea18eae..5d880c3bc6 100644 --- a/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less +++ b/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less @@ -1,6 +1,3 @@ -/** @define DatePicker */ -@import "./client/src/shared/branding/colors.default.less"; - .DatePicker { flex: 1 0 auto; display: flex; diff --git a/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less b/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less index b47409d252..62512a73cc 100644 --- a/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less +++ b/awx/ui/client/src/system-tracking/fact-data-table/fact-data-table.block.less @@ -1,5 +1,4 @@ /** @define FactDataTable */ -@import "../../shared/branding/colors.default.less"; .FactDataTable { &-row, &-headingRow, &-groupHeadingRow { diff --git a/awx/ui/client/src/system-tracking/fact-module-filter.block.less b/awx/ui/client/src/system-tracking/fact-module-filter.block.less index df768206b4..23466ebfe9 100644 --- a/awx/ui/client/src/system-tracking/fact-module-filter.block.less +++ b/awx/ui/client/src/system-tracking/fact-module-filter.block.less @@ -1,9 +1,5 @@ /** @define FactModuleFilter */ -@import "./client/src/shared/branding/colors.less"; -@import "./client/src/shared/branding/colors.default.less"; - - .FactModuleFilter { width: 100%; display: flex; diff --git a/awx/ui/client/src/system-tracking/fact-module-pickers.block.less b/awx/ui/client/src/system-tracking/fact-module-pickers.block.less index 4ff1cb936a..6c5ebb4551 100644 --- a/awx/ui/client/src/system-tracking/fact-module-pickers.block.less +++ b/awx/ui/client/src/system-tracking/fact-module-pickers.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .FactModulePickers-label { padding-right: 0; text-align: right; diff --git a/awx/ui/client/src/system-tracking/system-tracking-container.block.less b/awx/ui/client/src/system-tracking/system-tracking-container.block.less index 31bb34cd4e..6688d2f255 100644 --- a/awx/ui/client/src/system-tracking/system-tracking-container.block.less +++ b/awx/ui/client/src/system-tracking/system-tracking-container.block.less @@ -1,4 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; .SystemTrackingContainer { display: flex; flex-direction: column; diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less index 6585cb7300..694e298a0e 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less @@ -1,5 +1,3 @@ -@import "../../../shared/branding/colors.default.less"; - .MultiCredential-selectedBar { display: flex; padding: 5px 10px; diff --git a/awx/ui/client/src/templates/labels/labelsList.block.less b/awx/ui/client/src/templates/labels/labelsList.block.less index d0d0c07c2f..8cd9523c1c 100644 --- a/awx/ui/client/src/templates/labels/labelsList.block.less +++ b/awx/ui/client/src/templates/labels/labelsList.block.less @@ -1,5 +1,4 @@ /** @define LabelList */ -@import "./client/src/shared/branding/colors.default.less"; .LabelList { display: flex; diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less index c91eb2cd87..7fd6a8aecc 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .link circle, .link .linkCross, .node .addCircle, .node .removeCircle, .node .WorkflowChart-hoverPath { opacity: 0; } diff --git a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less index 87d9fbb8f6..f8ee0523a4 100644 --- a/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-controls/workflow-controls.block.less @@ -1,5 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - .WorkflowControls { display: flex; } diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 5892f9403b..0f860547b3 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -1,6 +1,3 @@ -@import "./client/src/shared/branding/colors.default.less"; - - .WorkflowMaker-dialog { padding: 0px; margin-bottom: 20px; @@ -9,6 +6,7 @@ display:none; } } + .WorkflowMaker-header { display: flex; height: 34px; diff --git a/awx/ui/client/src/tooltip/tooltip.block.less b/awx/ui/client/src/tooltip/tooltip.block.less index 9fc41e5835..e51483507a 100644 --- a/awx/ui/client/src/tooltip/tooltip.block.less +++ b/awx/ui/client/src/tooltip/tooltip.block.less @@ -1,6 +1,5 @@ /** @define Tooltip */ -@import "../shared/branding/colors.less"; .Tooltip-inner { white-space: pre-wrap; word-break: break-word; diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less index 5d8e2b9e93..48f927eb3a 100644 --- a/awx/ui/client/src/workflow-results/workflow-results.block.less +++ b/awx/ui/client/src/workflow-results/workflow-results.block.less @@ -1,7 +1,3 @@ -@import '../shared/branding/colors.less'; -@import '../shared/branding/colors.default.less'; -@import '../shared/layouts/one-plus-two.less'; - @breakpoint-md: 1200px; @breakpoint-sm: 623px; diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less index 3f9bf3d8f0..b5a14a8605 100644 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less +++ b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less @@ -1,5 +1,3 @@ -@import '../../shared/branding/colors.default.less'; - .WorkflowStatusBar { display: flex; flex: 0 0 auto; diff --git a/awx/ui/grunt-tasks/concat.js b/awx/ui/grunt-tasks/concat.js new file mode 100644 index 0000000000..8bf300ed1c --- /dev/null +++ b/awx/ui/grunt-tasks/concat.js @@ -0,0 +1,17 @@ +module.exports = { + css: { + src: [ + 'static/assets/custom-theme/jquery-ui-1.10.3.custom.min.css', + 'static/assets/ansible-bootstrap.min.css', + 'static/assets/fontcustom/fontcustom.css', + 'static/lib/components-font-awesome/css/font-awesome.min.css', + 'static/lib/select2/dist/css/select2.css', + 'static/lib/codemirror/lib/codemirror.css', + 'static/lib/codemirror/theme/elegant.css', + 'static/lib/codemirror/addon/lint/lint.css', + 'static/lib/nvd3/build/nv.d3.css', + 'static/lib/ng-toast/dist/ngToast.min.css' + ], + dest: 'static/css/tower.vendor.css' + } +}; diff --git a/awx/ui/grunt-tasks/concurrent.js b/awx/ui/grunt-tasks/concurrent.js index 3cafeba559..64fd12bcc7 100644 --- a/awx/ui/grunt-tasks/concurrent.js +++ b/awx/ui/grunt-tasks/concurrent.js @@ -1,6 +1,6 @@ module.exports = { dev: { - tasks: ['copy:vendor', 'copy:assets', 'copy:partials', 'copy:views', 'copy:languages', 'copy:config', 'less:dev'], + tasks: ['copy:vendor', 'copy:assets', 'copy:partials', 'copy:views', 'copy:languages', 'copy:config'], }, // This concurrent target is intended for development ui builds that do not require raising browser-sync or filesystem polling devNoSync: { diff --git a/awx/ui/grunt-tasks/copy.js b/awx/ui/grunt-tasks/copy.js index 327787ef0f..ba13f92c4b 100644 --- a/awx/ui/grunt-tasks/copy.js +++ b/awx/ui/grunt-tasks/copy.js @@ -14,6 +14,33 @@ var staticFiles = ['angular-tz-extensions/tz/data/*', ]; module.exports = { + fonts: { + files: [{ + cwd: 'client/', + expand: true, + flatten: true, + filter: 'isFile', + src: [ + 'assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff', + 'assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf' + ], + dest: 'static/css/' + }] + }, + icons: { + files: [{ + cwd: 'node_modules/', + expand: true, + flatten: true, + filter: 'isFile', + src: [ + 'components-font-awesome/fonts/fontawesome-webfont.ttf', + 'components-font-awesome/fonts/fontawesome-webfont.woff', + 'components-font-awesome/fonts/fontawesome-webfont.woff2' + ], + dest: 'static/fonts/' + }] + }, assets: { files: [{ cwd: 'client/', diff --git a/awx/ui/grunt-tasks/cssmin.js b/awx/ui/grunt-tasks/cssmin.js new file mode 100644 index 0000000000..5ddfefb9ba --- /dev/null +++ b/awx/ui/grunt-tasks/cssmin.js @@ -0,0 +1,22 @@ +module.exports = { + vendor: { + files: [ + { + expand: true, + src: 'static/css/tower.vendor.css', + dest: '.', + ext: '.vendor.min.css' + } + ] + }, + source: { + files: [ + { + expand: true, + src: 'static/css/tower.css', + dest: '.', + ext: '.min.css' + } + ] + } +}; diff --git a/awx/ui/grunt-tasks/less.js b/awx/ui/grunt-tasks/less.js index 3995ae3485..5578d66cb8 100644 --- a/awx/ui/grunt-tasks/less.js +++ b/awx/ui/grunt-tasks/less.js @@ -1,32 +1,27 @@ +var AutoPrefixer = require('less-plugin-autoprefix'); + +var autoPrefixer = new AutoPrefixer({ + browsers: [ 'last 2 versions' ] +}); + module.exports = { - options: { - options : { - plugins : [ new (require('less-plugin-autoprefix'))({browsers : [ "last 2 versions" ]}) ] - } - }, dev: { - files: [{ - dest: 'static/tower.min.css', - src: [ - 'client/legacy-styles/*.less', - 'client/src/**/*.less', - 'client/lib/theme/index.less' - ] - }], + files: { + 'static/css/tower.css': 'client/lib/theme/index.less' + }, options: { - sourceMap: true + sourceMap: true, + plugins: [ autoPrefixer ] } }, prod: { files: { - 'static/tower.min.css': [ - 'client/legacy-styles/*.less', - 'client/src/**/*.less', - 'client/lib/theme/index.less' - ] + 'static/css/tower.css': 'client/lib/theme/index.less' }, options: { compress: true, + sourceMap: false, + plugins: [ autoPrefixer ] } } }; diff --git a/awx/ui/npm-shrinkwrap.json b/awx/ui/npm-shrinkwrap.json index 3bfe37efce..c46c589ccb 100644 --- a/awx/ui/npm-shrinkwrap.json +++ b/awx/ui/npm-shrinkwrap.json @@ -1141,6 +1141,20 @@ "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", "dev": true }, + "clean-css": { + "version": "4.1.7", + "from": "clean-css@>=4.1.1 <4.2.0", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.7.tgz", + "dev": true, + "dependencies": { + "source-map": { + "version": "0.5.6", + "from": "source-map@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "dev": true + } + } + }, "cli": { "version": "1.0.1", "from": "cli@>=1.0.0 <1.1.0", @@ -1575,6 +1589,12 @@ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", "dev": true }, + "duplexer": { + "version": "0.1.1", + "from": "duplexer@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "dev": true + }, "duplexify": { "version": "3.5.0", "from": "duplexify@>=3.1.2 <4.0.0", @@ -2524,6 +2544,12 @@ "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", "dev": true }, + "grunt-contrib-cssmin": { + "version": "2.2.0", + "from": "grunt-contrib-cssmin@latest", + "resolved": "https://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-2.2.0.tgz", + "dev": true + }, "grunt-contrib-jshint": { "version": "1.1.0", "from": "grunt-contrib-jshint@>=1.0.0 <2.0.0", @@ -2638,6 +2664,12 @@ } } }, + "gzip-size": { + "version": "3.0.0", + "from": "gzip-size@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", + "dev": true + }, "handlebars": { "version": "4.0.10", "from": "handlebars@>=4.0.0 <4.1.0", @@ -3950,6 +3982,12 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "dev": true }, + "maxmin": { + "version": "2.1.0", + "from": "maxmin@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz", + "dev": true + }, "media-typer": { "version": "0.3.0", "from": "media-typer@0.3.0", @@ -4494,6 +4532,12 @@ "from": "preserve@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" }, + "pretty-bytes": { + "version": "3.0.1", + "from": "pretty-bytes@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", + "dev": true + }, "pretty-ms": { "version": "2.1.0", "from": "pretty-ms@>=2.1.0 <3.0.0", diff --git a/awx/ui/package.json b/awx/ui/package.json index 5c603e29cc..cbb998b181 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -53,6 +53,7 @@ "grunt-contrib-clean": "^1.0.0", "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "^1.0.0", + "grunt-contrib-cssmin": "^2.2.0", "grunt-contrib-jshint": "^1.0.0", "grunt-contrib-less": "^1.3.0", "grunt-extract-sourcemap": "^0.1.18", diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index 2d265c6580..3168e9207a 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -7,17 +7,8 @@ - - - - - - - - - - - + + - - + + diff --git a/awx/ui/webpack.config.js b/awx/ui/webpack.config.js index 5aa9b4a033..28b1f5a1a0 100644 --- a/awx/ui/webpack.config.js +++ b/awx/ui/webpack.config.js @@ -52,7 +52,7 @@ var baseConfig = function() { }, output: { path: './static/', - filename: 'tower.js' + filename: 'app.js' }, plugins: [ // vendor shims: @@ -66,7 +66,7 @@ var baseConfig = function() { 'jsyaml': 'js-yaml', 'jsonlint': 'codemirror.jsonlint' }), - new webpack.optimize.CommonsChunkPlugin('vendor', 'tower.vendor.js') + new webpack.optimize.CommonsChunkPlugin('vendor', 'app.vendor.js') ], module: { loaders: [ From de3764c3f402bd1c751e30d47fa1b010f2eb3c52 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Fri, 21 Jul 2017 16:18:34 -0400 Subject: [PATCH 037/342] Fix path to image assets --- awx/ui/grunt-tasks/copy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/grunt-tasks/copy.js b/awx/ui/grunt-tasks/copy.js index 6060aa337f..cca102dc8d 100644 --- a/awx/ui/grunt-tasks/copy.js +++ b/awx/ui/grunt-tasks/copy.js @@ -40,7 +40,7 @@ module.exports = { expand: true, flatten: true, filter: 'isFile', - src: 'assets/images/images.new/*', + src: 'assets/custom-theme/images.new/*', dest: 'static/images/' }] }, From 20f83b6c55876c7a3aff8cfa65451844f6d33166 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Mon, 24 Jul 2017 10:53:42 -0400 Subject: [PATCH 038/342] Update .gitignore, remove ui static file from wrong dir --- .gitignore | 3 --- awx/static/tower.vendor.css | 0 2 files changed, 3 deletions(-) delete mode 100644 awx/static/tower.vendor.css diff --git a/.gitignore b/.gitignore index 763d15001d..8bab4577ad 100644 --- a/.gitignore +++ b/.gitignore @@ -12,10 +12,7 @@ awx/job_output awx/public/media awx/public/static awx/ui/tests/test-results.xml -awx/ui/static/js/awx.min.js -awx/ui/static/js/local_settings.json awx/ui/client/src/local_settings.json -awx/ui/static/css/awx.min.css awx/main/fixtures awx/*.log tower/tower_warnings.log diff --git a/awx/static/tower.vendor.css b/awx/static/tower.vendor.css deleted file mode 100644 index e69de29bb2..0000000000 From 1e27bad0100204c995db1c8cf1ee749a32d9c382 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Tue, 25 Jul 2017 09:40:23 -0400 Subject: [PATCH 039/342] Don't tie awx-manage to a particular dev environment version --- tools/docker-compose/awx-manage | 6 +++--- tools/docker-compose/start_development.sh | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/docker-compose/awx-manage b/tools/docker-compose/awx-manage index d4dff5664a..b0a62494c1 100755 --- a/tools/docker-compose/awx-manage +++ b/tools/docker-compose/awx-manage @@ -1,10 +1,10 @@ #!/venv/awx/bin/python -# EASY-INSTALL-ENTRY-SCRIPT: 'ansible-awx==placeholder','console_scripts','awx-manage' -__requires__ = 'ansible-awx==placeholder' +# EASY-INSTALL-ENTRY-SCRIPT: 'ansible-awx','console_scripts','awx-manage' +__requires__ = 'ansible-awx' import sys from pkg_resources import load_entry_point if __name__ == '__main__': sys.exit( - load_entry_point('ansible-awx==placeholder', 'console_scripts', 'awx-manage')() + load_entry_point('ansible-awx', 'console_scripts', 'awx-manage')() ) diff --git a/tools/docker-compose/start_development.sh b/tools/docker-compose/start_development.sh index c1032bf848..1892674079 100755 --- a/tools/docker-compose/start_development.sh +++ b/tools/docker-compose/start_development.sh @@ -23,7 +23,6 @@ fi cp -R /tmp/ansible_awx.egg-info /awx_devel/ || true sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/ansible_awx.egg-info/PKG-INFO -sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /usr/local/bin/awx-manage cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link ln -s /awx_devel/tools/rdb.py /venv/awx/lib/python2.7/site-packages/rdb.py || true yes | cp -rf /awx_devel/tools/docker-compose/supervisor.conf /supervisor.conf From a49209b954c4e5dbaea6302cf88916a5576d8f3e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 25 Jul 2017 09:22:55 -0400 Subject: [PATCH 040/342] disable activity stream messages for copying labels on job launch --- awx/main/models/unified_jobs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index dadb9d2b65..be960a0dab 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -359,7 +359,9 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio unified_job.save() # Labels and extra credentials copied here - copy_m2m_relationships(self, unified_job, fields, kwargs=kwargs) + from awx.main.signals import disable_activity_stream + with disable_activity_stream(): + copy_m2m_relationships(self, unified_job, fields, kwargs=kwargs) return unified_job @classmethod From 2ef962ed13539371aaae4c40e682e556536170b5 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 25 Jul 2017 09:45:28 -0400 Subject: [PATCH 041/342] Fix UX hit list items that apply to Job Details --- awx/ui/client/legacy-styles/lists.less | 1 + .../inventory-scripts/add/add.controller.js | 2 +- .../job-results-stdout.block.less | 33 ++++++++++++------- .../job-results-stdout.partial.html | 15 +++++---- .../src/job-results/job-results.block.less | 4 +-- 5 files changed, 33 insertions(+), 22 deletions(-) diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index a0f22e53d5..7fff572c07 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -158,6 +158,7 @@ table, tbody { font-weight: normal; padding: 2px 10px; height: 14px; + line-height: 10px; margin: 0; background-color: @list-title-badge; border-radius: 5px; diff --git a/awx/ui/client/src/inventory-scripts/add/add.controller.js b/awx/ui/client/src/inventory-scripts/add/add.controller.js index 15c4d301e9..5b162c2c4c 100644 --- a/awx/ui/client/src/inventory-scripts/add/add.controller.js +++ b/awx/ui/client/src/inventory-scripts/add/add.controller.js @@ -43,7 +43,7 @@ export default ['Rest', 'Wait', organization: $scope.organization, script: $scope.script }) - .success(function(data) { + .success(function() { $state.go('inventoryScripts', null, { reload: true }); Wait('stop'); }) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index ad98ec351e..3f7490ff59 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -149,7 +149,7 @@ padding-top: 2px; padding-bottom: 2px; width: 75px; - flex: initial; + flex: 1 0 70px; user-select: none; -moz-user-select: none; -webkit-user-select: none; @@ -193,7 +193,7 @@ } .JobResultsStdOut-footer { - height: 10px; + height: 20px; border-bottom-right-radius: 5px; border-bottom-left-radius: 5px; background-color: @default-secondary-bg; @@ -201,6 +201,8 @@ border-radius: 5px; border-top-left-radius: 0px; border-top-right-radius: 0px; + overflow: hidden; + margin-top: -1px; } .JobResultsStdOut-footerNumberColumn { @@ -211,19 +213,28 @@ } .JobResultsStdOut-followAnchor { - height: 20px; - width: 100%; - border-left: 70px solid @default-list-header-bg; - background-color: @default-secondary-bg; + height: 0px; } .JobResultsStdOut-toTop { - margin-right: 20px; color: @default-icon; cursor: pointer; - text-align: right; font-family: monaco; - border-left: 1px solid @d7grey; + font-size: 10px; + margin-right: 20px; + text-align: right; + display: flex; + + span { + margin-left: auto; + } +} + +.JobResultsStdOut-toTop--numberColumn { + background: @default-list-header-bg; + height: 40px; + width: 70px; + border-right: 1px solid #D7D7D7; } .JobResultsStdOut-toTop:hover { @@ -249,9 +260,7 @@ } .JobResultsStdOut-followAnchor { - height: 20px; - width: 100%; - border-left: 70px solid @default-list-header-bg; + height: 0px; } .JobResultsStdOut-stdoutContainer { diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 88ee156a4e..7b193b0032 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -37,7 +37,7 @@
- +
The standard output is too large to display. Please specify additional filters to narrow the standard out.
@@ -51,14 +51,15 @@
-
- ^ TOP -
-
+
\ No newline at end of file diff --git a/awx/ui/client/src/job-results/job-results.block.less b/awx/ui/client/src/job-results/job-results.block.less index 4ee845e811..7feb06a90c 100644 --- a/awx/ui/client/src/job-results/job-results.block.less +++ b/awx/ui/client/src/job-results/job-results.block.less @@ -16,7 +16,7 @@ .JobResults-leftSide { .OnePlusTwo-left--panel(100%, @breakpoint-md); - max-width: 33%; + max-width: 30%; height: ~"calc(100vh - 177px)"; @media screen and (max-width: @breakpoint-md) { @@ -73,7 +73,7 @@ .JobResults-resultRowLabel { text-transform: uppercase; color: @default-interface-txt; - font-size: 14px; + font-size: 12px; font-weight: normal!important; width: 30%; margin-right: 20px; From 568fd7ad22b33db02617e2bb92b38fc260f9a3c0 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Tue, 25 Jul 2017 09:49:25 -0400 Subject: [PATCH 042/342] Fix jshint warning --- awx/ui/client/src/inventory-scripts/add/add.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/inventory-scripts/add/add.controller.js b/awx/ui/client/src/inventory-scripts/add/add.controller.js index 15c4d301e9..5b162c2c4c 100644 --- a/awx/ui/client/src/inventory-scripts/add/add.controller.js +++ b/awx/ui/client/src/inventory-scripts/add/add.controller.js @@ -43,7 +43,7 @@ export default ['Rest', 'Wait', organization: $scope.organization, script: $scope.script }) - .success(function(data) { + .success(function() { $state.go('inventoryScripts', null, { reload: true }); Wait('stop'); }) From 4e057f6c4575ff6e334f30e966ee967c8e54478c Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 25 Jul 2017 09:53:18 -0400 Subject: [PATCH 043/342] Sanizite node resource name on hover --- .../workflows/workflow-chart/workflow-chart.directive.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js index e17ef5bba7..b650bdd5ed 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.directive.js @@ -4,8 +4,8 @@ * All Rights Reserved *************************************************/ -export default ['$state','moment', '$timeout', '$window', - function($state, moment, $timeout, $window) { +export default ['$state','moment', '$timeout', '$window', '$filter', + function($state, moment, $timeout, $window, $filter) { return { scope: { @@ -363,7 +363,7 @@ export default ['$state','moment', '$timeout', '$window', }); // Render the tooltip quickly in the dom and then remove. This lets us know how big the tooltip is so that we can place // it properly on the workflow - let tooltipDimensionChecker = $(""); + let tooltipDimensionChecker = $(""); $('body').append(tooltipDimensionChecker); let tipWidth = $(tooltipDimensionChecker).outerWidth(); let tipHeight = $(tooltipDimensionChecker).outerHeight(); @@ -376,7 +376,7 @@ export default ['$state','moment', '$timeout', '$window', .attr("height", tipHeight+20) .attr("class", "WorkflowChart-tooltip") .html(function(){ - return "
" + resourceName + "
"; + return "
" + $filter('sanitize')(resourceName) + "
"; }); } d3.select("#node-" + d.id) From f6d59409def81c487c652a88dd8086fda7019d65 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 25 Jul 2017 10:11:11 -0400 Subject: [PATCH 044/342] Fixing cookie settings for CSRF and auth token --- awx/settings/defaults.py | 3 +++ awx/sso/views.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index d076e234ea..ec5f18ba43 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -189,6 +189,9 @@ JOB_EVENT_MAX_QUEUE_SIZE = 10000 # Disallow sending session cookies over insecure connections SESSION_COOKIE_SECURE = True +# Do not allow non-browser clients to read the CSRF cookie. +CSRF_COOKIE_HTTPONLY = True + # Disallow sending csrf cookies over insecure connections CSRF_COOKIE_SECURE = True diff --git a/awx/sso/views.py b/awx/sso/views.py index 80092a8040..84826a0bb0 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -60,7 +60,7 @@ class CompleteView(BaseRedirectView): logger.info(smart_text(u"User {} logged in".format(self.request.user.username))) request.session['auth_token_key'] = token.key token_key = urllib.quote('"%s"' % token.key) - response.set_cookie('token', token_key) + response.set_cookie('token', value=token_key, httponly=True) token_expires = token.expires.astimezone(utc).strftime('%Y-%m-%dT%H:%M:%S') token_expires = '%s.%03dZ' % (token_expires, token.expires.microsecond / 1000) token_expires = urllib.quote('"%s"' % token_expires) From e29492a25955c54c2a79587a4ec471df5786f3cb Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Mon, 24 Jul 2017 17:02:08 -0400 Subject: [PATCH 045/342] more tower -> awx for task execution and isolated tooling --- awx/main/isolated/isolated_manager.py | 4 ++-- awx/main/isolated/run.py | 2 +- awx/main/tasks.py | 4 ++-- awx/main/tests/unit/isolated/test_expect.py | 2 +- awx/main/tests/unit/test_tasks.py | 12 ++++++------ awx/main/utils/common.py | 2 +- awx/playbooks/heartbeat_isolated.yml | 4 ++-- .../isolated/{tower_capacity.py => awx_capacity.py} | 0 ...r_isolated_cleanup.py => awx_isolated_cleanup.py} | 4 ++-- tools/docker-compose/README | 4 ++-- tools/docker-isolated/README.md | 4 ++-- 11 files changed, 21 insertions(+), 21 deletions(-) rename awx/plugins/isolated/{tower_capacity.py => awx_capacity.py} (100%) rename awx/plugins/isolated/{tower_isolated_cleanup.py => awx_isolated_cleanup.py} (94%) diff --git a/awx/main/isolated/isolated_manager.py b/awx/main/isolated/isolated_manager.py index c20e61f2f2..72d9f9c58e 100644 --- a/awx/main/isolated/isolated_manager.py +++ b/awx/main/isolated/isolated_manager.py @@ -193,7 +193,7 @@ class IsolatedManager(object): isolated_ssh_path = None try: if getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None): - isolated_ssh_path = tempfile.mkdtemp(prefix='ansible_tower_isolated', dir=settings.AWX_PROOT_BASE_PATH) + isolated_ssh_path = tempfile.mkdtemp(prefix='ansible_awx_isolated', dir=settings.AWX_PROOT_BASE_PATH) os.chmod(isolated_ssh_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) isolated_key = os.path.join(isolated_ssh_path, '.isolated') ssh_sock = os.path.join(isolated_ssh_path, '.isolated_ssh_auth.sock') @@ -446,7 +446,7 @@ class IsolatedManager(object): isolated job on :param private_data_dir: an absolute path on the local file system where job-specific data should be written - (i.e., `/tmp/ansible_tower_xyz/`) + (i.e., `/tmp/ansible_awx_xyz/`) :param proot_temp_dir: a temporary directory which bwrap maps restricted paths to diff --git a/awx/main/isolated/run.py b/awx/main/isolated/run.py index 00c6c423e9..db14be820d 100755 --- a/awx/main/isolated/run.py +++ b/awx/main/isolated/run.py @@ -143,7 +143,7 @@ def run_isolated_job(private_data_dir, secrets, logfile=sys.stdout): :param private_data_dir: an absolute path on the local file system where job metadata exists (i.e., - `/tmp/ansible_tower_xyz/`) + `/tmp/ansible_awx_xyz/`) :param secrets: a dict containing sensitive job metadata, { 'env': { ... } # environment variables, 'passwords': { ... } # pexpect password prompts diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 1da82635e1..c8ffa67f69 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -472,7 +472,7 @@ class BaseTask(Task): ''' Create a temporary directory for job-related files. ''' - path = tempfile.mkdtemp(prefix='ansible_tower_%s_' % instance.pk, dir=settings.AWX_PROOT_BASE_PATH) + path = tempfile.mkdtemp(prefix='ansible_awx_%s_' % instance.pk, dir=settings.AWX_PROOT_BASE_PATH) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) self.cleanup_paths.append(path) return path @@ -1855,7 +1855,7 @@ class RunInventoryUpdate(BaseTask): elif src == 'scm': args.append(inventory_update.get_actual_source_path()) elif src == 'custom': - runpath = tempfile.mkdtemp(prefix='ansible_tower_inventory_', dir=settings.AWX_PROOT_BASE_PATH) + runpath = tempfile.mkdtemp(prefix='ansible_awx_inventory_', dir=settings.AWX_PROOT_BASE_PATH) handle, path = tempfile.mkstemp(dir=runpath) f = os.fdopen(handle, 'w') if inventory_update.source_script is None: diff --git a/awx/main/tests/unit/isolated/test_expect.py b/awx/main/tests/unit/isolated/test_expect.py index adf43d4bb9..e9e36095d7 100644 --- a/awx/main/tests/unit/isolated/test_expect.py +++ b/awx/main/tests/unit/isolated/test_expect.py @@ -28,7 +28,7 @@ def rsa_key(request): @pytest.fixture(scope='function') def private_data_dir(request): - path = tempfile.mkdtemp(prefix='ansible_tower_unit_test') + path = tempfile.mkdtemp(prefix='ansible_awx_unit_test') request.addfinalizer(lambda: shutil.rmtree(path)) return path diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 162904629c..234f2c4b73 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -181,7 +181,7 @@ class TestJobExecution: EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----' def setup_method(self, method): - self.project_path = tempfile.mkdtemp(prefix='ansible_tower_project_') + self.project_path = tempfile.mkdtemp(prefix='ansible_awx_project_') with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f: f.write('---') @@ -312,7 +312,7 @@ class TestIsolatedExecution(TestJobExecution): credential.inputs['password'] = encrypt_field(credential, 'password') self.instance.credential = credential - private_data = tempfile.mkdtemp(prefix='ansible_tower_') + private_data = tempfile.mkdtemp(prefix='ansible_awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) inventory = json.dumps({"all": {"hosts": ["localhost"]}}) @@ -351,7 +351,7 @@ class TestIsolatedExecution(TestJobExecution): extra_vars = json.loads(extra_vars) assert extra_vars['dest'] == '/tmp' assert extra_vars['src'] == private_data - assert extra_vars['proot_temp_dir'].startswith('/tmp/ansible_tower_proot_') + assert extra_vars['proot_temp_dir'].startswith('/tmp/ansible_awx_proot_') def test_systemctl_failure(self): # If systemctl fails, read the contents of `artifacts/systemctl_logs` @@ -364,7 +364,7 @@ class TestIsolatedExecution(TestJobExecution): ) self.instance.credential = credential - private_data = tempfile.mkdtemp(prefix='ansible_tower_') + private_data = tempfile.mkdtemp(prefix='ansible_awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) inventory = json.dumps({"all": {"hosts": ["localhost"]}}) @@ -464,7 +464,7 @@ class TestJobCredentials(TestJobExecution): ) return ['successful', 0] - private_data = tempfile.mkdtemp(prefix='ansible_tower_') + private_data = tempfile.mkdtemp(prefix='ansible_awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) self.run_pexpect.side_effect = partial(run_pexpect_side_effect, private_data) self.task.run(self.pk, private_data_dir=private_data) @@ -1145,7 +1145,7 @@ class TestProjectUpdateCredentials(TestJobExecution): assert 'bob' in kwargs.get('expect_passwords').values() return ['successful', 0] - private_data = tempfile.mkdtemp(prefix='ansible_tower_') + private_data = tempfile.mkdtemp(prefix='ansible_awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) self.run_pexpect.side_effect = partial(run_pexpect_side_effect, private_data) self.task.run(self.pk) diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index a3b402a92a..80a72c9cb2 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -655,7 +655,7 @@ def build_proot_temp_dir(): Create a temporary directory for proot to use. ''' from django.conf import settings - path = tempfile.mkdtemp(prefix='ansible_tower_proot_', dir=settings.AWX_PROOT_BASE_PATH) + path = tempfile.mkdtemp(prefix='ansible_awx_proot_', dir=settings.AWX_PROOT_BASE_PATH) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) return path diff --git a/awx/playbooks/heartbeat_isolated.yml b/awx/playbooks/heartbeat_isolated.yml index 9c100c84af..7963d5fbe2 100644 --- a/awx/playbooks/heartbeat_isolated.yml +++ b/awx/playbooks/heartbeat_isolated.yml @@ -6,7 +6,7 @@ tasks: - name: Get capacity of the instance - tower_capacity: + awx_capacity: - name: Remove any stale temporary files - tower_isolated_cleanup: + awx_isolated_cleanup: diff --git a/awx/plugins/isolated/tower_capacity.py b/awx/plugins/isolated/awx_capacity.py similarity index 100% rename from awx/plugins/isolated/tower_capacity.py rename to awx/plugins/isolated/awx_capacity.py diff --git a/awx/plugins/isolated/tower_isolated_cleanup.py b/awx/plugins/isolated/awx_isolated_cleanup.py similarity index 94% rename from awx/plugins/isolated/tower_isolated_cleanup.py rename to awx/plugins/isolated/awx_isolated_cleanup.py index ac9a0cd101..a5b4d9b1df 100644 --- a/awx/plugins/isolated/tower_isolated_cleanup.py +++ b/awx/plugins/isolated/awx_isolated_cleanup.py @@ -39,7 +39,7 @@ def main(): job_cutoff = datetime.datetime.now() - datetime.timedelta(hours=1) for search_pattern in [ - '/tmp/ansible_tower_[0-9]*_*', '/tmp/ansible_tower_proot_*', + '/tmp/ansible_awx_[0-9]*_*', '/tmp/ansible_awx_proot_*', ]: for path in glob.iglob(search_pattern): st = os.stat(path) @@ -49,7 +49,7 @@ def main(): continue elif modtime > folder_cutoff: try: - re_match = re.match(r'\/tmp\/ansible_tower_\d+_.+', path) + re_match = re.match(r'\/tmp\/ansible_awx_\d+_.+', path) if re_match is not None: if subprocess.check_call(['awx-expect', 'is-alive', path]) == 0: continue diff --git a/tools/docker-compose/README b/tools/docker-compose/README index c6706af8ce..3591102b0f 100644 --- a/tools/docker-compose/README +++ b/tools/docker-compose/README @@ -1,5 +1,5 @@ -docker build --no-cache=true --rm=true -t ansible/tower_devel:latest . -docker run --name tower_test -it --memory="4g" --cpuset="0,1" -v /Users/meyers/ansible/:/tower_devel -p 8013:8013 -p 8080:8080 -p 27017:27017 -p 2222:22 ansible/tower_devel +docker build --no-cache=true --rm=true -t ansible/awx_devel:latest . +docker run --name awx_test -it --memory="4g" --cpuset="0,1" -v /Users/meyers/ansible/:/awx_devel -p 8013:8013 -p 8080:8080 -p 27017:27017 -p 2222:22 ansible/awx_devel ## How to use the logstash container diff --git a/tools/docker-isolated/README.md b/tools/docker-isolated/README.md index e2574fd6ca..397c4485cb 100644 --- a/tools/docker-isolated/README.md +++ b/tools/docker-isolated/README.md @@ -56,12 +56,12 @@ by disabling some parts of the cleanup_isolated.yml playbook. Example location of a private data directory: -`/tmp/ansible_tower_29_OM6Mnx/` +`/tmp/ansible_awx_29_OM6Mnx/` The following command would run the playbook corresponding to that job. ```bash -awx-expect start /tmp/ansible_tower_29_OM6Mnx/ +awx-expect start /tmp/ansible_awx_29_OM6Mnx/ ``` Other awx-expect commands include `start`, `is-alive`, and `stop`. From aaa8ccef0e74b2b3603355434fceffec056f5008 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 25 Jul 2017 10:49:51 -0400 Subject: [PATCH 046/342] Added inventory type to org inventories --- .../organizations-inventories.controller.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js index cd2a8508df..dc81823f72 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js @@ -7,12 +7,12 @@ export default ['$scope', '$rootScope', '$location', '$stateParams', '$compile', '$filter', 'Rest', 'InventoryList', 'OrgInventoryDataset', 'OrgInventoryList', - 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', + 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'i18n', function($scope, $rootScope, $location, $stateParams, $compile, $filter, Rest, InventoryList, Dataset, OrgInventoryList, ProcessErrors, GetBasePath, Wait, - Find, Empty, $state) { + Find, Empty, $state, i18n) { var list = OrgInventoryList, orgBase = GetBasePath('organizations'); @@ -38,11 +38,11 @@ export default ['$scope', '$rootScope', '$location', }); $scope.$watch('inventories', ()=>{ - _.forEach($scope.inventories, buildInventorySyncStatus); + _.forEach($scope.inventories, processInventoryRow); }); } - function buildInventorySyncStatus(item) { + function processInventoryRow(item) { if (item.has_inventory_sources) { if (item.inventory_sources_with_failures > 0) { item.syncStatus = 'error'; @@ -66,6 +66,9 @@ export default ['$scope', '$rootScope', '$location', item.hostsStatus = 'none'; item.hostsTip = 'Inventory contains 0 hosts.'; } + + item.kind_label = item.kind === '' ? 'Inventory' : (item.kind === 'smart' ? i18n._('Smart Inventory'): i18n._('Inventory')); + return item; } From bef94f0c620d13bce2bfa7253f2c2cef407b1c03 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 25 Jul 2017 10:59:06 -0400 Subject: [PATCH 047/342] Fixed org inventory name link --- .../controllers/organizations-inventories.controller.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js index cd2a8508df..b11c534bd4 100644 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js +++ b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js @@ -235,8 +235,13 @@ export default ['$scope', '$rootScope', '$location', }; - $scope.editInventory = function(id) { - $state.go('inventories.edit', { inventory_id: id }); + $scope.editInventory = function (inventory) { + if(inventory.kind && inventory.kind === 'smart') { + $state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id}); + } + else { + $state.go('inventories.edit', {inventory_id: inventory.id}); + } }; // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status From 7695cb6419238c9d48c878c094b722fa82633599 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 25 Jul 2017 10:54:42 -0400 Subject: [PATCH 048/342] Fix version when installing from sdist When installing an sdist, setup.py is invoked on the machine you're installing on. We extract the version from a git tag, but the repo is not included in the sdist. The git describe --long command will silently fail and cause the installed package to report version 0.0.0.0. --- .gitignore | 1 + MANIFEST.in | 1 + Makefile | 8 ++++++-- setup.py | 11 ++++++++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8bab4577ad..ee2a0211ce 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,7 @@ local/ *.mo requirements/vendor .i18n_built +VERSION # AWX python libs populated by requirements.txt awx/lib/.deps_built diff --git a/MANIFEST.in b/MANIFEST.in index 81483cee39..b65d4b5b3a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,6 +22,7 @@ include tools/scripts/failure-event-handler include tools/scripts/tower-python include awx/playbooks/library/mkfifo.py include tools/sosreport/* +include VERSION include COPYING include Makefile prune awx/public diff --git a/Makefile b/Makefile index a36cfee834..c4ab38102a 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ UI_RELEASE_FLAG_FILE = awx/ui/.release_built reprepro setup_tarball virtualbox-ovf virtualbox-centos-7 \ virtualbox-centos-6 clean-bundle setup_bundle_tarball \ ui-docker-machine ui-docker ui-release ui-devel \ - ui-test ui-deps ui-test-ci ui-test-saucelabs jlaska + ui-test ui-deps ui-test-ci ui-test-saucelabs jlaska VERSION # remove ui build artifacts clean-ui: @@ -117,6 +117,7 @@ clean: clean-ui clean-dist rm -rf requirements/vendor rm -rf tmp rm -rf $(I18N_FLAG_FILE) + rm -f VERSION mkdir tmp rm -rf build $(NAME)-$(VERSION) *.egg-info find . -type f -regex ".*\.py[co]$$" -delete @@ -535,7 +536,7 @@ dev_build: release_build: $(PYTHON) setup.py release_build -dist/$(SDIST_TAR_FILE): ui-release +dist/$(SDIST_TAR_FILE): ui-release VERSION BUILD="$(BUILD)" $(PYTHON) setup.py $(SDIST_COMMAND) dist/$(WHEEL_FILE): ui-release @@ -621,3 +622,6 @@ clean-elk: psql-container: docker run -it --net tools_default --rm postgres:9.4.1 sh -c 'exec psql -h "postgres" -p "5432" -U postgres' + +VERSION: + echo $(RELEASE_VERSION) > $@ diff --git a/setup.py b/setup.py index 5aa7b36447..9dde802e7c 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ import os import glob import sys import subprocess -import re from setuptools import setup from distutils.command.sdist import sdist @@ -26,8 +25,14 @@ else: def get_version(): - ver = subprocess.Popen("git describe --long | cut -f1-1 -d -", shell=True, stdout=subprocess.PIPE).stdout.read().strip() - return re.sub(r'-([0-9]+)-.*', r'.\1', ver) + current_dir = os.path.dirname(os.path.abspath(__file__)) + version_file = os.path.join(current_dir, 'VERSION') + if os.path.isfile(version_file): + with open(version_file, 'r') as file: + version = file.read().strip() + else: + version = subprocess.Popen("git describe --long | cut -d - -f 1-1", shell=True, stdout=subprocess.PIPE).stdout.read().strip() + return version if os.path.exists("/etc/debian_version"): From 3f3b02dbea233d3d78eedcdb68914d5d1b0ad040 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 25 Jul 2017 10:54:56 -0400 Subject: [PATCH 049/342] Fix whitespace --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c4ab38102a..53f4cad235 100644 --- a/Makefile +++ b/Makefile @@ -56,11 +56,11 @@ endif ifeq ($(OFFICIAL),yes) SETUP_TAR_NAME=$(NAME)-setup-$(RELEASE_VERSION) SDIST_TAR_NAME=$(NAME)-$(RELEASE_VERSION) - WHEEL_NAME=$(NAME)-$(RELEASE_VERSION) + WHEEL_NAME=$(NAME)-$(RELEASE_VERSION) else SETUP_TAR_NAME=$(NAME)-setup-$(RELEASE_VERSION)-$(RELEASE) SDIST_TAR_NAME=$(NAME)-$(RELEASE_VERSION)-$(RELEASE) - WHEEL_NAME=$(NAME)-$(RELEASE_VERSION)_$(RELEASE) + WHEEL_NAME=$(NAME)-$(RELEASE_VERSION)_$(RELEASE) endif SDIST_COMMAND ?= sdist From 9979513e244573a6cd1bc4dfb34937675f20f709 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 25 Jul 2017 10:55:37 -0400 Subject: [PATCH 050/342] Remove unused variable from setup.py invocations --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 53f4cad235..7542e41193 100644 --- a/Makefile +++ b/Makefile @@ -537,10 +537,10 @@ release_build: $(PYTHON) setup.py release_build dist/$(SDIST_TAR_FILE): ui-release VERSION - BUILD="$(BUILD)" $(PYTHON) setup.py $(SDIST_COMMAND) + $(PYTHON) setup.py $(SDIST_COMMAND) dist/$(WHEEL_FILE): ui-release - BUILD="$(BUILD)" $(PYTHON) setup.py $(WHEEL_COMMAND) + $(PYTHON) setup.py $(WHEEL_COMMAND) sdist: dist/$(SDIST_TAR_FILE) @echo "#############################################" From 82e94d70de4e91d3b88bea2fda7dafcb0e7e6995 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 25 Jul 2017 12:46:42 -0400 Subject: [PATCH 051/342] Reorder Settings cards to match mockup --- .../src/setup-menu/setup-menu.partial.html | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/awx/ui/client/src/setup-menu/setup-menu.partial.html b/awx/ui/client/src/setup-menu/setup-menu.partial.html index d33301698b..85118a9c9e 100644 --- a/awx/ui/client/src/setup-menu/setup-menu.partial.html +++ b/awx/ui/client/src/setup-menu/setup-menu.partial.html @@ -24,6 +24,13 @@ Add passwords, SSH keys, and other credentials to use when launching jobs against machines, or when syncing inventories or projects.

+ +

Credential Types

+

+ Create custom credential types to be used for authenticating to network hosts and cloud sources +

+

Management Jobs

@@ -43,19 +50,6 @@ Create templates for sending notifications with Email, HipChat, Slack, and SMS.

- -

Credential Types

-

- Create custom credential types to be used for authenticating to network hosts and cloud sources -

-
- -

View Your License

-

- View and edit your license information. -

-

Instance Groups

@@ -73,7 +67,13 @@

View information about this version of Ansible {{BRAND_NAME}}.

-
+ + +

View Your License

+

+ View and edit your license information. +

+
From 0ce3152e6f394a0a5932cc3aa47b59887e30ff57 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 25 Jul 2017 12:47:46 -0400 Subject: [PATCH 052/342] fix busted test runs --- tools/docker-compose/unit-tests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose/unit-tests/docker-compose.yml b/tools/docker-compose/unit-tests/docker-compose.yml index 8f5cc07c20..c71cf02e8a 100644 --- a/tools/docker-compose/unit-tests/docker-compose.yml +++ b/tools/docker-compose/unit-tests/docker-compose.yml @@ -9,6 +9,6 @@ services: environment: SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl" TEST_DIRS: awx/main/tests/functional awx/main/tests/unit awx/conf/tests awx/sso/tests - command: ["make test"] + command: ["cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link; make test"] volumes: - ../../../:/awx_devel From 1249a8b30f53598cfc9c88e0b43c1aaaaba81c1c Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 25 Jul 2017 11:49:21 -0400 Subject: [PATCH 053/342] Rename tower-python to awx-python --- MANIFEST.in | 2 +- setup.py | 4 ++-- tools/scripts/{tower-python => awx-python} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename tools/scripts/{tower-python => awx-python} (100%) diff --git a/MANIFEST.in b/MANIFEST.in index b65d4b5b3a..ff4d8ccddb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -19,7 +19,7 @@ include tools/scripts/request_tower_configuration.sh include tools/scripts/request_tower_configuration.ps1 include tools/scripts/ansible-tower-service include tools/scripts/failure-event-handler -include tools/scripts/tower-python +include tools/scripts/awx-python include awx/playbooks/library/mkfifo.py include tools/sosreport/* include VERSION diff --git a/setup.py b/setup.py index 9dde802e7c..349910d927 100755 --- a/setup.py +++ b/setup.py @@ -168,7 +168,7 @@ setup( ("%s" % docdir, ["docs/licenses/*",]), ("%s" % bindir, ["tools/scripts/ansible-tower-service", "tools/scripts/failure-event-handler", - "tools/scripts/tower-python", + "tools/scripts/awx-python", "tools/scripts/ansible-tower-setup"]), ("%s" % sosconfig, ["tools/sosreport/tower.py"])]), cmdclass = {'sdist_isolated': sdist_isolated}, @@ -182,7 +182,7 @@ setup( 'isolated_build': 'clean --all egg_info -b "" sdist_isolated', }, 'build_scripts': { - 'executable': '/usr/bin/tower-python', + 'executable': '/usr/bin/awx-python', }, }, ) diff --git a/tools/scripts/tower-python b/tools/scripts/awx-python similarity index 100% rename from tools/scripts/tower-python rename to tools/scripts/awx-python From e904d47122942d57e7c16c0c4a287e23c423fbe1 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Tue, 25 Jul 2017 14:01:43 -0400 Subject: [PATCH 054/342] Fix unit test runs --- tools/docker-compose/unit-tests/Dockerfile | 6 ++++-- tools/docker-compose/unit-tests/docker-compose.yml | 2 +- tools/docker-compose/unit-tests/entrypoint.sh | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 tools/docker-compose/unit-tests/entrypoint.sh diff --git a/tools/docker-compose/unit-tests/Dockerfile b/tools/docker-compose/unit-tests/Dockerfile index 98f6ce7069..9398c89398 100644 --- a/tools/docker-compose/unit-tests/Dockerfile +++ b/tools/docker-compose/unit-tests/Dockerfile @@ -7,5 +7,7 @@ RUN npm set progress=false WORKDIR "/awx_devel" -ENTRYPOINT ["/bin/bash", "-c"] -CMD ["bash"] +ADD tools/docker-compose/unit-tests/entrypoint.sh / +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/tools/docker-compose/unit-tests/docker-compose.yml b/tools/docker-compose/unit-tests/docker-compose.yml index c71cf02e8a..8f5cc07c20 100644 --- a/tools/docker-compose/unit-tests/docker-compose.yml +++ b/tools/docker-compose/unit-tests/docker-compose.yml @@ -9,6 +9,6 @@ services: environment: SWIG_FEATURES: "-cpperraswarn -includeall -I/usr/include/openssl" TEST_DIRS: awx/main/tests/functional awx/main/tests/unit awx/conf/tests awx/sso/tests - command: ["cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link; make test"] + command: ["make test"] volumes: - ../../../:/awx_devel diff --git a/tools/docker-compose/unit-tests/entrypoint.sh b/tools/docker-compose/unit-tests/entrypoint.sh new file mode 100644 index 0000000000..63190314b9 --- /dev/null +++ b/tools/docker-compose/unit-tests/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Code duplicated from start_development.sh +cp -R /tmp/ansible_awx.egg-info /awx_devel/ || true +sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/ansible_awx.egg-info/PKG-INFO +cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link + +cp -f awx/settings/local_settings.py.docker_compose awx/settings/local_settings.py + +/bin/bash -c "$@" From 89a39f4bed3c436657276a756535d725e068e6e9 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 25 Jul 2017 14:33:07 -0400 Subject: [PATCH 055/342] add scm to cloud sources so computed fields works --- awx/main/models/base.py | 2 +- awx/main/models/inventory.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/main/models/base.py b/awx/main/models/base.py index ea70070770..99572fa91d 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -52,7 +52,7 @@ PROJECT_UPDATE_JOB_TYPE_CHOICES = [ (PERM_INVENTORY_CHECK, _('Check')), ] -CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'azure_rm', 'openstack', 'custom', 'satellite6', 'cloudforms'] +CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'azure_rm', 'openstack', 'custom', 'satellite6', 'cloudforms', 'scm',] VERBOSITY_CHOICES = [ (0, '0 (Normal)'), diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 17809b51ab..8cf291a86d 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -334,7 +334,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin): failed_hosts = active_hosts.filter(has_active_failures=True) active_groups = self.groups failed_groups = active_groups.filter(has_active_failures=True) - active_inventory_sources = self.inventory_sources.filter( source__in=CLOUD_INVENTORY_SOURCES) + active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) failed_inventory_sources = active_inventory_sources.filter(last_job_failed=True) computed_fields = { 'has_active_failures': bool(failed_hosts.count()), From 27772495024191277d1f5e1a0b476e0847a01e71 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Tue, 25 Jul 2017 14:59:27 -0400 Subject: [PATCH 056/342] Change secret input edit mode to display as hiddden --- awx/ui/client/lib/components/input/secret.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/lib/components/input/secret.directive.js b/awx/ui/client/lib/components/input/secret.directive.js index 5a0a9876e2..1d3a03546c 100644 --- a/awx/ui/client/lib/components/input/secret.directive.js +++ b/awx/ui/client/lib/components/input/secret.directive.js @@ -18,11 +18,11 @@ function AtInputSecretController (baseInputController) { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; + scope.type = 'password'; if (!scope.state._value || scope.state._promptOnLaunch) { scope.mode = 'input'; scope.state._buttonText = vm.strings.get('SHOW'); - scope.type = 'password'; vm.toggle = vm.toggleShowHide; } else { From 8746df7b3106b912ef9252b325d554b4f87c0cc6 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 25 Jul 2017 15:41:01 -0400 Subject: [PATCH 057/342] update SCM test to mock inventory update --- awx/main/tests/functional/models/test_inventory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/tests/functional/models/test_inventory.py b/awx/main/tests/functional/models/test_inventory.py index 4c16a83c6f..8a43e66ff9 100644 --- a/awx/main/tests/functional/models/test_inventory.py +++ b/awx/main/tests/functional/models/test_inventory.py @@ -20,7 +20,7 @@ class TestSCMUpdateFeatures: inventory=inventory, update_on_project_update=True, source='scm') - with mock.patch.object(inv_src.source_project, 'update') as mck_update: + with mock.patch.object(inv_src, 'update') as mck_update: inv_src.save() mck_update.assert_called_once_with() From d3796e81ba4997b960b17beaddfd151db25587ba Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 25 Jul 2017 15:40:32 -0400 Subject: [PATCH 058/342] disable extraneous activity stream messages for credential creation see: https://github.com/ansible/ansible-tower/issues/7257 --- awx/main/models/base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/awx/main/models/base.py b/awx/main/models/base.py index ea70070770..8ba549a2e5 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -225,7 +225,13 @@ class PasswordFieldsModel(BaseModel): saved_value = getattr(self, '_saved_%s' % field, '') setattr(self, field, saved_value) self.mark_field_for_save(update_fields, field) - self.save(update_fields=update_fields) + + from awx.main.signals import disable_activity_stream + with disable_activity_stream(): + # We've already got an activity stream record for the object + # creation, there's no need to have an extra one for the + # secondary save for secrets + self.save(update_fields=update_fields) def encrypt_field(self, field, ask): encrypted = encrypt_field(self, field, ask) From aa14d12f424147d55d9d728dc7752f85bdecaae3 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 25 Jul 2017 13:35:53 -0400 Subject: [PATCH 059/342] show just id field for API browser POST box --- awx/api/generics.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/awx/api/generics.py b/awx/api/generics.py index 654457ef45..d2d7f0ff40 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -627,6 +627,13 @@ class SubListAttachDetachAPIView(SubListCreateAttachDetachAPIView): status=status.HTTP_400_BAD_REQUEST) return super(SubListAttachDetachAPIView, self).post(request, *args, **kwargs) + def update_raw_data(self, data): + request_method = getattr(self, '_raw_data_request_method', None) + response_status = getattr(self, '_raw_data_response_status', 0) + if request_method == 'POST' and response_status in xrange(400, 500): + return super(SubListAttachDetachAPIView, self).update_raw_data(data) + return {'id': None} + class DeleteLastUnattachLabelMixin(object): ''' From a855b66517c179b7617be94602ac049dcc12e0d0 Mon Sep 17 00:00:00 2001 From: gconsidine Date: Tue, 25 Jul 2017 16:30:56 -0400 Subject: [PATCH 060/342] Fix dismiss of credential permissions routing --- .../features/credentials/add-edit-credentials.view.html | 2 +- awx/ui/client/lib/components/panel/panel.directive.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/features/credentials/add-edit-credentials.view.html b/awx/ui/client/features/credentials/add-edit-credentials.view.html index b3e1c66907..ccdded4416 100644 --- a/awx/ui/client/features/credentials/add-edit-credentials.view.html +++ b/awx/ui/client/features/credentials/add-edit-credentials.view.html @@ -28,7 +28,7 @@ - + {{:: vm.strings.get('permissions.TITLE') }} diff --git a/awx/ui/client/lib/components/panel/panel.directive.js b/awx/ui/client/lib/components/panel/panel.directive.js index 7f3c0abfbf..001e564dc2 100644 --- a/awx/ui/client/lib/components/panel/panel.directive.js +++ b/awx/ui/client/lib/components/panel/panel.directive.js @@ -16,7 +16,7 @@ function AtPanelController ($state) { }; vm.dismiss = () => { - $state.go('^'); + $state.go(scope.onDismiss || '^'); }; vm.use = child => { @@ -38,7 +38,7 @@ function atPanel (pathService, _$animate_) { link: atPanelLink, scope: { state: '=', - animate: '@' + onDismiss: '@' } }; } From 5a3466a3b6a0c0282ae183b2a2da67a913b76e30 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 25 Jul 2017 16:44:34 -0400 Subject: [PATCH 061/342] 3.2 hardening color audit --- awx/ui/client/legacy-styles/ansible-ui.less | 36 ++++++++++--------- awx/ui/client/legacy-styles/codemirror.less | 2 +- awx/ui/client/legacy-styles/forms.less | 26 ++++++++------ .../legacy-styles/jquery-ui-overrides.less | 2 +- awx/ui/client/lib/theme/_mixins.less | 2 +- awx/ui/client/lib/theme/_variables.less | 17 +++++---- awx/ui/client/src/app.js | 2 +- .../counts/dashboard-counts.block.less | 2 +- .../src/home/dashboard/dashboard.block.less | 2 +- .../src/home/dashboard/dashboard.partial.html | 1 + .../graphs/dashboard-graphs.block.less | 2 +- .../job-submission/job-submission.block.less | 2 +- .../management-jobs/card/mgmtcards.block.less | 2 +- .../src/organizations/orgcards.block.less | 2 +- .../src/setup-menu/setup-item.block.less | 2 +- .../src/shared/branding/colors.default.less | 23 ++++++------ awx/ui/client/src/shared/form-generator.js | 4 +-- 17 files changed, 69 insertions(+), 60 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 4f1df1be6d..51162a5bbf 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -27,12 +27,6 @@ body.modal-open { margin-right: 0; } - - -.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - border-color: @b7grey; -} - /* Helper Classes */ .pad-right-sm { padding-right: 10px; } .pad-left-md { padding-left: 30px; } @@ -607,13 +601,13 @@ dd { .ui-widget-content a.help-link, .ui-widget-content a.help-link:active, .ui-widget-content a.help-link:visited { - color: @grey; + color: @default-icon; text-decoration: none; } .help-link:hover, .ui-widget-content a.help-link:hover { - color: @default-icon; + color: @default-interface-txt; text-decoration: none; } @@ -2085,8 +2079,8 @@ tr td button i { } .form-control { - border-color: @d7grey; - background-color: @default-no-items-bord; + border-color: @b7grey; + background-color: @f2grey; color: @default-data-txt; transition: border-color 0.3s; box-shadow: none; @@ -2096,7 +2090,7 @@ tr td button i { .form-control + .select2 .select2-selection { border-color: @b7grey !important; - background-color: @default-bg !important; + background-color: @f2grey !important; color: @default-data-txt !important; transition: border-color 0.3s !important; box-shadow: none !important; @@ -2108,7 +2102,7 @@ tr td button i { } .form-control + .select2-container--disabled .select2-selection { - background-color: @egrey !important; + background-color: @ebgrey !important; } .form-control:active, .form-control:focus { @@ -2146,7 +2140,7 @@ tr td button i { .select2-container--disabled,.select2-container--disabled .select2-selection--single,.select2-container--disabled .select2-selection--multiple { cursor: not-allowed; opacity: 100; - background: @egrey; + background: @ebgrey; border-radius: 5px; } @@ -2311,7 +2305,7 @@ html input[disabled] { .select2-container--disabled .select2-selection, .select2-container--disabled .select2-arrow { - background: @egrey; + background: @ebgrey; } .btn.disabled,.btn[disabled],fieldset[disabled] .bt { @@ -2320,11 +2314,11 @@ html input[disabled] { .ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled { opacity: 1; - background: @egrey; + background: @ebgrey; } input[disabled].ui-spinner-input { - background-color: @egrey; + background-color: @ebgrey; } .CodeMirror-scroll { @@ -2339,3 +2333,13 @@ input[disabled].ui-spinner-input { .CodeMirror-lines { margin-bottom: 20px; } + +.btn-default { + border-color: @b7grey; +} + +.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { + background-color: @f2grey; + border-color: @b7grey; + color: @default-interface-txt; +} diff --git a/awx/ui/client/legacy-styles/codemirror.less b/awx/ui/client/legacy-styles/codemirror.less index 439291d4a6..68317c844d 100644 --- a/awx/ui/client/legacy-styles/codemirror.less +++ b/awx/ui/client/legacy-styles/codemirror.less @@ -43,7 +43,7 @@ textarea[disabled="disabled"] + div[id*="-container"]{ .CodeMirror.cm-s-default, .CodeMirror-line { - background-color: @egrey; + background-color: @ebgrey; } .CodeMirror-gutters { diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 0c3f397b98..6374b87153 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -156,7 +156,7 @@ } .Form-tab--disabled { - opacity: 0.4; + opacity: 0.65; } .Form-tab--disabled:hover { @@ -200,7 +200,7 @@ .Form-formGroup { input.form-control { - background-color: @default-secondary-bg; + background-color: @f2grey; border-color: @b7grey; border-radius: 5px; height: 30px; @@ -247,7 +247,7 @@ left: -20px; position: absolute; width: 5px; - background-color: @default-border; + background-color: @b7grey; height: 100%; } @@ -318,7 +318,7 @@ .ui-spinner{ height: 30px; - background-color: @field-secondary-bg !important; + background-color: @f2grey !important; border-radius: 5px; border:1px solid @field-border !important; color: @field-input-text; @@ -366,13 +366,12 @@ border-left:1px solid @field-border; border-bottom-right-radius: 5px; border-top-right-radius: 5px; - background-color: @field-button-bg !important; width: 30px!important; height: 28px!important; } .select2-container--disabled .select2-selection__arrow { - background: @egrey !important; + background: @ebgrey !important; } .select2-results__option{ @@ -420,19 +419,19 @@ .Form-passwordButton{ height: 30px; - color: @field-lookup-btn-icon!important; + color: @default-interface-txt; text-transform: uppercase; line-height: 1; padding-left: 7px; padding-right: 7px; - background-color: @field-lookup-btn-bg; - border:1px solid @field-border; + background-color: @default-bg; + border:1px solid @b7grey; } .Form-passwordButton:hover { cursor: pointer; - background-color: @field-lookup-btn-hov-bg; - border: 1px solid @field-border; + background-color: @f2grey; + border: 1px solid @b7grey; color: @field-lookup-btn-icon; } @@ -754,3 +753,8 @@ input[type='radio']:checked:before { :-moz-placeholder { /* Firefox 18- */ color: @b7grey; } + +.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { + border-color: @b7grey; + background: @ebgrey; +} diff --git a/awx/ui/client/legacy-styles/jquery-ui-overrides.less b/awx/ui/client/legacy-styles/jquery-ui-overrides.less index c214c55979..79f7dd5931 100644 --- a/awx/ui/client/legacy-styles/jquery-ui-overrides.less +++ b/awx/ui/client/legacy-styles/jquery-ui-overrides.less @@ -223,5 +223,5 @@ table.ui-datepicker-calendar { } .ui-state-disabled.ui-widget-content { - background-color: @egrey; + background-color: @ebgrey !important; } diff --git a/awx/ui/client/lib/theme/_mixins.less b/awx/ui/client/lib/theme/_mixins.less index 3669549531..90a898be9b 100644 --- a/awx/ui/client/lib/theme/_mixins.less +++ b/awx/ui/client/lib/theme/_mixins.less @@ -34,7 +34,7 @@ &, &:active, &:hover, &:focus { color: @at-color-button-text-default; border-color: @at-color-input-border; - background-color: @at-color-default; + background-color: @at-gray-light-2x; cursor: pointer; } } diff --git a/awx/ui/client/lib/theme/_variables.less b/awx/ui/client/lib/theme/_variables.less index b006601d10..ea5240ebed 100644 --- a/awx/ui/client/lib/theme/_variables.less +++ b/awx/ui/client/lib/theme/_variables.less @@ -1,20 +1,20 @@ /** * All variables used in the UI. Use these variables directly during the development of components * and features. Be sure the context of the variable name applies to the work that's being done. - * For example, it wouldn't make sense to use `@at-input-height` to describe the height of a - * button. Either add an alias if it makes sense to use the same base variable, or add a new + * For example, it wouldn't make sense to use `@at-input-height` to describe the height of a + * button. Either add an alias if it makes sense to use the same base variable, or add a new * base variable to reference. * - * Keep in mind the goal is to be able to modify an item by referencing its context instead of - * an arbitrary variable name. For example, tt should be a simple change when an ask comes in to + * Keep in mind the goal is to be able to modify an item by referencing its context instead of + * an arbitrary variable name. For example, tt should be a simple change when an ask comes in to * "increase the height of inputs" * * 1. Colors * 2. Typography * 3. Layout * 4. Buttons - * 5. Misc - * + * 5. Misc + * */ // 1. Colors -------------------------------------------------------------------------------------- @@ -68,11 +68,11 @@ @at-color-input-disabled: @at-gray-light-2x; @at-color-icon-dismiss: @at-gray-dark; -@at-color-icon-popover: @at-gray-dark-3x; +@at-color-icon-popover: @at-gray-dark-4x; @at-color-icon-hover: @at-gray-dark-5x; @at-color-panel-heading: @at-gray-dark-5x; -@at-color-panel-border: @at-gray-dark; +@at-color-panel-border: @at-gray-dark-2x; @at-color-search-key-active: @at-blue; @@ -140,4 +140,3 @@ @at-line-height-short: 0.9; @at-line-height-tall: 2; @at-line-height: 24px; - diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index fffaac55a4..1a2f427a85 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -206,7 +206,7 @@ var awApp = angular.module('awApp', [ LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, $filter, SocketService, AppStrings) { - + console.log("This works fine!"); $rootScope.$state = $state; $rootScope.$state.matches = function(stateName) { return $state.current.name.search(stateName) > 0; diff --git a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less index 4c548c20d3..411fd2e313 100644 --- a/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less +++ b/awx/ui/client/src/home/dashboard/counts/dashboard-counts.block.less @@ -19,7 +19,7 @@ padding-right: 15px; border-radius: 5px; background-color: @db-panel-bg; - border: 1px solid @db-panel-border; + border: 1px solid @b7grey; flex: 1 0 auto; max-width: ~"calc(16.6% - 15px)"; flex-basis: ~"calc(16.6% - 15px)"; diff --git a/awx/ui/client/src/home/dashboard/dashboard.block.less b/awx/ui/client/src/home/dashboard/dashboard.block.less index c5bb5a2082..ec509d1833 100644 --- a/awx/ui/client/src/home/dashboard/dashboard.block.less +++ b/awx/ui/client/src/home/dashboard/dashboard.block.less @@ -19,7 +19,7 @@ } .Dashboard-list { - border: 1px solid @default-border; + border: 1px solid @b7grey; border-radius: 5px; margin-top: 20px; width: 50%; diff --git a/awx/ui/client/src/home/dashboard/dashboard.partial.html b/awx/ui/client/src/home/dashboard/dashboard.partial.html index dc2aa78245..97ef27c136 100644 --- a/awx/ui/client/src/home/dashboard/dashboard.partial.html +++ b/awx/ui/client/src/home/dashboard/dashboard.partial.html @@ -1,4 +1,5 @@
+ HEY! \n
\n"; } @@ -1371,7 +1371,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += `
`; html += "\n"; - html += ``; From 73ac54c4e5651ffcd634c6150059f932c4e79485 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 25 Jul 2017 16:46:20 -0400 Subject: [PATCH 062/342] Fixed bug deleting survey questions --- awx/ui/client/src/partials/survey-maker-modal.html | 8 ++++---- awx/ui/client/src/shared/directives.js | 5 ++++- awx/ui/grunt-tasks/watch.js | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/partials/survey-maker-modal.html b/awx/ui/client/src/partials/survey-maker-modal.html index b8f56cadcc..1be3cd5d39 100644 --- a/awx/ui/client/src/partials/survey-maker-modal.html +++ b/awx/ui/client/src/partials/survey-maker-modal.html @@ -22,7 +22,7 @@
{{name || "New Job Template"}}
SURVEY
-
+
@@ -56,17 +56,17 @@ {{question.question_description}}
- +  
- -
diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index 445352fef3..64f542c1d8 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -804,6 +804,7 @@ function(ConfigurationUtils, i18n, $rootScope) { link: function(scope, element, attrs) { var delay = { show: 200, hide: 0 }, placement, + container, stateChangeWatcher; if (attrs.awTipPlacement) { placement = attrs.awTipPlacement; @@ -811,6 +812,8 @@ function(ConfigurationUtils, i18n, $rootScope) { placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; } + container = attrs.container ? attrs.container : 'body'; + var template, custom_class; if (attrs.tooltipInnerClass || attrs.tooltipinnerclass) { custom_class = attrs.tooltipInnerClass || attrs.tooltipinnerclass; @@ -849,7 +852,7 @@ function(ConfigurationUtils, i18n, $rootScope) { delay: delay, html: true, title: attrs.awToolTip, - container: 'body', + container: container, trigger: 'hover', template: template }); diff --git a/awx/ui/grunt-tasks/watch.js b/awx/ui/grunt-tasks/watch.js index a9cf2a2fcc..e2a6c4e55c 100644 --- a/awx/ui/grunt-tasks/watch.js +++ b/awx/ui/grunt-tasks/watch.js @@ -6,7 +6,8 @@ module.exports = { partials: { files: [ 'client/lib/components/**/*.partial.html', - 'client/src/**/*.partial.html' + 'client/src/**/*.partial.html', + 'client/src/partials/*.html' ], tasks: ['newer:copy:partials'] }, From f04f900358871fd6c5d05c20172d58368aa54667 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 25 Jul 2017 16:59:54 -0400 Subject: [PATCH 063/342] color updates to workflows --- awx/ui/client/legacy-styles/ansible-ui.less | 4 ++++ awx/ui/client/src/shared/branding/colors.default.less | 1 + .../workflows/workflow-chart/workflow-chart.block.less | 2 +- .../workflows/workflow-maker/workflow-maker.block.less | 10 ++++++---- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 51162a5bbf..bfabb4c7ed 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -2343,3 +2343,7 @@ input[disabled].ui-spinner-input { border-color: @b7grey; color: @default-interface-txt; } + +.ui-dialog .ui-dialog-content { + background: @default-bg; +} diff --git a/awx/ui/client/src/shared/branding/colors.default.less b/awx/ui/client/src/shared/branding/colors.default.less index 0ec2d4795d..af8286108f 100644 --- a/awx/ui/client/src/shared/branding/colors.default.less +++ b/awx/ui/client/src/shared/branding/colors.default.less @@ -25,6 +25,7 @@ @f7grey: #F7F7F7; @insights-yellow: #dedc4f; @f2grey: #f2f2f2; +@f6grey: #f6f6f6; @ebgrey: #ebebeb; diff --git a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less index 7fd6a8aecc..e6424a90e0 100644 --- a/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-chart/workflow-chart.block.less @@ -69,7 +69,7 @@ } .WorkflowResults-rightSide .WorkflowChart-svg { - background-color: @default-secondary-bg; + background-color: @f6grey; border: 1px solid @d7grey; border-top: 0px; border-bottom-right-radius: 5px; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less index 0f860547b3..c506907a09 100644 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less @@ -43,7 +43,7 @@ } .WorkflowMaker-contentHolder { display: flex; - border: 1px solid @d7grey; + border: 1px solid @b7grey; border-radius: 5px; height: ~"calc(100% - 85px)"; } @@ -54,7 +54,8 @@ } .WorkflowMaker-contentRight { flex: 0 0 400px; - border-left: 1px solid @d7grey; + border-left: 1px solid @b7grey; + background: @default-bg; padding: 20px; height: 100%; overflow-y: scroll; @@ -157,7 +158,8 @@ height: 40px; line-height: 40px; color: @default-interface-txt; - border-bottom: 1px solid @d7grey; + background: @default-bg; + border-bottom: 1px solid @b7grey; } .WorkflowLegend-maker--left { flex: 1 0 auto; @@ -246,7 +248,7 @@ width: 293px; background-color: @default-bg; display: flex; - border: 1px solid @d7grey; + border: 1px solid @b7grey; border-top: 0px; border-bottom-left-radius: 5px; margin-left: -1px; From b204270871590a618c7b5516decbe6571efa4f9c Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 25 Jul 2017 17:03:02 -0400 Subject: [PATCH 064/342] fix typo that got added to dashboard partial --- awx/ui/client/src/home/dashboard/dashboard.partial.html | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/ui/client/src/home/dashboard/dashboard.partial.html b/awx/ui/client/src/home/dashboard/dashboard.partial.html index 97ef27c136..dc2aa78245 100644 --- a/awx/ui/client/src/home/dashboard/dashboard.partial.html +++ b/awx/ui/client/src/home/dashboard/dashboard.partial.html @@ -1,5 +1,4 @@
- HEY! - +
diff --git a/awx/ui/client/src/activity-stream/streams.list.js b/awx/ui/client/src/activity-stream/streams.list.js index e1699741e6..0105f1899e 100644 --- a/awx/ui/client/src/activity-stream/streams.list.js +++ b/awx/ui/client/src/activity-stream/streams.list.js @@ -18,7 +18,6 @@ export default ['i18n', function(i18n) { selectInstructions: '', index: false, hover: true, - "class": "table-condensed", toolbarAuxAction: "", fields: { From 6855e3420f0f5aa2de275aaffac419a843f6bc7e Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Tue, 25 Jul 2017 16:16:26 -0400 Subject: [PATCH 069/342] Fix search example. --- .../client/src/shared/smart-search/django-search-model.class.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/shared/smart-search/django-search-model.class.js b/awx/ui/client/src/shared/smart-search/django-search-model.class.js index 1866d41ff2..3809e47a31 100644 --- a/awx/ui/client/src/shared/smart-search/django-search-model.class.js +++ b/awx/ui/client/src/shared/smart-search/django-search-model.class.js @@ -41,7 +41,7 @@ class DjangoSearchModel { stringFound = true; } if(!dateTimeFound && value.type === 'datetime') { - this.searchExamples.push(key + ":>=\"2000-01-01T00:00:00Z\""); + this.searchExamples.push(key + ":>=2000-01-01T00:00:00Z"); this.searchExamples.push(key + ":<2000-01-01"); dateTimeFound = true; } From 005079e5b6c5fd65e84fc9711ab156f7d8d4532f Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 25 Jul 2017 14:37:38 -0700 Subject: [PATCH 070/342] fixing URL used for "VIEW MORE" of groups --- .../related-groups-labels/relatedGroupsLabelsList.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js index 0ff214b6d2..e115871c4a 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js @@ -33,7 +33,7 @@ export default scope.seeMore = function () { var seeMoreResolve = $q.defer(); - Rest.setUrl(`${scope[scope.$parent.list.iterator].related.groups}/?order_by=id`); + Rest.setUrl(`${scope[scope.$parent.list.iterator].related.groups}?order_by=id`); Rest.get() .success(function(data) { if (data.next) { From 6f51571550144318404e38e24bf7674950eff420 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Tue, 25 Jul 2017 18:14:50 -0700 Subject: [PATCH 071/342] allowing job submission to submit only a vault cred previously you were only allowed to submit a job if it had an ssh cred --- .../src/job-submission/job-submission.block.less | 3 +++ .../job-submission/job-submission.controller.js | 3 ++- .../src/job-submission/job-submission.partial.html | 14 ++++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/src/job-submission/job-submission.block.less b/awx/ui/client/src/job-submission/job-submission.block.less index 350ffc4f86..f1b0790e18 100644 --- a/awx/ui/client/src/job-submission/job-submission.block.less +++ b/awx/ui/client/src/job-submission/job-submission.block.less @@ -243,6 +243,9 @@ align-items: flex-start; } .JobSubmission-previewTagLabel { + color: @default-interface-txt; +} +.JobSubmission-previewTagLabel--deletable{ color: @default-list-header-bg; } .JobSubmission-previewTagRevert { diff --git a/awx/ui/client/src/job-submission/job-submission.controller.js b/awx/ui/client/src/job-submission/job-submission.controller.js index b225a913fb..9fb0ece241 100644 --- a/awx/ui/client/src/job-submission/job-submission.controller.js +++ b/awx/ui/client/src/job-submission/job-submission.controller.js @@ -164,6 +164,7 @@ export default $scope.has_default_inventory = data.defaults && data.defaults.inventory && data.defaults.inventory.id; $scope.has_default_credential = data.defaults && data.defaults.credential && data.defaults.credential.id; $scope.has_default_vault_credential = data.defaults && data.defaults.vault_credential && data.defaults.vault_credential.id; + $scope.vault_password_required = ($scope.password_needed && data.passwords_needed_to_start.includes('vault_password')); $scope.has_default_extra_credentials = data.defaults && data.defaults.extra_credentials && data.defaults.extra_credentials.length > 0; $scope.other_prompt_data = {}; @@ -434,7 +435,7 @@ export default } } else if($scope.step === "credential") { - if($scope.selected_credentials.machine && $scope.forms.credentialpasswords && $scope.forms.credentialpasswords.$valid) { + if(($scope.selected_credentials.machine || $scope.selected_credentials.vault) && $scope.forms.credentialpasswords && $scope.forms.credentialpasswords.$valid) { return false; } else { diff --git a/awx/ui/client/src/job-submission/job-submission.partial.html b/awx/ui/client/src/job-submission/job-submission.partial.html index 4cd78f99d1..109fa1d173 100644 --- a/awx/ui/client/src/job-submission/job-submission.partial.html +++ b/awx/ui/client/src/job-submission/job-submission.partial.html @@ -51,7 +51,7 @@
-
+
SELECTED:
@@ -62,7 +62,8 @@
- MACHINE: {{selected_credentials.machine.name}} + + MACHINE: {{selected_credentials.machine.name}}
@@ -70,13 +71,14 @@
- {{credential_types[extraCredential.credential_type].name | uppercase}}: {{extraCredential.name}} + + {{credential_types[extraCredential.credential_type].name | uppercase}}: {{extraCredential.name}}
- - VAULT: {{selected_credentials.vault.name}} + + VAULT: {{selected_credentials.vault.name}}
@@ -340,7 +342,7 @@
CREDENTIAL
- None selected + None selected
Machine From 485848619b4fc87e4dafb180d8b0ab329924645a Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 26 Jul 2017 09:05:54 -0400 Subject: [PATCH 072/342] update spinner field background color and fix formattings of forms.less file --- awx/ui/client/legacy-styles/forms.less | 88 +++++++++++++------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 6374b87153..fdde7e18a4 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -17,26 +17,26 @@ not supported by any browser */ } -.Form{ +.Form { display:flex; flex-wrap:wrap; flex-direction: row; } -.Form-header--fields{ +.Form-header--fields { flex: 1 1 auto; } -.Form-header-field{ +.Form-header-field { margin-left: 10px; flex: 1 1 auto; } -.Form-header{ +.Form-header { display: flex; } -.Form-title{ +.Form-title { flex: 0 1 auto; color: @list-header-txt; font-size: 14px; @@ -51,7 +51,7 @@ text-transform: uppercase; } -.Form-secondaryTitle{ +.Form-secondaryTitle { color: @default-icon; padding-bottom: 20px; min-height: 40px; @@ -61,7 +61,7 @@ .Form-title--is_superuser, .Form-title--is_system_auditor, .Form-title--is_ldap_user, -.Form-title--is_external_account{ +.Form-title--is_external_account { height:15px; color: @default-interface-txt; background-color: @default-list-header-bg; @@ -77,12 +77,12 @@ height: 16px; } -.Form-exitHolder{ +.Form-exitHolder { justify-content: flex-end; display:flex; } -.Form-exit{ +.Form-exit { cursor:pointer; padding:0px; border: none; @@ -94,11 +94,11 @@ line-height:1; } -.Form-exit:hover{ +.Form-exit:hover { color:@default-icon; } -.Form-tabHolder{ +.Form-tabHolder { display: flex; min-height: 30px; flex-wrap:wrap; @@ -165,12 +165,12 @@ cursor:not-allowed!important; } -.Form-tabSection{ +.Form-tabSection { display: none; width: 0%; } -.Form-tabSection.is-selected{ +.Form-tabSection.is-selected { width: 100%; display: block; } @@ -261,7 +261,7 @@ margin-bottom: 10px; } -.Form-textAreaLabel{ +.Form-textAreaLabel { width:100%; order: 1; } @@ -270,7 +270,7 @@ max-width: 100%; } -.Form-textArea{ +.Form-textArea { border-radius: 5px; color: @field-input-text; background-color: @field-secondary-bg; @@ -285,11 +285,11 @@ color: @field-input-text; } -.Form-textInput:active{ +.Form-textInput:active { border:1px solid @field-border-sel; } -.Form-monospace{ +.Form-monospace { font-family: Menlo,Monaco,Consolas,"Courier New",monospace!important; } @@ -316,7 +316,7 @@ border: 1px solid @default-link-hov; } -.ui-spinner{ +.ui-spinner { height: 30px; background-color: @f2grey !important; border-radius: 5px; @@ -325,21 +325,21 @@ width:100% } -.ui-spinner-input{ +.ui-spinner-input { color: @field-input-text; - background-color: @field-secondary-bg; + background-color: @f2grey; } -.ui-spinner-input:focus{ +.ui-spinner-input:focus { outline: none; } -.ui-spinner-button{ +.ui-spinner-button { border-left:1px solid @field-border!important; background-color: @field-button-bg !important; } -.ui-spinner-button:hover{ +.ui-spinner-button:hover { background-color:@field-button-hov !important; cursor: pointer!important; } @@ -349,7 +349,7 @@ color: @default-data-txt; } -.Form-numberInputButton{ +.Form-numberInputButton { color: @default-icon!important; font-size: 14px; @@ -362,7 +362,7 @@ color: @field-input-text!important; } -.select2-selection__arrow{ +.select2-selection__arrow { border-left:1px solid @field-border; border-bottom-right-radius: 5px; border-top-right-radius: 5px; @@ -374,23 +374,23 @@ background: @ebgrey !important; } -.select2-results__option{ +.select2-results__option { color: @field-label !important; } -.select2-container--default .select2-results__option--highlighted[aria-selected]{ +.select2-container--default .select2-results__option--highlighted[aria-selected] { background-color: @field-button-hov !important; } -.select2-container--default .select2-results__option[aria-selected=true]{ +.select2-container--default .select2-results__option[aria-selected=true] { background-color: @default-white-button-bord !important; } -.select2-container--default .select2-selection--single .select2-selection__arrow b{ +.select2-container--default .select2-selection--single .select2-selection__arrow b { border-color: @field-dropdown-icon transparent transparent transparent !important; } -.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{ +.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { border-color: transparent transparent @field-dropdown-icon transparent!important; } @@ -399,7 +399,7 @@ border-bottom-right-radius: 0 !important; } -.select2-dropdown{ +.select2-dropdown { border:1px solid @field-border; } @@ -409,15 +409,15 @@ border-top: 1px solid @field-border; } -.Form-dropDown:focus{ +.Form-dropDown:focus { outline: none!important; } -.Form-dropDown--scmType{ +.Form-dropDown--scmType { width: 100%; } -.Form-passwordButton{ +.Form-passwordButton { height: 30px; color: @default-interface-txt; text-transform: uppercase; @@ -435,7 +435,7 @@ color: @field-lookup-btn-icon; } -.Form-passwordButton:focus{ +.Form-passwordButton:focus { border: 1px solid @field-border; background-color: @field-lookup-btn-hov-bg; } @@ -470,13 +470,13 @@ border-right: 0px; } -.CodeMirror{ +.CodeMirror { border-radius: 5px; font-style: normal; color: @field-input-text; } -.CodeMirror-gutters{ +.CodeMirror-gutters { background-color:@code-mirror-gutter !important; } @@ -562,7 +562,7 @@ input[type='radio']:checked:before { display: inline-block !important; } -.Form-inputLabel{ +.Form-inputLabel { text-transform: uppercase; color: @default-interface-txt; font-weight: normal; @@ -582,7 +582,7 @@ input[type='radio']:checked:before { .noselect; } -.Form-buttons{ +.Form-buttons { height: 30px; display: flex; justify-content: flex-end; @@ -592,7 +592,7 @@ input[type='radio']:checked:before { } } -.Form-button{ +.Form-button { margin-left: 4px; } @@ -602,7 +602,7 @@ input[type='radio']:checked:before { border-color: @default-border; } -.Form-saveButton, .Form-launchButton{ +.Form-saveButton, .Form-launchButton { background-color: @submit-button-bg; color: @submit-button-text; text-transform: uppercase; @@ -611,11 +611,11 @@ input[type='radio']:checked:before { padding-right: 15px; } -.Form-saveButton:disabled, .Form-launchButton:disabled{ +.Form-saveButton:disabled, .Form-launchButton:disabled { background-color: @submit-button-bg-dis; } -.Form-saveButton:hover, .Form-launchButton:hover{ +.Form-saveButton:hover, .Form-launchButton:hover { background-color: @submit-button-bg-hov; color: @submit-button-text; } @@ -632,7 +632,7 @@ input[type='radio']:checked:before { margin-left: 20px; } -.Form-cancelButton:hover{ +.Form-cancelButton:hover { background-color: @btn-bg-hov; color: @btn-txt; } From be39c483b467e67e11e8d157764388c76c06a968 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 26 Jul 2017 09:34:53 -0400 Subject: [PATCH 073/342] WIP - Show only two panels at a time --- .../management-jobs/card/card.controller.js | 2 +- .../management-jobs/card/card.partial.html | 83 ++++++++++--------- .../src/management-jobs/scheduler/main.js | 12 +-- .../src/scheduler/schedulerEdit.controller.js | 4 +- .../src/scheduler/schedulerList.controller.js | 2 +- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/awx/ui/client/src/management-jobs/card/card.controller.js b/awx/ui/client/src/management-jobs/card/card.controller.js index ce94245d9a..a29c6f93d6 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -258,7 +258,7 @@ export default }; $scope.configureSchedule = function(id) { - $state.transitionTo('managementJobSchedules', { + $state.transitionTo('managementJobsList.schedule', { id: id }); }; diff --git a/awx/ui/client/src/management-jobs/card/card.partial.html b/awx/ui/client/src/management-jobs/card/card.partial.html index b2d9be4812..66aa4befbe 100644 --- a/awx/ui/client/src/management-jobs/card/card.partial.html +++ b/awx/ui/client/src/management-jobs/card/card.partial.html @@ -1,42 +1,45 @@ -
-
-
-
- MANAGEMENT JOBS -
- - {{ mgmtCards.length }} - -
-
-
- -
-

{{ card.name }}

-
- - - -
+
+ +
+
+
+
+ MANAGEMENT JOBS
- - -

{{card.description || "Place organization description here"}}

- + + {{ mgmtCards.length }} +
-
+
+
+ +
+

{{ card.name }}

+
+ + + +
+
+ + +

{{card.description || "Place organization description here"}}

+ +
+
+
\ No newline at end of file diff --git a/awx/ui/client/src/management-jobs/scheduler/main.js b/awx/ui/client/src/management-jobs/scheduler/main.js index e924150ef7..b40dda21ac 100644 --- a/awx/ui/client/src/management-jobs/scheduler/main.js +++ b/awx/ui/client/src/management-jobs/scheduler/main.js @@ -19,14 +19,14 @@ angular.module('managementJobScheduler', []) .run(['$stateExtender', function($stateExtender) { $stateExtender.addState({ searchPrefix: 'schedule', - name: 'managementJobSchedules', + name: 'managementJobsList.schedule', route: '/management_jobs/:id/schedules', ncyBreadcrumb: { parent: 'managementJobsList', label: N_('SCHEDULES') }, views: { - '@': { + '@managementJobsList': { templateProvider: function(ScheduleList, generateList, ParentObject) { // include name of parent resource in listTitle ScheduleList.listTitle = `${ParentObject.name}
` + N_('SCHEDULES'); @@ -74,10 +74,10 @@ angular.module('managementJobScheduler', []) } }); $stateExtender.addState({ - name: 'managementJobSchedules.add', + name: 'managementJobsList.schedule.add', route: '/add', ncyBreadcrumb: { - parent: 'managementJobSchedules', + parent: 'managementJobsList.schedule', label: N_('CREATE SCHEDULED JOB') }, views: { @@ -88,10 +88,10 @@ angular.module('managementJobScheduler', []) } }); $stateExtender.addState({ - name: 'managementJobSchedules.edit', + name: 'managementJobsList.schedule.edit', route: '/edit/:schedule_id', ncyBreadcrumb: { - parent: 'managementJobSchedules', + parent: 'managementJobsList.schedule', label: N_('EDIT SCHEDULED JOB') }, views: { diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index 1b328b0926..c8e7dab9ca 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -18,7 +18,7 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, /* * This is a workaround for the angular-scheduler library inserting `ll` into fields after an - * invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars + * invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars * in that case. * * Because this same problem exists in the edit mode and because there's no inheritence, this @@ -91,7 +91,7 @@ function($filter, $state, $stateParams, EditSchedule, Wait, $scope, $rootScope, }; // extra_data field is not manifested in the UI when scheduling a Management Job - if ($state.current.name !== 'managementJobSchedules.add' && $state.current.name !== 'managementJobSchedules.edit'){ + if ($state.current.name !== 'managementJobsList.schedule.add' && $state.current.name !== 'managementJobsList.schedule.edit'){ $scope.$on('ScheduleFound', function(){ let readOnly = !$scope.schedule_obj.summary_fields.user_capabilities .edit; diff --git a/awx/ui/client/src/scheduler/schedulerList.controller.js b/awx/ui/client/src/scheduler/schedulerList.controller.js index db400dc9c4..fe6e884ce7 100644 --- a/awx/ui/client/src/scheduler/schedulerList.controller.js +++ b/awx/ui/client/src/scheduler/schedulerList.controller.js @@ -183,7 +183,7 @@ export default [ case 'system_job': deferred.resolve({ - name: 'managementJobSchedules.edit', + name: 'managementJobsList.schedule.edit', params: { id: schedule.unified_job_template, schedule_id: schedule.id From faa244da8c21ca0272e696585599245542013b48 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Mon, 24 Jul 2017 12:07:55 -0400 Subject: [PATCH 074/342] Prevent DB changes for read_only tower configurations --- awx/conf/registry.py | 3 +++ awx/conf/views.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/awx/conf/registry.py b/awx/conf/registry.py index 92e58200aa..00a2cd4057 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -116,6 +116,9 @@ class SettingsRegistry(object): def is_setting_encrypted(self, setting): return bool(self._registry.get(setting, {}).get('encrypted', False)) + def is_setting_read_only(self, setting): + return bool(self._registry.get(setting, {}).get('read_only', False)) + def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs): from rest_framework.fields import empty field_kwargs = {} diff --git a/awx/conf/views.py b/awx/conf/views.py index 4fb4cd60c7..e476f5b0cf 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -122,16 +122,18 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView): user = self.request.user if self.category_slug == 'user' else None settings_change_list = [] for key, value in serializer.validated_data.items(): - if key == 'LICENSE': + if key == 'LICENSE' or settings_registry.is_setting_read_only(key): continue - if settings_registry.is_setting_encrypted(key) and isinstance(value, basestring) and value.startswith('$encrypted$'): + if settings_registry.is_setting_encrypted(key) and \ + isinstance(value, basestring) and \ + value.startswith('$encrypted$'): continue setattr(serializer.instance, key, value) setting = settings_qs.filter(key=key).order_by('pk').first() if not setting: setting = Setting.objects.create(key=key, user=user, value=value) settings_change_list.append(key) - elif setting.value != value or type(setting.value) != type(value): + elif setting.value != value: setting.value = value setting.save(update_fields=['value']) settings_change_list.append(key) From a867411507f0c7c1a402ef64f22d83f13700960a Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 26 Jul 2017 11:33:27 -0400 Subject: [PATCH 075/342] Show api errors when attempting circular association --- awx/ui/client/src/app.js | 1 - .../related/groups/hosts-related-groups-associate.route.js | 1 - .../nested-groups/group-nested-groups-associate.route.js | 1 - .../nested-hosts/group-nested-hosts-associate.route.js | 1 - .../nested-groups/host-nested-groups-associate.route.js | 1 - .../shared/associate-groups/associate-groups.controller.js | 7 +------ .../shared/associate-hosts/associate-hosts.controller.js | 7 +------ 7 files changed, 2 insertions(+), 17 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 1a2f427a85..8d3c38e6e7 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -206,7 +206,6 @@ var awApp = angular.module('awApp', [ LoadConfig, Store, pendoService, Prompt, Rest, Wait, ProcessErrors, $state, GetBasePath, ConfigService, FeaturesService, $filter, SocketService, AppStrings) { - console.log("This works fine!"); $rootScope.$state = $state; $rootScope.$state.matches = function(stateName) { return $state.current.name.search(stateName) > 0; diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js index c1567200d2..9ed6dbfa0b 100644 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js +++ b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js @@ -26,7 +26,6 @@ export default { onExit: function($state) { if ($state.transition) { $('#associate-groups-modal').modal('hide'); - $('.modal-backdrop').remove(); $('body').removeClass('modal-open'); } }, diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js index 294dcf031c..6f8f86e860 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js @@ -26,7 +26,6 @@ export default { onExit: function($state) { if ($state.transition) { $('#associate-groups-modal').modal('hide'); - $('.modal-backdrop').remove(); $('body').removeClass('modal-open'); } }, diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js index 959055ad02..1d1ad65388 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js @@ -26,7 +26,6 @@ export default { onExit: function($state) { if ($state.transition) { $('#associate-groups-modal').modal('hide'); - $('.modal-backdrop').remove(); $('body').removeClass('modal-open'); } }, diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js index d17a181687..c710a76754 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js @@ -26,7 +26,6 @@ export default { onExit: function($state) { if ($state.transition) { $('#associate-groups-modal').modal('hide'); - $('.modal-backdrop').remove(); $('body').removeClass('modal-open'); } }, diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js index 7cb16ca60c..bfc4cda17e 100644 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js +++ b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js @@ -94,13 +94,8 @@ $scope.saveFunction({selectedItems: $scope.selectedItems}) .then(() =>{ $scope.closeModal(); - }).catch((error) => { + }).catch(() => { $scope.closeModal(); - ProcessErrors(null, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to associate host to group(s): POST returned status' + - error.status - }); }); }; diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js index a5e5f4967a..9a3014afcf 100644 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js +++ b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js @@ -93,13 +93,8 @@ $scope.saveFunction({selectedItems: $scope.selectedItems}) .then(() =>{ $scope.closeModal(); - }).catch((error) => { + }).catch(() => { $scope.closeModal(); - ProcessErrors(null, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to associate host to host(s): POST returned status' + - error.status - }); }); }; From cf033f31906ff44192cd1467c0e80e7068842ddf Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 26 Jul 2017 09:43:25 -0400 Subject: [PATCH 076/342] introduce new parent task class in order to log exceptions --- awx/main/tasks.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 1172027653..4259b3cc96 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -74,6 +74,12 @@ Try upgrading OpenSSH or providing your private key in an different format. \ logger = logging.getLogger('awx.main.tasks') +class LogErrorsTask(Task): + def on_failure(self, exc, task_id, args, kwargs, einfo): + logger.exception('Task {} encountered exception.'.format(self.name), exc_info=exc) + super(LogErrorsTask, self).on_failure(exc, task_id, args, kwargs, einfo) + + @celeryd_init.connect def celery_startup(conf=None, **kwargs): # Re-init all schedules @@ -86,8 +92,8 @@ def celery_startup(conf=None, **kwargs): from awx.main.signals import disable_activity_stream with disable_activity_stream(): sch.save() - except Exception as e: - logger.error("Failed to rebuild schedule {}: {}".format(sch, e)) + except: + logger.exception("Failed to rebuild schedule {}.".format(sch)) @worker_process_init.connect @@ -96,7 +102,7 @@ def task_set_logger_pre_run(*args, **kwargs): configure_external_logger(settings, is_startup=False) -@task(queue='tower_broadcast_all', bind=True) +@task(queue='tower_broadcast_all', bind=True, base=LogErrorsTask) def handle_setting_changes(self, setting_keys): orig_len = len(setting_keys) for i in range(orig_len): @@ -113,7 +119,7 @@ def handle_setting_changes(self, setting_keys): break -@task(queue='tower') +@task(queue='tower', base=LogErrorsTask) def send_notifications(notification_list, job_id=None): if not isinstance(notification_list, list): raise TypeError("notification_list should be of type list") @@ -137,7 +143,7 @@ def send_notifications(notification_list, job_id=None): notification.save() -@task(bind=True, queue='tower') +@task(bind=True, queue='tower', base=LogErrorsTask) def run_administrative_checks(self): logger.warn("Running administrative checks.") if not settings.TOWER_ADMIN_ALERTS: @@ -159,13 +165,13 @@ def run_administrative_checks(self): fail_silently=True) -@task(bind=True, queue='tower') +@task(bind=True, queue='tower', base=LogErrorsTask) def cleanup_authtokens(self): logger.warn("Cleaning up expired authtokens.") AuthToken.objects.filter(expires__lt=now()).delete() -@task(bind=True) +@task(bind=True, base=LogErrorsTask) def purge_old_stdout_files(self): nowtime = time.time() for f in os.listdir(settings.JOBOUTPUT_ROOT): @@ -174,7 +180,7 @@ def purge_old_stdout_files(self): logger.info("Removing {}".format(os.path.join(settings.JOBOUTPUT_ROOT,f))) -@task(bind=True) +@task(bind=True, base=LogErrorsTask) def cluster_node_heartbeat(self): logger.debug("Cluster node heartbeat task.") nowtime = now() @@ -206,7 +212,7 @@ def cluster_node_heartbeat(self): raise RuntimeError("Shutting down.") -@task(bind=True) +@task(bind=True, base=LogErrorsTask) def tower_isolated_heartbeat(self): local_hostname = settings.CLUSTER_HOST_ID logger.debug("Controlling node checking for any isolated management tasks.") @@ -230,7 +236,7 @@ def tower_isolated_heartbeat(self): isolated_manager.IsolatedManager.health_check(isolated_instance_qs) -@task(bind=True, queue='tower') +@task(bind=True, queue='tower', base=LogErrorsTask) def tower_periodic_scheduler(self): run_now = now() state = TowerScheduleState.get_solo() @@ -280,7 +286,7 @@ def _send_notification_templates(instance, status_str): job_id=instance.id) -@task(bind=True, queue='tower') +@task(bind=True, queue='tower', base=LogErrorsTask) def handle_work_success(self, result, task_actual): try: instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id']) @@ -296,7 +302,7 @@ def handle_work_success(self, result, task_actual): run_job_complete.delay(instance.id) -@task(bind=True, queue='tower') +@task(bind=True, queue='tower', base=LogErrorsTask) def handle_work_error(self, task_id, subtasks=None): print('Executing error task id %s, subtasks: %s' % (str(self.request.id), str(subtasks))) @@ -336,7 +342,7 @@ def handle_work_error(self, task_id, subtasks=None): pass -@task(queue='tower') +@task(queue='tower', base=LogErrorsTask) def update_inventory_computed_fields(inventory_id, should_update_hosts=True): ''' Signal handler and wrapper around inventory.update_computed_fields to @@ -350,7 +356,7 @@ def update_inventory_computed_fields(inventory_id, should_update_hosts=True): i.update_computed_fields(update_hosts=should_update_hosts) -@task(queue='tower') +@task(queue='tower', base=LogErrorsTask) def update_host_smart_inventory_memberships(): try: with transaction.atomic(): @@ -366,7 +372,7 @@ def update_host_smart_inventory_memberships(): return -@task(queue='tower') +@task(queue='tower', base=LogErrorsTask) def delete_inventory(inventory_id): with ignore_inventory_computed_fields(), \ ignore_inventory_group_removal(): @@ -401,7 +407,7 @@ def with_path_cleanup(f): return _wrapped -class BaseTask(Task): +class BaseTask(LogErrorsTask): name = None model = None abstract = True From 08f3d95926209b7c3b865a4115a1d260e3b1387c Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 26 Jul 2017 12:37:38 -0400 Subject: [PATCH 077/342] Fix UX items related to Project form --- awx/ui/client/legacy-styles/forms.less | 5 ++--- awx/ui/client/src/projects/projects.form.js | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index fdde7e18a4..99cc2b365d 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -294,11 +294,10 @@ } .Form-alertblock { - margin: 20px 0; + margin: 0; font-size: 12px; width: 100%; - padding: 15px; - padding-top: 10px; + padding: 20px; margin-bottom: 15px; border-radius: 4px; border: 1px solid @login-notice-border; diff --git a/awx/ui/client/src/projects/projects.form.js b/awx/ui/client/src/projects/projects.form.js index 3c81bc8b00..3d4f8c56ef 100644 --- a/awx/ui/client/src/projects/projects.form.js +++ b/awx/ui/client/src/projects/projects.form.js @@ -55,6 +55,7 @@ export default ['i18n', 'NotificationsList', function(i18n, NotificationsList) { label: i18n._('SCM Type'), type: 'select', class: 'Form-dropDown--scmType', + defaultText: 'Choose an SCM Type', ngOptions: 'type.label for type in scm_type_options track by type.value', ngChange: 'scmChange()', required: true, From ef055110f501689bbf42af8b80729cc388ce0aaa Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 26 Jul 2017 12:41:13 -0400 Subject: [PATCH 078/342] handle unicode host names in fact cache --- awx/main/models/jobs.py | 4 ++-- awx/main/tests/unit/models/test_jobs.py | 14 ++++++++++++++ awx/plugins/fact_caching/tower.py | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 1dee17bd56..049df6f2a5 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -716,10 +716,10 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana return '{}'.format(self.inventory.id) def memcached_fact_host_key(self, host_name): - return '{}-{}'.format(self.inventory.id, base64.b64encode(host_name)) + return '{}-{}'.format(self.inventory.id, base64.b64encode(host_name.encode('utf-8'))) def memcached_fact_modified_key(self, host_name): - return '{}-{}-modified'.format(self.inventory.id, base64.b64encode(host_name)) + return '{}-{}-modified'.format(self.inventory.id, base64.b64encode(host_name.encode('utf-8'))) def _get_inventory_hosts(self, only=['name', 'ansible_facts', 'modified',]): return self.inventory.hosts.only(*only) diff --git a/awx/main/tests/unit/models/test_jobs.py b/awx/main/tests/unit/models/test_jobs.py index 0e9113cf7a..e60b775066 100644 --- a/awx/main/tests/unit/models/test_jobs.py +++ b/awx/main/tests/unit/models/test_jobs.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import pytest from awx.main.models import ( @@ -111,6 +113,18 @@ def test_start_job_fact_cache_existing_host(hosts, hosts2, job, job2, inventory, assert ansible_facts_cached == json.dumps(hosts[1].ansible_facts) +def test_memcached_fact_host_key_unicode(job): + host_name = u'Iñtërnâtiônàlizætiøn' + host_key = job.memcached_fact_host_key(host_name) + assert host_key == '5-ScOxdMOrcm7DonRpw7Ruw6BsaXrDpnRpw7hu' + + +def test_memcached_fact_modified_key_unicode(job): + host_name = u'Iñtërnâtiônàlizætiøn' + host_key = job.memcached_fact_modified_key(host_name) + assert host_key == '5-ScOxdMOrcm7DonRpw7Ruw6BsaXrDpnRpw7hu-modified' + + def test_finish_job_fact_cache(job, hosts, inventory, mocker, new_time): job.start_job_fact_cache() diff --git a/awx/plugins/fact_caching/tower.py b/awx/plugins/fact_caching/tower.py index 86624f75da..8267a0d498 100755 --- a/awx/plugins/fact_caching/tower.py +++ b/awx/plugins/fact_caching/tower.py @@ -57,10 +57,10 @@ class CacheModule(BaseCacheModule): return '{}'.format(self._inventory_id) def translate_host_key(self, host_name): - return '{}-{}'.format(self._inventory_id, base64.b64encode(host_name)) + return '{}-{}'.format(self._inventory_id, base64.b64encode(host_name.encode('utf-8'))) def translate_modified_key(self, host_name): - return '{}-{}-modified'.format(self._inventory_id, base64.b64encode(host_name)) + return '{}-{}-modified'.format(self._inventory_id, base64.b64encode(host_name.encode('utf-8'))) def get(self, key): host_key = self.translate_host_key(key) From c7a85d9738a1e7ddd52a65968766800ed8c34d12 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 10:58:46 -0400 Subject: [PATCH 079/342] Mass rename from ansible_(awx|tower) -> (awx|tower) --- awx/__init__.py | 2 +- awx/api/filters.py | 2 +- awx/api/metadata.py | 2 +- awx/api/templates/api/api_root_view.md | 2 +- awx/api/templates/api/api_v2_root_view.md | 2 +- awx/main/isolated/run.py | 6 ++-- awx/main/models/base.py | 2 +- awx/main/models/ha.py | 6 ++-- awx/main/models/notifications.py | 10 +++--- awx/main/notifications/base.py | 2 +- awx/main/tasks.py | 36 +++++++++---------- awx/main/tests/job_base.py | 4 +-- awx/main/tests/unit/test_tasks.py | 12 +++---- awx/main/utils/__init__.py | 2 +- awx/main/utils/common.py | 6 ++-- awx/main/utils/handlers.py | 6 ++-- awx/main/utils/mem_inventory.py | 2 +- awx/main/utils/reload.py | 2 +- awx/sso/views.py | 2 +- awx/templates/error.html | 2 +- awx/templates/rest_framework/api.html | 2 +- awx/templates/rest_framework/base.html | 2 +- setup.py | 6 ++-- tools/docker-compose/Dockerfile | 4 +-- tools/docker-compose/awx-manage | 6 ++-- .../PKG-INFO | 8 ++--- .../SOURCES.txt | 0 .../dependency_links.txt | 0 .../entry_points.txt | 0 .../not-zip-safe | 0 .../top_level.txt | 0 .../{ansible-awx.egg-link => awx.egg-link} | 0 tools/docker-compose/start_development.sh | 6 ++-- tools/docker-isolated/awx-expect | 2 +- tools/scripts/awx-expect | 2 +- 35 files changed, 74 insertions(+), 74 deletions(-) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/PKG-INFO (76%) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/SOURCES.txt (100%) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/dependency_links.txt (100%) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/entry_points.txt (100%) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/not-zip-safe (100%) rename tools/docker-compose/{ansible_awx.egg-info => awx.egg-info}/top_level.txt (100%) rename tools/docker-compose/{ansible-awx.egg-link => awx.egg-link} (100%) diff --git a/awx/__init__.py b/awx/__init__.py index 39111823c6..b35364ce35 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -7,7 +7,7 @@ import warnings from pkg_resources import get_distribution -__version__ = get_distribution('ansible-awx').version +__version__ = get_distribution('awx').version __all__ = ['__version__'] diff --git a/awx/api/filters.py b/awx/api/filters.py index afce3dfe57..a231c8af8d 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -20,7 +20,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ParseError, PermissionDenied from rest_framework.filters import BaseFilterBackend -# Ansible Tower +# AWX from awx.main.utils import get_type_for_model, to_python_boolean from awx.main.models.credential import CredentialType from awx.main.models.rbac import RoleAncestorEntry diff --git a/awx/api/metadata.py b/awx/api/metadata.py index b0879f5b41..87aec4b69f 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -16,7 +16,7 @@ from rest_framework import serializers from rest_framework.relations import RelatedField, ManyRelatedField from rest_framework.request import clone_request -# Ansible Tower +# AWX from awx.main.models import InventorySource, NotificationTemplate diff --git a/awx/api/templates/api/api_root_view.md b/awx/api/templates/api/api_root_view.md index 1d487dc9fe..fdef862d17 100644 --- a/awx/api/templates/api/api_root_view.md +++ b/awx/api/templates/api/api_root_view.md @@ -1,4 +1,4 @@ -The root of the Ansible Tower REST API. +The root of the REST API. Make a GET request to this resource to obtain information about the available API versions. diff --git a/awx/api/templates/api/api_v2_root_view.md b/awx/api/templates/api/api_v2_root_view.md index 837d90c1b4..b35e523a51 100644 --- a/awx/api/templates/api/api_v2_root_view.md +++ b/awx/api/templates/api/api_v2_root_view.md @@ -1,4 +1,4 @@ -Version 2 of the Ansible Tower REST API. +Version 2 of the REST API. Make a GET request to this resource to obtain a list of all child resources available via the API. diff --git a/awx/main/isolated/run.py b/awx/main/isolated/run.py index db14be820d..432a9b1c11 100755 --- a/awx/main/isolated/run.py +++ b/awx/main/isolated/run.py @@ -180,10 +180,10 @@ def run_isolated_job(private_data_dir, secrets, logfile=sys.stdout): pexpect_timeout = secrets.get('pexpect_timeout', 5) # Use local callback directory - callback_dir = os.getenv('TOWER_LIB_DIRECTORY') + callback_dir = os.getenv('AWX_LIB_DIRECTORY') if callback_dir is None: - raise RuntimeError('Location for Tower Ansible callbacks must be specified ' - 'by environment variable TOWER_LIB_DIRECTORY.') + raise RuntimeError('Location for callbacks must be specified ' + 'by environment variable AWX_LIB_DIRECTORY.') env['ANSIBLE_CALLBACK_PLUGINS'] = os.path.join(callback_dir, 'isolated_callbacks') if 'AD_HOC_COMMAND_ID' in env: env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 8ba549a2e5..ff0a9797fd 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -20,7 +20,7 @@ from taggit.managers import TaggableManager # Django-CRUM from crum import get_current_user -# Ansible Tower +# AWX from awx.main.utils import encrypt_field __all__ = ['prevent_search', 'VarsDictProperty', 'BaseModel', 'CreatedModifiedModel', diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 600a72af27..2790f89ca6 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -19,7 +19,7 @@ __all__ = ('Instance', 'InstanceGroup', 'JobOrigin', 'TowerScheduleState',) class Instance(models.Model): - """A model representing an Ansible Tower instance running against this database.""" + """A model representing an AWX instance running against this database.""" objects = InstanceManager() uuid = models.CharField(max_length=40) @@ -51,11 +51,11 @@ class Instance(models.Model): @property def role(self): # NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing - return "tower" + return "awx" class InstanceGroup(models.Model): - """A model representing a Queue/Group of Tower Instances.""" + """A model representing a Queue/Group of AWX Instances.""" name = models.CharField(max_length=250, unique=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index f98bcd99b1..3d81f36906 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -194,11 +194,11 @@ class JobNotificationMixin(object): def _build_notification_message(self, status_str): notification_body = self.notification_data() - notification_subject = u"{} #{} '{}' {} on Ansible Tower: {}".format(self.get_notification_friendly_name(), - self.id, - self.name, - status_str, - notification_body['url']) + notification_subject = u"{} #{} '{}' {}: {}".format(self.get_notification_friendly_name(), + self.id, + self.name, + status_str, + notification_body['url']) notification_body['friendly_name'] = self.get_notification_friendly_name() return (notification_subject, notification_body) diff --git a/awx/main/notifications/base.py b/awx/main/notifications/base.py index 58202f6612..7d6ed13910 100644 --- a/awx/main/notifications/base.py +++ b/awx/main/notifications/base.py @@ -14,7 +14,7 @@ class TowerBaseEmailBackend(BaseEmailBackend): if "body" in body: body_actual = body['body'] else: - body_actual = smart_text(_("{} #{} had status {} on Ansible Tower, view details at {}\n\n").format( + body_actual = smart_text(_("{} #{} had status {}, view details at {}\n\n").format( body['friendly_name'], body['id'], body['status'], body['url']) ) body_actual += json.dumps(body, indent=4) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 11e7b9efd0..31237a6f54 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -43,8 +43,8 @@ from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist # AWX -from awx import __version__ as tower_application_version -from awx.main.constants import CLOUD_PROVIDERS, PRIVILEGE_ESCALATION_METHODS +from awx import __version__ as awx_application_version +from awx.main.constants import CLOUD_PROVIDERS from awx.main.models import * # noqa from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.queue import CallbackQueueDispatcher @@ -188,7 +188,7 @@ def cluster_node_heartbeat(self): if inst.exists(): inst = inst[0] inst.capacity = get_system_task_capacity() - inst.version = tower_application_version + inst.version = awx_application_version inst.save() else: raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID)) @@ -197,7 +197,7 @@ def cluster_node_heartbeat(self): for other_inst in recent_inst: if other_inst.version == "": continue - if Version(other_inst.version.split('-', 1)[0]) > Version(tower_application_version) and not settings.DEBUG: + if Version(other_inst.version.split('-', 1)[0]) > Version(awx_application_version) and not settings.DEBUG: logger.error("Host {} reports version {}, but this node {} is at {}, shutting down".format(other_inst.hostname, other_inst.version, inst.hostname, @@ -478,7 +478,7 @@ class BaseTask(LogErrorsTask): ''' Create a temporary directory for job-related files. ''' - path = tempfile.mkdtemp(prefix='ansible_awx_%s_' % instance.pk, dir=settings.AWX_PROOT_BASE_PATH) + path = tempfile.mkdtemp(prefix='awx_%s_' % instance.pk, dir=settings.AWX_PROOT_BASE_PATH) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) self.cleanup_paths.append(path) return path @@ -541,7 +541,7 @@ class BaseTask(LogErrorsTask): '': '', } - def add_ansible_venv(self, env, add_tower_lib=True): + def add_ansible_venv(self, env, add_awx_lib=True): env['VIRTUAL_ENV'] = settings.ANSIBLE_VENV_PATH env['PATH'] = os.path.join(settings.ANSIBLE_VENV_PATH, "bin") + ":" + env['PATH'] venv_libdir = os.path.join(settings.ANSIBLE_VENV_PATH, "lib") @@ -551,11 +551,11 @@ class BaseTask(LogErrorsTask): env['PYTHONPATH'] = os.path.join(venv_libdir, python_ver, "site-packages") + ":" break # Add awx/lib to PYTHONPATH. - if add_tower_lib: + if add_awx_lib: env['PYTHONPATH'] = env.get('PYTHONPATH', '') + self.get_path_to('..', 'lib') + ':' return env - def add_tower_venv(self, env): + def add_awx_venv(self, env): env['VIRTUAL_ENV'] = settings.AWX_VENV_PATH env['PATH'] = os.path.join(settings.AWX_VENV_PATH, "bin") + ":" + env['PATH'] return env @@ -576,7 +576,7 @@ class BaseTask(LogErrorsTask): # callbacks to work. # Update PYTHONPATH to use local site-packages. # NOTE: - # Derived class should call add_ansible_venv() or add_tower_venv() + # Derived class should call add_ansible_venv() or add_awx_venv() if self.should_use_proot(instance, **kwargs): env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH return env @@ -612,7 +612,7 @@ class BaseTask(LogErrorsTask): # For isolated jobs, we have to interact w/ the REST API from the # controlling node and ship the static JSON inventory to the # isolated host (because the isolated host itself can't reach the - # Tower REST API to fetch the inventory). + # REST API to fetch the inventory). path = os.path.join(kwargs['private_data_dir'], 'inventory') if os.path.exists(path): return path @@ -932,7 +932,7 @@ class RunJob(BaseTask): plugin_dirs.extend(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) plugin_path = ':'.join(plugin_dirs) env = super(RunJob, self).build_env(job, **kwargs) - env = self.add_ansible_venv(env, add_tower_lib=kwargs.get('isolated', False)) + env = self.add_ansible_venv(env, add_awx_lib=kwargs.get('isolated', False)) # Set environment variables needed for inventory and job event # callbacks to work. env['JOB_ID'] = str(job.pk) @@ -1073,7 +1073,7 @@ class RunJob(BaseTask): if job.start_at_task: args.append('--start-at-task=%s' % job.start_at_task) - # Define special extra_vars for Tower, combine with job.extra_vars. + # Define special extra_vars for AWX, combine with job.extra_vars. extra_vars = { 'tower_job_id': job.pk, 'tower_job_launch_type': job.launch_type, @@ -1738,7 +1738,7 @@ class RunInventoryUpdate(BaseTask): """ env = super(RunInventoryUpdate, self).build_env(inventory_update, **kwargs) - env = self.add_tower_venv(env) + env = self.add_awx_venv(env) # Pass inventory source ID to inventory script. env['INVENTORY_SOURCE_ID'] = str(inventory_update.inventory_source_id) env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) @@ -1748,7 +1748,7 @@ class RunInventoryUpdate(BaseTask): # These are set here and then read in by the various Ansible inventory # modules, which will actually do the inventory sync. # - # The inventory modules are vendored in Tower in the + # The inventory modules are vendored in AWX in the # `awx/plugins/inventory` directory; those files should be kept in # sync with those in Ansible core at all times. passwords = kwargs.get('passwords', {}) @@ -1820,9 +1820,9 @@ class RunInventoryUpdate(BaseTask): src = inventory_update.source # Add several options to the shell arguments based on the - # inventory-source-specific setting in the Tower configuration. + # inventory-source-specific setting in the AWX configuration. # These settings are "per-source"; it's entirely possible that - # they will be different between cloud providers if a Tower user + # they will be different between cloud providers if an AWX user # actively uses more than one. if getattr(settings, '%s_ENABLED_VAR' % src.upper(), False): args.extend(['--enabled-var', @@ -1852,7 +1852,7 @@ class RunInventoryUpdate(BaseTask): elif src == 'scm': args.append(inventory_update.get_actual_source_path()) elif src == 'custom': - runpath = tempfile.mkdtemp(prefix='ansible_awx_inventory_', dir=settings.AWX_PROOT_BASE_PATH) + runpath = tempfile.mkdtemp(prefix='awx_inventory_', dir=settings.AWX_PROOT_BASE_PATH) handle, path = tempfile.mkstemp(dir=runpath) f = os.fdopen(handle, 'w') if inventory_update.source_script is None: @@ -2139,7 +2139,7 @@ class RunSystemJob(BaseTask): def build_env(self, instance, **kwargs): env = super(RunSystemJob, self).build_env(instance, **kwargs) - env = self.add_tower_venv(env) + env = self.add_awx_venv(env) return env def build_cwd(self, instance, **kwargs): diff --git a/awx/main/tests/job_base.py b/awx/main/tests/job_base.py index 1f195c831d..0d36624a79 100644 --- a/awx/main/tests/job_base.py +++ b/awx/main/tests/job_base.py @@ -37,10 +37,10 @@ class BaseJobTestMixin(BaseTestMixin): return inventory def populate(self): - # Here's a little story about the Ansible Bread Company, or ABC. They + # Here's a little story about the AWX Bread Company, or ABC. They # make machines that make bread - bakers, slicers, and packagers - and # these machines are each controlled by a Linux boxes, which is in turn - # managed by Ansible Commander. + # managed by AWX. # Sue is the super user. You don't mess with Sue or you're toast. Ha. self.user_sue = self.make_user('sue', super_user=True) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 234f2c4b73..8aae8dc27b 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -181,7 +181,7 @@ class TestJobExecution: EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----' def setup_method(self, method): - self.project_path = tempfile.mkdtemp(prefix='ansible_awx_project_') + self.project_path = tempfile.mkdtemp(prefix='awx_project_') with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f: f.write('---') @@ -312,7 +312,7 @@ class TestIsolatedExecution(TestJobExecution): credential.inputs['password'] = encrypt_field(credential, 'password') self.instance.credential = credential - private_data = tempfile.mkdtemp(prefix='ansible_awx_') + private_data = tempfile.mkdtemp(prefix='awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) inventory = json.dumps({"all": {"hosts": ["localhost"]}}) @@ -351,7 +351,7 @@ class TestIsolatedExecution(TestJobExecution): extra_vars = json.loads(extra_vars) assert extra_vars['dest'] == '/tmp' assert extra_vars['src'] == private_data - assert extra_vars['proot_temp_dir'].startswith('/tmp/ansible_awx_proot_') + assert extra_vars['proot_temp_dir'].startswith('/tmp/awx_proot_') def test_systemctl_failure(self): # If systemctl fails, read the contents of `artifacts/systemctl_logs` @@ -364,7 +364,7 @@ class TestIsolatedExecution(TestJobExecution): ) self.instance.credential = credential - private_data = tempfile.mkdtemp(prefix='ansible_awx_') + private_data = tempfile.mkdtemp(prefix='awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) inventory = json.dumps({"all": {"hosts": ["localhost"]}}) @@ -464,7 +464,7 @@ class TestJobCredentials(TestJobExecution): ) return ['successful', 0] - private_data = tempfile.mkdtemp(prefix='ansible_awx_') + private_data = tempfile.mkdtemp(prefix='awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) self.run_pexpect.side_effect = partial(run_pexpect_side_effect, private_data) self.task.run(self.pk, private_data_dir=private_data) @@ -1145,7 +1145,7 @@ class TestProjectUpdateCredentials(TestJobExecution): assert 'bob' in kwargs.get('expect_passwords').values() return ['successful', 0] - private_data = tempfile.mkdtemp(prefix='ansible_awx_') + private_data = tempfile.mkdtemp(prefix='awx_') self.task.build_private_data_dir = mock.Mock(return_value=private_data) self.run_pexpect.side_effect = partial(run_pexpect_side_effect, private_data) self.task.run(self.pk) diff --git a/awx/main/utils/__init__.py b/awx/main/utils/__init__.py index fb20b92898..11a34c487c 100644 --- a/awx/main/utils/__init__.py +++ b/awx/main/utils/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ansible Tower by Red Hat +# Copyright (c) 2017 Ansible by Red Hat # All Rights Reserved. # AWX diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 80a72c9cb2..43c9340085 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -152,12 +152,12 @@ def get_ssh_version(): def get_awx_version(): ''' - Return Ansible Tower version as reported by setuptools. + Return AWX version as reported by setuptools. ''' from awx import __version__ try: import pkg_resources - return pkg_resources.require('ansible-awx')[0].version + return pkg_resources.require('awx')[0].version except: return __version__ @@ -655,7 +655,7 @@ def build_proot_temp_dir(): Create a temporary directory for proot to use. ''' from django.conf import settings - path = tempfile.mkdtemp(prefix='ansible_awx_proot_', dir=settings.AWX_PROOT_BASE_PATH) + path = tempfile.mkdtemp(prefix='awx_proot_', dir=settings.AWX_PROOT_BASE_PATH) os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) return path diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index 7f5c919e16..4c88681f4b 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ansible Tower by Red Hat +# Copyright (c) 2017 Ansible by Red Hat # All Rights Reserved. # Python @@ -134,7 +134,7 @@ class BaseHandler(logging.Handler): # Don't send handler-related records. if logger_name == logger.name: return True - # Tower log emission is only turned off by enablement setting + # AWX log emission is only turned off by enablement setting if not logger_name.startswith('awx.analytics'): return False return self.enabled_loggers is None or logger_name[len('awx.analytics.'):] not in self.enabled_loggers @@ -216,7 +216,7 @@ class BaseHTTPSHandler(BaseHandler): logger = logging.getLogger(__file__) fn, lno, func = logger.findCaller() record = logger.makeRecord('awx', 10, fn, lno, - 'Ansible Tower Connection Test', tuple(), + 'AWX Connection Test', tuple(), None, func) futures = handler.emit(record) for future in futures: diff --git a/awx/main/utils/mem_inventory.py b/awx/main/utils/mem_inventory.py index b7530fd358..b2d1c0d691 100644 --- a/awx/main/utils/mem_inventory.py +++ b/awx/main/utils/mem_inventory.py @@ -95,7 +95,7 @@ class MemHost(MemObject): self.instance_id = None self.name = name if port: - # was `ansible_ssh_port` in older Ansible/Tower versions + # was `ansible_ssh_port` in older Ansible versions self.variables['ansible_port'] = port logger.debug('Loaded host: %s', self.name) diff --git a/awx/main/utils/reload.py b/awx/main/utils/reload.py index 25a4d17a57..143a5d01cf 100644 --- a/awx/main/utils/reload.py +++ b/awx/main/utils/reload.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ansible Tower by Red Hat +# Copyright (c) 2017 Ansible by Red Hat # All Rights Reserved. # Python diff --git a/awx/sso/views.py b/awx/sso/views.py index 80092a8040..9e680186a9 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -16,7 +16,7 @@ from django.utils.encoding import smart_text # Django REST Framework from rest_framework.renderers import JSONRenderer -# Ansible Tower +# AWX from awx.main.models import AuthToken from awx.api.serializers import UserSerializer diff --git a/awx/templates/error.html b/awx/templates/error.html index 563befa8a6..69fade819e 100644 --- a/awx/templates/error.html +++ b/awx/templates/error.html @@ -1,7 +1,7 @@ {% extends "rest_framework/api.html" %} {% load i18n staticfiles %} -{% block title %}{{ name }} · {% trans 'Ansible Tower' %}{% endblock %} +{% block title %}{{ name }} · {% trans 'AWX' %}{% endblock %} {% block style %} {{ block.super }} diff --git a/awx/templates/rest_framework/api.html b/awx/templates/rest_framework/api.html index 863b0e22af..0bcc14eaf7 100644 --- a/awx/templates/rest_framework/api.html +++ b/awx/templates/rest_framework/api.html @@ -1,7 +1,7 @@ {% extends 'rest_framework/base.html' %} {% load i18n staticfiles %} -{% block title %}{{ name }} · {% trans 'Ansible Tower REST API' %}{% endblock %} +{% block title %}{{ name }} · {% trans 'AWX REST API' %}{% endblock %} {% block bootstrap_theme %} diff --git a/awx/templates/rest_framework/base.html b/awx/templates/rest_framework/base.html index a6c4169ebd..7ae5cb8c1a 100644 --- a/awx/templates/rest_framework/base.html +++ b/awx/templates/rest_framework/base.html @@ -1,5 +1,5 @@ -{# Copy of base.html from rest_framework with minor Ansible Tower change. #} +{# Copy of base.html from rest_framework with minor AWX change. #} {% load staticfiles %} {% load rest_framework %} {% load i18n %} diff --git a/setup.py b/setup.py index 349910d927..39a145b13c 100755 --- a/setup.py +++ b/setup.py @@ -123,12 +123,12 @@ def proc_data_files(data_files): setup( - name=os.getenv('NAME', 'ansible-awx'), + name=os.getenv('NAME', 'awx'), version=get_version(), author='Ansible, Inc.', author_email='info@ansible.com', - description='ansible-awx: API, UI and Task Engine for Ansible', - long_description='Ansible AWX provides a web-based user interface, REST API and ' + description='awx: API, UI and Task Engine for Ansible', + long_description='AWX provides a web-based user interface, REST API and ' 'task engine built on top of Ansible', license='MIT', keywords='ansible', diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index b9e5298901..0a648c3d5b 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -20,9 +20,9 @@ RUN /usr/bin/ssh-keygen -q -t rsa -N "" -f /root/.ssh/id_rsa RUN mkdir -p /data/db RUN pip2 install honcho RUN pip2 install supervisor -ADD tools/docker-compose/ansible-awx.egg-link /tmp/ansible-awx.egg-link +ADD tools/docker-compose/awx.egg-link /tmp/awx.egg-link ADD tools/docker-compose/awx-manage /usr/local/bin/awx-manage -ADD tools/docker-compose/ansible_awx.egg-info /tmp/ansible_awx.egg-info +ADD tools/docker-compose/awx.egg-info /tmp/awx.egg-info RUN ln -Ffs /awx_devel/tools/docker-compose/nginx.conf /etc/nginx/nginx.conf RUN ln -Ffs /awx_devel/tools/docker-compose/nginx.vh.default.conf /etc/nginx/conf.d/nginx.vh.default.conf RUN ln -s /awx_devel/tools/docker-compose/start_development.sh /start_development.sh diff --git a/tools/docker-compose/awx-manage b/tools/docker-compose/awx-manage index b0a62494c1..65d10accf7 100755 --- a/tools/docker-compose/awx-manage +++ b/tools/docker-compose/awx-manage @@ -1,10 +1,10 @@ #!/venv/awx/bin/python -# EASY-INSTALL-ENTRY-SCRIPT: 'ansible-awx','console_scripts','awx-manage' -__requires__ = 'ansible-awx' +# EASY-INSTALL-ENTRY-SCRIPT: 'awx','console_scripts','awx-manage' import sys from pkg_resources import load_entry_point +__requires__ = 'awx' if __name__ == '__main__': sys.exit( - load_entry_point('ansible-awx', 'console_scripts', 'awx-manage')() + load_entry_point('awx', 'console_scripts', 'awx-manage')() ) diff --git a/tools/docker-compose/ansible_awx.egg-info/PKG-INFO b/tools/docker-compose/awx.egg-info/PKG-INFO similarity index 76% rename from tools/docker-compose/ansible_awx.egg-info/PKG-INFO rename to tools/docker-compose/awx.egg-info/PKG-INFO index eb633cdebb..d02ec5de21 100644 --- a/tools/docker-compose/ansible_awx.egg-info/PKG-INFO +++ b/tools/docker-compose/awx.egg-info/PKG-INFO @@ -1,12 +1,12 @@ Metadata-Version: 1.1 -Name: ansible-awx +Name: awx Version: placeholder -Summary: ansible-awx: API, UI and Task Engine for Ansible -Home-page: http://github.com/ansible/ansible-awx +Summary: awx: API, UI and Task Engine for Ansible +Home-page: http://github.com/ansible/awx Author: Ansible, Inc. Author-email: info@ansible.com License: Proprietary -Description: Ansible AWXprovides a web-based user interface, REST API and task engine built on top of Ansible +Description: Ansible AWX provides a web-based user interface, REST API and task engine built on top of Ansible Keywords: ansible Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable diff --git a/tools/docker-compose/ansible_awx.egg-info/SOURCES.txt b/tools/docker-compose/awx.egg-info/SOURCES.txt similarity index 100% rename from tools/docker-compose/ansible_awx.egg-info/SOURCES.txt rename to tools/docker-compose/awx.egg-info/SOURCES.txt diff --git a/tools/docker-compose/ansible_awx.egg-info/dependency_links.txt b/tools/docker-compose/awx.egg-info/dependency_links.txt similarity index 100% rename from tools/docker-compose/ansible_awx.egg-info/dependency_links.txt rename to tools/docker-compose/awx.egg-info/dependency_links.txt diff --git a/tools/docker-compose/ansible_awx.egg-info/entry_points.txt b/tools/docker-compose/awx.egg-info/entry_points.txt similarity index 100% rename from tools/docker-compose/ansible_awx.egg-info/entry_points.txt rename to tools/docker-compose/awx.egg-info/entry_points.txt diff --git a/tools/docker-compose/ansible_awx.egg-info/not-zip-safe b/tools/docker-compose/awx.egg-info/not-zip-safe similarity index 100% rename from tools/docker-compose/ansible_awx.egg-info/not-zip-safe rename to tools/docker-compose/awx.egg-info/not-zip-safe diff --git a/tools/docker-compose/ansible_awx.egg-info/top_level.txt b/tools/docker-compose/awx.egg-info/top_level.txt similarity index 100% rename from tools/docker-compose/ansible_awx.egg-info/top_level.txt rename to tools/docker-compose/awx.egg-info/top_level.txt diff --git a/tools/docker-compose/ansible-awx.egg-link b/tools/docker-compose/awx.egg-link similarity index 100% rename from tools/docker-compose/ansible-awx.egg-link rename to tools/docker-compose/awx.egg-link diff --git a/tools/docker-compose/start_development.sh b/tools/docker-compose/start_development.sh index 1892674079..9dd095aeed 100755 --- a/tools/docker-compose/start_development.sh +++ b/tools/docker-compose/start_development.sh @@ -21,9 +21,9 @@ else echo "Failed to find awx source tree, map your development tree volume" fi -cp -R /tmp/ansible_awx.egg-info /awx_devel/ || true -sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/ansible_awx.egg-info/PKG-INFO -cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link +cp -R /tmp/awx.egg-info /awx_devel/ || true +sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/awx.egg-info/PKG-INFO +cp /tmp/awx.egg-link /venv/awx/lib/python2.7/site-packages/awx.egg-link ln -s /awx_devel/tools/rdb.py /venv/awx/lib/python2.7/site-packages/rdb.py || true yes | cp -rf /awx_devel/tools/docker-compose/supervisor.conf /supervisor.conf diff --git a/tools/docker-isolated/awx-expect b/tools/docker-isolated/awx-expect index 0ceb7a629c..bf2efb54d2 100755 --- a/tools/docker-isolated/awx-expect +++ b/tools/docker-isolated/awx-expect @@ -1,3 +1,3 @@ #!/bin/bash . /venv/awx/bin/activate -exec env TOWER_LIB_DIRECTORY=/awx_lib /awx_devel/run.py "$@" +exec env AWX_LIB_DIRECTORY=/awx_lib /awx_devel/run.py "$@" diff --git a/tools/scripts/awx-expect b/tools/scripts/awx-expect index bd3364e666..23b85150b0 100755 --- a/tools/scripts/awx-expect +++ b/tools/scripts/awx-expect @@ -1,4 +1,4 @@ #!/bin/bash AWX_LIB=`/var/lib/awx/venv/awx/bin/python -c 'import os, awx; print os.path.dirname(awx.__file__)'` . /var/lib/awx/venv/awx/bin/activate -exec env TOWER_LIB_DIRECTORY=$AWX_LIB/lib /var/lib/awx/venv/awx/bin/python $AWX_LIB/main/isolated/run.pyc "$@" +exec env AWX_LIB_DIRECTORY=$AWX_LIB/lib /var/lib/awx/venv/awx/bin/python $AWX_LIB/main/isolated/run.pyc "$@" From d4b1a07495a746d0cd97f1a35d21255086fe5974 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 12:06:23 -0400 Subject: [PATCH 080/342] Rename tower display plugins to awx display --- .../__init__.py | 6 +++--- .../cleanup.py | 0 .../display.py | 0 .../events.py | 0 .../minimal.py | 0 .../module.py | 8 ++++---- .../{tower_display.py => awx_display.py} | 2 +- awx/lib/isolated_callbacks/minimal.py | 2 +- awx/lib/sitecustomize.py | 4 ++-- awx/lib/tests/test_display_callback.py | 8 ++++---- awx/main/isolated/run.py | 2 +- awx/main/tasks.py | 2 +- awx/main/tests/unit/isolated/test_expect.py | 2 +- awx/plugins/callback/{tower_display.py => awx_display.py} | 2 +- awx/plugins/callback/minimal.py | 2 +- 15 files changed, 20 insertions(+), 20 deletions(-) rename awx/lib/{tower_display_callback => awx_display_callback}/__init__.py (84%) rename awx/lib/{tower_display_callback => awx_display_callback}/cleanup.py (100%) rename awx/lib/{tower_display_callback => awx_display_callback}/display.py (100%) rename awx/lib/{tower_display_callback => awx_display_callback}/events.py (100%) rename awx/lib/{tower_display_callback => awx_display_callback}/minimal.py (100%) rename awx/lib/{tower_display_callback => awx_display_callback}/module.py (98%) rename awx/lib/isolated_callbacks/{tower_display.py => awx_display.py} (92%) rename awx/plugins/callback/{tower_display.py => awx_display.py} (92%) diff --git a/awx/lib/tower_display_callback/__init__.py b/awx/lib/awx_display_callback/__init__.py similarity index 84% rename from awx/lib/tower_display_callback/__init__.py rename to awx/lib/awx_display_callback/__init__.py index d984956c7f..b7cbf97b9b 100644 --- a/awx/lib/tower_display_callback/__init__.py +++ b/awx/lib/awx_display_callback/__init__.py @@ -17,9 +17,9 @@ from __future__ import (absolute_import, division, print_function) -# Tower Display Callback +# AWX Display Callback from . import cleanup # noqa (registers control persistent cleanup) from . import display # noqa (wraps ansible.display.Display methods) -from .module import TowerDefaultCallbackModule, TowerMinimalCallbackModule +from .module import AWXDefaultCallbackModule, AWXMinimalCallbackModule -__all__ = ['TowerDefaultCallbackModule', 'TowerMinimalCallbackModule'] +__all__ = ['AWXDefaultCallbackModule', 'AWXMinimalCallbackModule'] diff --git a/awx/lib/tower_display_callback/cleanup.py b/awx/lib/awx_display_callback/cleanup.py similarity index 100% rename from awx/lib/tower_display_callback/cleanup.py rename to awx/lib/awx_display_callback/cleanup.py diff --git a/awx/lib/tower_display_callback/display.py b/awx/lib/awx_display_callback/display.py similarity index 100% rename from awx/lib/tower_display_callback/display.py rename to awx/lib/awx_display_callback/display.py diff --git a/awx/lib/tower_display_callback/events.py b/awx/lib/awx_display_callback/events.py similarity index 100% rename from awx/lib/tower_display_callback/events.py rename to awx/lib/awx_display_callback/events.py diff --git a/awx/lib/tower_display_callback/minimal.py b/awx/lib/awx_display_callback/minimal.py similarity index 100% rename from awx/lib/tower_display_callback/minimal.py rename to awx/lib/awx_display_callback/minimal.py diff --git a/awx/lib/tower_display_callback/module.py b/awx/lib/awx_display_callback/module.py similarity index 98% rename from awx/lib/tower_display_callback/module.py rename to awx/lib/awx_display_callback/module.py index 0b0b964a72..6800560cfc 100644 --- a/awx/lib/tower_display_callback/module.py +++ b/awx/lib/awx_display_callback/module.py @@ -27,7 +27,7 @@ from copy import copy from ansible.plugins.callback import CallbackBase from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule -# Tower Display Callback +# AWX Display Callback from .events import event_context from .minimal import CallbackModule as MinimalCallbackModule @@ -448,12 +448,12 @@ class BaseCallbackModule(CallbackBase): super(BaseCallbackModule, self).v2_runner_retry(result) -class TowerDefaultCallbackModule(BaseCallbackModule, DefaultCallbackModule): +class AWXDefaultCallbackModule(BaseCallbackModule, DefaultCallbackModule): - CALLBACK_NAME = 'tower_display' + CALLBACK_NAME = 'awx_display' -class TowerMinimalCallbackModule(BaseCallbackModule, MinimalCallbackModule): +class AWXMinimalCallbackModule(BaseCallbackModule, MinimalCallbackModule): CALLBACK_NAME = 'minimal' diff --git a/awx/lib/isolated_callbacks/tower_display.py b/awx/lib/isolated_callbacks/awx_display.py similarity index 92% rename from awx/lib/isolated_callbacks/tower_display.py rename to awx/lib/isolated_callbacks/awx_display.py index dbe463303d..f65e96f9af 100644 --- a/awx/lib/isolated_callbacks/tower_display.py +++ b/awx/lib/isolated_callbacks/awx_display.py @@ -27,4 +27,4 @@ if awx_lib_path not in sys.path: sys.path.insert(0, awx_lib_path) # Tower Display Callback -from tower_display_callback import TowerDefaultCallbackModule as CallbackModule # noqa +from awx_display_callback import AWXDefaultCallbackModule as CallbackModule # noqa diff --git a/awx/lib/isolated_callbacks/minimal.py b/awx/lib/isolated_callbacks/minimal.py index 6c136b7824..f54b706837 100644 --- a/awx/lib/isolated_callbacks/minimal.py +++ b/awx/lib/isolated_callbacks/minimal.py @@ -27,4 +27,4 @@ if awx_lib_path not in sys.path: sys.path.insert(0, awx_lib_path) # Tower Display Callback -from tower_display_callback import TowerMinimalCallbackModule as CallbackModule # noqa +from awx_display_callback import AWXMinimalCallbackModule as CallbackModule # noqa diff --git a/awx/lib/sitecustomize.py b/awx/lib/sitecustomize.py index 224840aae7..3df0dc9aef 100644 --- a/awx/lib/sitecustomize.py +++ b/awx/lib/sitecustomize.py @@ -2,13 +2,13 @@ import os import sys -# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize tower display +# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize awx display # callback as early as possible to wrap ansible.display.Display methods. def argv_ready(argv): if argv and os.path.basename(argv[0]) in {'ansible', 'ansible-playbook'}: - import tower_display_callback # noqa + import awx_display_callback # noqa class argv_placeholder(object): diff --git a/awx/lib/tests/test_display_callback.py b/awx/lib/tests/test_display_callback.py index 48960f97c7..1607544827 100644 --- a/awx/lib/tests/test_display_callback.py +++ b/awx/lib/tests/test_display_callback.py @@ -11,10 +11,10 @@ import pytest # search for a plugin implementation (which should be named `CallbackModule`) # # this code modifies the Python path to make our -# `awx.lib.tower_display_callback` callback importable (because `awx.lib` +# `awx.lib.awx_display_callback` callback importable (because `awx.lib` # itself is not a package) # -# we use the `tower_display_callback` imports below within this file, but +# we use the `awx_display_callback` imports below within this file, but # Ansible also uses them when it discovers this file in # `ANSIBLE_CALLBACK_PLUGINS` CALLBACK = os.path.splitext(os.path.basename(__file__))[0] @@ -32,8 +32,8 @@ with mock.patch.dict(os.environ, {'ANSIBLE_STDOUT_CALLBACK': CALLBACK, if path not in sys.path: sys.path.insert(0, path) - from tower_display_callback import TowerDefaultCallbackModule as CallbackModule # noqa - from tower_display_callback.events import event_context # noqa + from awx_display_callback import AWXDefaultCallbackModule as CallbackModule # noqa + from awx_display_callback.events import event_context # noqa @pytest.fixture() diff --git a/awx/main/isolated/run.py b/awx/main/isolated/run.py index 432a9b1c11..a5d524377a 100755 --- a/awx/main/isolated/run.py +++ b/awx/main/isolated/run.py @@ -188,7 +188,7 @@ def run_isolated_job(private_data_dir, secrets, logfile=sys.stdout): if 'AD_HOC_COMMAND_ID' in env: env['ANSIBLE_STDOUT_CALLBACK'] = 'minimal' else: - env['ANSIBLE_STDOUT_CALLBACK'] = 'tower_display' + env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' env['AWX_ISOLATED_DATA_DIR'] = private_data_dir env['PYTHONPATH'] = env.get('PYTHONPATH', '') + callback_dir + ':' diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 31237a6f54..8b7b622c37 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -949,7 +949,7 @@ class RunJob(BaseTask): env['MAX_EVENT_RES'] = str(settings.MAX_EVENT_RES_DATA) if not kwargs.get('isolated'): env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_path - env['ANSIBLE_STDOUT_CALLBACK'] = 'tower_display' + env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display' env['REST_API_URL'] = settings.INTERNAL_API_URL env['REST_API_TOKEN'] = job.task_auth_token or '' env['TOWER_HOST'] = settings.TOWER_URL_BASE diff --git a/awx/main/tests/unit/isolated/test_expect.py b/awx/main/tests/unit/isolated/test_expect.py index e9e36095d7..a180d3e6c2 100644 --- a/awx/main/tests/unit/isolated/test_expect.py +++ b/awx/main/tests/unit/isolated/test_expect.py @@ -193,7 +193,7 @@ def test_run_isolated_job(private_data_dir, rsa_key): assert FILENAME in stdout.getvalue() assert '/path/to/awx/lib' in env['PYTHONPATH'] - assert env['ANSIBLE_STDOUT_CALLBACK'] == 'tower_display' + assert env['ANSIBLE_STDOUT_CALLBACK'] == 'awx_display' assert env['ANSIBLE_CALLBACK_PLUGINS'] == '/path/to/awx/lib/isolated_callbacks' assert env['AWX_ISOLATED_DATA_DIR'] == private_data_dir diff --git a/awx/plugins/callback/tower_display.py b/awx/plugins/callback/awx_display.py similarity index 92% rename from awx/plugins/callback/tower_display.py rename to awx/plugins/callback/awx_display.py index 725232dfe4..326cc6c7be 100644 --- a/awx/plugins/callback/tower_display.py +++ b/awx/plugins/callback/awx_display.py @@ -27,4 +27,4 @@ if awx_lib_path not in sys.path: sys.path.insert(0, awx_lib_path) # Tower Display Callback -from tower_display_callback import TowerDefaultCallbackModule as CallbackModule # noqa +from awx_display_callback import AWXDefaultCallbackModule as CallbackModule # noqa diff --git a/awx/plugins/callback/minimal.py b/awx/plugins/callback/minimal.py index fcbaa76d55..e41f7ff62f 100644 --- a/awx/plugins/callback/minimal.py +++ b/awx/plugins/callback/minimal.py @@ -27,4 +27,4 @@ if awx_lib_path not in sys.path: sys.path.insert(0, awx_lib_path) # Tower Display Callback -from tower_display_callback import TowerMinimalCallbackModule as CallbackModule # noqa +from awx_display_callback import AWXMinimalCallbackModule as CallbackModule # noqa From cc0802c87ef7580d794de4de0317335fe399a07b Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 12:20:23 -0400 Subject: [PATCH 081/342] Refactor fact cache plugin from tower -> awx --- awx/main/tasks.py | 2 +- awx/plugins/fact_caching/{tower.py => awx.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename awx/plugins/fact_caching/{tower.py => awx.py} (100%) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 8b7b622c37..c8def0fc59 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -940,7 +940,7 @@ class RunJob(BaseTask): if job.use_fact_cache and not kwargs.get('isolated'): env['ANSIBLE_LIBRARY'] = self.get_path_to('..', 'plugins', 'library') env['ANSIBLE_CACHE_PLUGINS'] = self.get_path_to('..', 'plugins', 'fact_caching') - env['ANSIBLE_CACHE_PLUGIN'] = "tower" + env['ANSIBLE_CACHE_PLUGIN'] = "awx" env['ANSIBLE_FACT_CACHE_TIMEOUT'] = str(settings.ANSIBLE_FACT_CACHE_TIMEOUT) env['ANSIBLE_CACHE_PLUGIN_CONNECTION'] = settings.CACHES['default']['LOCATION'] if 'LOCATION' in settings.CACHES['default'] else '' if job.project: diff --git a/awx/plugins/fact_caching/tower.py b/awx/plugins/fact_caching/awx.py similarity index 100% rename from awx/plugins/fact_caching/tower.py rename to awx/plugins/fact_caching/awx.py From 9fe6453b1c69bde47848e82aee8c35424f992de4 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 12:25:36 -0400 Subject: [PATCH 082/342] Refactor Notification backend for tower -> awx --- awx/main/notifications/base.py | 2 +- awx/main/notifications/hipchat_backend.py | 4 ++-- awx/main/notifications/irc_backend.py | 4 ++-- awx/main/notifications/pagerduty_backend.py | 4 ++-- awx/main/notifications/slack_backend.py | 4 ++-- awx/main/notifications/twilio_backend.py | 4 ++-- awx/main/notifications/webhook_backend.py | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/awx/main/notifications/base.py b/awx/main/notifications/base.py index 7d6ed13910..bb5ac7a9ca 100644 --- a/awx/main/notifications/base.py +++ b/awx/main/notifications/base.py @@ -8,7 +8,7 @@ from django.core.mail.backends.base import BaseEmailBackend from django.utils.translation import ugettext_lazy as _ -class TowerBaseEmailBackend(BaseEmailBackend): +class AWXBaseEmailBackend(BaseEmailBackend): def format_body(self, body): if "body" in body: diff --git a/awx/main/notifications/hipchat_backend.py b/awx/main/notifications/hipchat_backend.py index b286439954..e75fcbc296 100644 --- a/awx/main/notifications/hipchat_backend.py +++ b/awx/main/notifications/hipchat_backend.py @@ -7,12 +7,12 @@ import requests from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend logger = logging.getLogger('awx.main.notifications.hipchat_backend') -class HipChatBackend(TowerBaseEmailBackend): +class HipChatBackend(AWXBaseEmailBackend): init_parameters = {"token": {"label": "Token", "type": "password"}, "rooms": {"label": "Destination Rooms", "type": "list"}, diff --git a/awx/main/notifications/irc_backend.py b/awx/main/notifications/irc_backend.py index 277364cf07..2493451e77 100644 --- a/awx/main/notifications/irc_backend.py +++ b/awx/main/notifications/irc_backend.py @@ -9,12 +9,12 @@ import irc.client from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend logger = logging.getLogger('awx.main.notifications.irc_backend') -class IrcBackend(TowerBaseEmailBackend): +class IrcBackend(AWXBaseEmailBackend): init_parameters = {"server": {"label": "IRC Server Address", "type": "string"}, "port": {"label": "IRC Server Port", "type": "int"}, diff --git a/awx/main/notifications/pagerduty_backend.py b/awx/main/notifications/pagerduty_backend.py index 76322c18cf..21eb2a9aae 100644 --- a/awx/main/notifications/pagerduty_backend.py +++ b/awx/main/notifications/pagerduty_backend.py @@ -6,12 +6,12 @@ import pygerduty from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend logger = logging.getLogger('awx.main.notifications.pagerduty_backend') -class PagerDutyBackend(TowerBaseEmailBackend): +class PagerDutyBackend(AWXBaseEmailBackend): init_parameters = {"subdomain": {"label": "Pagerduty subdomain", "type": "string"}, "token": {"label": "API Token", "type": "password"}, diff --git a/awx/main/notifications/slack_backend.py b/awx/main/notifications/slack_backend.py index 2da5c5d8a3..3cea4bd44e 100644 --- a/awx/main/notifications/slack_backend.py +++ b/awx/main/notifications/slack_backend.py @@ -6,12 +6,12 @@ from slackclient import SlackClient from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend logger = logging.getLogger('awx.main.notifications.slack_backend') -class SlackBackend(TowerBaseEmailBackend): +class SlackBackend(AWXBaseEmailBackend): init_parameters = {"token": {"label": "Token", "type": "password"}, "channels": {"label": "Destination Channels", "type": "list"}} diff --git a/awx/main/notifications/twilio_backend.py b/awx/main/notifications/twilio_backend.py index 8bdcc922b1..077ef7573b 100644 --- a/awx/main/notifications/twilio_backend.py +++ b/awx/main/notifications/twilio_backend.py @@ -7,12 +7,12 @@ from twilio.rest import Client from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend logger = logging.getLogger('awx.main.notifications.twilio_backend') -class TwilioBackend(TowerBaseEmailBackend): +class TwilioBackend(AWXBaseEmailBackend): init_parameters = {"account_sid": {"label": "Account SID", "type": "string"}, "account_token": {"label": "Account Token", "type": "password"}, diff --git a/awx/main/notifications/webhook_backend.py b/awx/main/notifications/webhook_backend.py index 8489a90f7b..04135213ed 100644 --- a/awx/main/notifications/webhook_backend.py +++ b/awx/main/notifications/webhook_backend.py @@ -6,13 +6,13 @@ import requests from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ -from awx.main.notifications.base import TowerBaseEmailBackend +from awx.main.notifications.base import AWXBaseEmailBackend from awx.main.utils import get_awx_version logger = logging.getLogger('awx.main.notifications.webhook_backend') -class WebhookBackend(TowerBaseEmailBackend): +class WebhookBackend(AWXBaseEmailBackend): init_parameters = {"url": {"label": "Target URL", "type": "string"}, "headers": {"label": "HTTP Headers", "type": "object"}} From b3b4a515e26c960464449de81945e45b9b29556d Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 12:32:55 -0400 Subject: [PATCH 083/342] Refactor some tower periodic tasks to label as awx --- awx/main/isolated/isolated_manager.py | 2 +- awx/main/scheduler/__init__.py | 2 +- awx/main/tasks.py | 4 ++-- awx/main/tests/functional/test_tasks.py | 6 +++--- awx/main/tests/unit/settings/test_defaults.py | 2 +- awx/settings/defaults.py | 2 +- awx/settings/development.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/awx/main/isolated/isolated_manager.py b/awx/main/isolated/isolated_manager.py index 72d9f9c58e..1ac035ad4b 100644 --- a/awx/main/isolated/isolated_manager.py +++ b/awx/main/isolated/isolated_manager.py @@ -193,7 +193,7 @@ class IsolatedManager(object): isolated_ssh_path = None try: if getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None): - isolated_ssh_path = tempfile.mkdtemp(prefix='ansible_awx_isolated', dir=settings.AWX_PROOT_BASE_PATH) + isolated_ssh_path = tempfile.mkdtemp(prefix='awx_isolated', dir=settings.AWX_PROOT_BASE_PATH) os.chmod(isolated_ssh_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) isolated_key = os.path.join(isolated_ssh_path, '.isolated') ssh_sock = os.path.join(isolated_ssh_path, '.isolated_ssh_auth.sock') diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py index 3e83c759d8..fbbb8b0fb5 100644 --- a/awx/main/scheduler/__init__.py +++ b/awx/main/scheduler/__init__.py @@ -161,7 +161,7 @@ class TaskManager(): job.save(update_fields=['status', 'job_explanation']) connection.on_commit(lambda: job.websocket_emit_status('failed')) - # TODO: should we emit a status on the socket here similar to tasks.py tower_periodic_scheduler() ? + # TODO: should we emit a status on the socket here similar to tasks.py awx_periodic_scheduler() ? #emit_websocket_notification('/socket.io/jobs', '', dict(id=)) # See comment in tasks.py::RunWorkflowJob::run() diff --git a/awx/main/tasks.py b/awx/main/tasks.py index c8def0fc59..0608f1c5bd 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -213,7 +213,7 @@ def cluster_node_heartbeat(self): @task(bind=True, base=LogErrorsTask) -def tower_isolated_heartbeat(self): +def awx_isolated_heartbeat(self): local_hostname = settings.CLUSTER_HOST_ID logger.debug("Controlling node checking for any isolated management tasks.") poll_interval = settings.AWX_ISOLATED_PERIODIC_CHECK @@ -237,7 +237,7 @@ def tower_isolated_heartbeat(self): @task(bind=True, queue='tower', base=LogErrorsTask) -def tower_periodic_scheduler(self): +def awx_periodic_scheduler(self): run_now = now() state = TowerScheduleState.get_solo() last_run = state.schedule_last_run diff --git a/awx/main/tests/functional/test_tasks.py b/awx/main/tests/functional/test_tasks.py index 065d979819..24451abbdd 100644 --- a/awx/main/tests/functional/test_tasks.py +++ b/awx/main/tests/functional/test_tasks.py @@ -6,7 +6,7 @@ from django.utils.timezone import now, timedelta from awx.main.tasks import ( RunProjectUpdate, RunInventoryUpdate, - tower_isolated_heartbeat, + awx_isolated_heartbeat, isolated_manager ) from awx.main.models import ( @@ -121,7 +121,7 @@ class TestIsolatedManagementTask: original_isolated_instance = needs_updating.instances.all().first() with mock.patch('awx.main.tasks.settings', MockSettings()): with mock.patch.object(isolated_manager.IsolatedManager, 'health_check') as check_mock: - tower_isolated_heartbeat() + awx_isolated_heartbeat() iso_instance = Instance.objects.get(hostname='isolated') call_args, _ = check_mock.call_args assert call_args[0][0] == iso_instance @@ -131,7 +131,7 @@ class TestIsolatedManagementTask: def test_does_not_take_action(self, control_instance, just_updated): with mock.patch('awx.main.tasks.settings', MockSettings()): with mock.patch.object(isolated_manager.IsolatedManager, 'health_check') as check_mock: - tower_isolated_heartbeat() + awx_isolated_heartbeat() iso_instance = Instance.objects.get(hostname='isolated') check_mock.assert_not_called() assert iso_instance.capacity == 103 diff --git a/awx/main/tests/unit/settings/test_defaults.py b/awx/main/tests/unit/settings/test_defaults.py index 894289002d..52eeb56425 100644 --- a/awx/main/tests/unit/settings/test_defaults.py +++ b/awx/main/tests/unit/settings/test_defaults.py @@ -6,7 +6,7 @@ from datetime import timedelta @pytest.mark.parametrize("job_name,function_path", [ ('admin_checks', 'awx.main.tasks.run_administrative_checks'), - ('tower_scheduler', 'awx.main.tasks.tower_periodic_scheduler'), + ('tower_scheduler', 'awx.main.tasks.awx_periodic_scheduler'), ]) def test_CELERYBEAT_SCHEDULE(mocker, job_name, function_path): assert job_name in settings.CELERYBEAT_SCHEDULE diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index d076e234ea..b8dfc97f28 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -445,7 +445,7 @@ CELERY_ROUTES = {'awx.main.scheduler.tasks.run_task_manager': {'queue': 'tower', CELERYBEAT_SCHEDULE = { 'tower_scheduler': { - 'task': 'awx.main.tasks.tower_periodic_scheduler', + 'task': 'awx.main.tasks.awx_periodic_scheduler', 'schedule': timedelta(seconds=30), 'options': {'expires': 20,} }, diff --git a/awx/settings/development.py b/awx/settings/development.py index 1dc7bb8688..cb6834ee2d 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -126,7 +126,7 @@ CELERY_ROUTES['awx.main.tasks.cluster_node_heartbeat'] = {'queue': CLUSTER_HOST_ # Production only runs this schedule on controlling nodes # but development will just run it on all nodes CELERYBEAT_SCHEDULE['isolated_heartbeat'] = { - 'task': 'awx.main.tasks.tower_isolated_heartbeat', + 'task': 'awx.main.tasks.awx_isolated_heartbeat', 'schedule': timedelta(seconds = AWX_ISOLATED_PERIODIC_CHECK), 'options': {'expires': AWX_ISOLATED_PERIODIC_CHECK * 2,} } From 01403f5fa4bd50df623b88bff3c8a729df4b8c71 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 12:37:43 -0400 Subject: [PATCH 084/342] Add awx_ and AWX_ environment vars and extra_vars alongside Tower --- awx/main/models/credential.py | 2 +- awx/main/tasks.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/awx/main/models/credential.py b/awx/main/models/credential.py index 53d5cfccb7..2c337bd5f0 100644 --- a/awx/main/models/credential.py +++ b/awx/main/models/credential.py @@ -384,7 +384,7 @@ class CredentialType(CommonModelNameNotUnique): 'VIRTUAL_ENV', 'PATH', 'PYTHONPATH', 'PROOT_TMP_DIR', 'JOB_ID', 'INVENTORY_ID', 'INVENTORY_SOURCE_ID', 'INVENTORY_UPDATE_ID', 'AD_HOC_COMMAND_ID', 'REST_API_URL', 'REST_API_TOKEN', 'TOWER_HOST', - 'MAX_EVENT_RES', 'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE', + 'AWX_HOST', 'MAX_EVENT_RES', 'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE', 'JOB_CALLBACK_DEBUG', 'INVENTORY_HOSTVARS', 'FACT_QUEUE', )) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 0608f1c5bd..e14a5ec0b2 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -953,6 +953,7 @@ class RunJob(BaseTask): env['REST_API_URL'] = settings.INTERNAL_API_URL env['REST_API_TOKEN'] = job.task_auth_token or '' env['TOWER_HOST'] = settings.TOWER_URL_BASE + env['AWX_HOST'] = settings.TOWER_URL_BASE env['CALLBACK_QUEUE'] = settings.CALLBACK_QUEUE env['CALLBACK_CONNECTION'] = settings.BROKER_URL env['CACHE'] = settings.CACHES['default']['LOCATION'] if 'LOCATION' in settings.CACHES['default'] else '' @@ -1077,20 +1078,27 @@ class RunJob(BaseTask): extra_vars = { 'tower_job_id': job.pk, 'tower_job_launch_type': job.launch_type, + 'awx_job_id': job.pk, + 'awx_job_launch_type': job.launch_type, } if job.project: extra_vars.update({ 'tower_project_revision': job.project.scm_revision, + 'awx_project_revision': job.project.scm_revision, }) if job.job_template: extra_vars.update({ 'tower_job_template_id': job.job_template.pk, 'tower_job_template_name': job.job_template.name, + 'awx_job_template_id': job.job_template.pk, + 'awx_job_template_name': job.job_template.name, }) if job.created_by: extra_vars.update({ 'tower_user_id': job.created_by.pk, 'tower_user_name': job.created_by.username, + 'awx_user_id': job.created_by.pk, + 'awx_user_name': job.created_by.username, }) if job.extra_vars_dict: if kwargs.get('display', False) and job.job_template: From eff62a17a4d3b458b99f1c0ec7a9f1c83e7a5881 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Wed, 26 Jul 2017 13:57:14 -0400 Subject: [PATCH 085/342] No longer require the token for channels --- awx/main/consumers.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/awx/main/consumers.py b/awx/main/consumers.py index ff55507939..39020099d1 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -1,16 +1,14 @@ import json import logging -import urllib from channels import Group, channel_layers -from channels.sessions import channel_session -from channels.handler import AsgiRequest +from channels.sessions import enforce_ordering, channel_session, channel_and_http_session from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder from django.contrib.auth.models import User -from awx.main.models.organization import AuthToken +from django.contrib.sessions.models import Session logger = logging.getLogger('awx.main.consumers') @@ -22,24 +20,21 @@ def discard_groups(message): Group(group).discard(message.reply_channel) -@channel_session +@channel_and_http_session def ws_connect(message): - connect_text = {'accept':False, 'user':None} + if message.http_session.session_key is None: + raise ValueError('No valid session key to get auth from') - message.content['method'] = 'FAKE' - request = AsgiRequest(message) - token = request.COOKIES.get('token', None) - if token is not None: - token = urllib.unquote(token).strip('"') - try: - auth_token = AuthToken.objects.get(key=token) - if auth_token.in_valid_tokens: - message.channel_session['user_id'] = auth_token.user_id - connect_text['accept'] = True - connect_text['user'] = auth_token.user_id - except AuthToken.DoesNotExist: - logger.error("auth_token provided was invalid.") - message.reply_channel.send({"text": json.dumps(connect_text)}) + session = Session.objects.get(session_key=message.http_session.session_key) + session_data = session.get_decoded() + + try: + user = User.objects.get(pk=session_data['_auth_user_id']) + except User.DoesNotExist: + raise ValueError('No valid user for the session key') + + message.channel_session['user_id'] = user.pk + message.reply_channel.send({"text": json.dumps({'accept': True, 'user': user.pk})}) @channel_session @@ -47,6 +42,7 @@ def ws_disconnect(message): discard_groups(message) +@enforce_ordering @channel_session def ws_receive(message): from awx.main.access import consumer_access From ea03e00a0fd14d958248b50760efe7e82fbf47e6 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 14:16:17 -0400 Subject: [PATCH 086/342] Add back in PRIVILEGE_ESCALATION_METHODS inadvertantly removed --- awx/main/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index e14a5ec0b2..c95ff9710c 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -44,7 +44,7 @@ from django.core.exceptions import ObjectDoesNotExist # AWX from awx import __version__ as awx_application_version -from awx.main.constants import CLOUD_PROVIDERS +from awx.main.constants import CLOUD_PROVIDERS, PRIVILEGE_ESCALATION_METHODS from awx.main.models import * # noqa from awx.main.models.unified_jobs import ACTIVE_STATES from awx.main.queue import CallbackQueueDispatcher From 7231537841a6f2ae08d460d25e426d63d719f119 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 26 Jul 2017 14:18:19 -0400 Subject: [PATCH 087/342] Remove ansible reference from dev PKG-INFO --- tools/docker-compose/awx.egg-info/PKG-INFO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose/awx.egg-info/PKG-INFO b/tools/docker-compose/awx.egg-info/PKG-INFO index d02ec5de21..3aded406c9 100644 --- a/tools/docker-compose/awx.egg-info/PKG-INFO +++ b/tools/docker-compose/awx.egg-info/PKG-INFO @@ -6,7 +6,7 @@ Home-page: http://github.com/ansible/awx Author: Ansible, Inc. Author-email: info@ansible.com License: Proprietary -Description: Ansible AWX provides a web-based user interface, REST API and task engine built on top of Ansible +Description: AWX provides a web-based user interface, REST API and task engine built on top of Ansible Keywords: ansible Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable From 2c2e5cadbf1f4cd2bcefe67d819731fa0ff88e01 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 26 Jul 2017 12:16:41 -0400 Subject: [PATCH 088/342] don't require a `credential` for job launch if vault is specified see: https://github.com/ansible/ansible-tower/issues/7310 --- awx/main/models/jobs.py | 2 -- .../functional/api/test_job_runtime_params.py | 31 +++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 1dee17bd56..2251332257 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -318,8 +318,6 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour resources_needed_to_start.append('credential') if not self.ask_credential_on_launch: validation_errors['credential'] = [_("Job Template must provide 'credential' or allow prompting for it."),] - elif self.credential is None and self.ask_credential_on_launch: - resources_needed_to_start.append('credential') # Job type dependent checks if self.project is None: diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py index 4d7f1e3ecd..b197ac2520 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -317,9 +317,11 @@ def test_job_launch_JT_enforces_unique_extra_credential_kinds(machine_credential @pytest.mark.django_db -def test_job_launch_with_no_credentials(deploy_jobtemplate): +@pytest.mark.parametrize('ask_credential_on_launch', [True, False]) +def test_job_launch_with_no_credentials(deploy_jobtemplate, ask_credential_on_launch): deploy_jobtemplate.credential = None deploy_jobtemplate.vault_credential = None + deploy_jobtemplate.ask_credential_on_launch = ask_credential_on_launch serializer = JobLaunchSerializer( instance=deploy_jobtemplate, data={}, context={'obj': deploy_jobtemplate, 'data': {}, 'passwords': {}}) @@ -353,8 +355,31 @@ def test_job_launch_with_vault_credential_ask_for_machine(vault_credential, depl instance=deploy_jobtemplate, data={}, context={'obj': deploy_jobtemplate, 'data': {}, 'passwords': {}}) validated = serializer.is_valid() - assert validated is False - assert serializer.errors['credential'] == ["Job Template 'credential' is missing or undefined."] + assert validated + + prompted_fields, ignored_fields = deploy_jobtemplate._accept_or_ignore_job_kwargs(**{}) + job_obj = deploy_jobtemplate.create_unified_job(**prompted_fields) + assert job_obj.credential is None + assert job_obj.vault_credential.pk == vault_credential.pk + + +@pytest.mark.django_db +def test_job_launch_with_vault_credential_and_prompted_machine_cred(machine_credential, vault_credential, + deploy_jobtemplate): + deploy_jobtemplate.credential = None + deploy_jobtemplate.ask_credential_on_launch = True + deploy_jobtemplate.vault_credential = vault_credential + kv = dict(credential=machine_credential.id) + serializer = JobLaunchSerializer( + instance=deploy_jobtemplate, data=kv, + context={'obj': deploy_jobtemplate, 'data': kv, 'passwords': {}}) + validated = serializer.is_valid() + assert validated + + prompted_fields, ignored_fields = deploy_jobtemplate._accept_or_ignore_job_kwargs(**kv) + job_obj = deploy_jobtemplate.create_unified_job(**prompted_fields) + assert job_obj.credential.pk == machine_credential.pk + assert job_obj.vault_credential.pk == vault_credential.pk @pytest.mark.django_db From c71e8d38b791f18e19d39bee688203bf524cfa97 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 26 Jul 2017 15:01:31 -0400 Subject: [PATCH 089/342] improve private passphrase error when it's provided unnecessarily see: https://github.com/ansible/ansible-tower/issues/7293 --- awx/main/fields.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/awx/main/fields.py b/awx/main/fields.py index 7e2c3edfaf..2ba41b7841 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -542,8 +542,11 @@ class CredentialInputField(JSONSchemaField): if model_instance.has_encrypted_ssh_key_data and not value.get('ssh_key_unlock'): errors['ssh_key_unlock'] = [_('must be set when SSH key is encrypted.')] - if not model_instance.has_encrypted_ssh_key_data and value.get('ssh_key_unlock'): - errors['ssh_key_unlock'] = [_('should not be set when SSH key is not encrypted.')] + if value.get('ssh_key_unlock'): + if not model_instance.ssh_key_data: + errors['ssh_key_unlock'] = [_('should not be set when SSH key is empty.')] + elif not model_instance.has_encrypted_ssh_key_data: + errors['ssh_key_unlock'] = [_('should not be set when SSH key is not encrypted.')] if errors: raise serializers.ValidationError({ From 7867a0b692e0a3d88cc2dab7ec1be858f6be15fd Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 26 Jul 2017 14:59:12 -0400 Subject: [PATCH 090/342] Fix privilege escalation password label on launch --- .../src/credentials/credentials.form.js | 4 +- .../factories/become-method-change.factory.js | 2 - .../factories/kind-change.factory.js | 2 - .../check-passwords.factory.js | 39 +++++++++++-------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/awx/ui/client/src/credentials/credentials.form.js b/awx/ui/client/src/credentials/credentials.form.js index f8c0440725..b6b3e7f905 100644 --- a/awx/ui/client/src/credentials/credentials.form.js +++ b/awx/ui/client/src/credentials/credentials.form.js @@ -292,7 +292,7 @@ export default ['i18n', function(i18n) { ngChange: 'becomeMethodChange()', }, "become_username": { - labelBind: 'becomeUsernameLabel', + label: i18n._('Privilege Escalation Username'), type: 'text', ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", @@ -302,7 +302,7 @@ export default ['i18n', function(i18n) { ngDisabled: '!(credential_obj.summary_fields.user_capabilities.edit || canAdd)' }, "become_password": { - labelBind: 'becomePasswordLabel', + label: i18n._('Privilege Escalation Password'), type: 'sensitive', ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ", ngDisabled: "become_password_ask || !(credential_obj.summary_fields.user_capabilities.edit || canAdd)", diff --git a/awx/ui/client/src/credentials/factories/become-method-change.factory.js b/awx/ui/client/src/credentials/factories/become-method-change.factory.js index 0727553528..2d85acaf35 100644 --- a/awx/ui/client/src/credentials/factories/become-method-change.factory.js +++ b/awx/ui/client/src/credentials/factories/become-method-change.factory.js @@ -15,8 +15,6 @@ export default break; case 'ssh': scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); - scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); break; case 'scm': scope.sshKeyDataLabel = i18n._('SCM Private Key'); diff --git a/awx/ui/client/src/credentials/factories/kind-change.factory.js b/awx/ui/client/src/credentials/factories/kind-change.factory.js index e35bedc526..7232d8a188 100644 --- a/awx/ui/client/src/credentials/factories/kind-change.factory.js +++ b/awx/ui/client/src/credentials/factories/kind-change.factory.js @@ -72,8 +72,6 @@ export default break; case 'ssh': scope.usernameLabel = i18n._('Username'); //formally 'SSH Username' - scope.becomeUsernameLabel = i18n._('Privilege Escalation Username'); - scope.becomePasswordLabel = i18n._('Privilege Escalation Password'); break; case 'scm': scope.sshKeyDataLabel = i18n._('SCM Private Key'); diff --git a/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js index 4f6089f7e2..06d6566168 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/check-passwords.factory.js @@ -1,5 +1,5 @@ export default - function CheckPasswords(Rest, GetBasePath, ProcessErrors, Empty) { + function CheckPasswords(Rest, GetBasePath, ProcessErrors, Empty, credentialTypesLookup) { return function(params) { var scope = params.scope, callback = params.callback, @@ -10,21 +10,25 @@ export default Rest.setUrl(GetBasePath('credentials')+credential); Rest.get() .success(function (data) { - if(data.kind === "ssh"){ - if(data.password === "ASK" ){ - passwords.push("ssh_password"); - } - if(data.ssh_key_unlock === "ASK"){ - passwords.push("ssh_key_unlock"); - } - if(data.become_password === "ASK"){ - passwords.push("become_password"); - } - if(data.vault_password === "ASK"){ - passwords.push("vault_password"); - } - } - scope.$emit(callback, passwords); + credentialTypesLookup() + .then(kinds => { + if(data.credential_type === kinds.Machine && data.inputs){console.log(data.inputs); + if(data.inputs.password === "ASK" ){ + passwords.push("ssh_password"); + } + if(data.inputs.ssh_key_unlock === "ASK"){ + passwords.push("ssh_key_unlock"); + } + if(data.inputs.become_password === "ASK"){ + passwords.push("become_password"); + } + if(data.inputs.vault_password === "ASK"){ + passwords.push("vault_password"); + } + } + scope.$emit(callback, passwords); + }); + }) .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', @@ -39,5 +43,6 @@ CheckPasswords.$inject = [ 'Rest', 'GetBasePath', 'ProcessErrors', - 'Empty' + 'Empty', + 'credentialTypesLookup' ]; From e6311f285382bfc32a08093cc980b9a7f6e06d0c Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 26 Jul 2017 16:10:18 -0400 Subject: [PATCH 091/342] Fix Permissions Modal UX items --- awx/ui/client/legacy-styles/forms.less | 3 ++- .../add-rbac-resource/rbac-resource.partial.html | 2 +- awx/ui/client/src/access/add-rbac.block.less | 12 +----------- .../linkout/addUsers/addUsers.block.less | 3 +-- .../src/shared/smart-search/smart-search.block.less | 2 +- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index fdde7e18a4..37dad2aede 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -61,7 +61,8 @@ .Form-title--is_superuser, .Form-title--is_system_auditor, .Form-title--is_ldap_user, -.Form-title--is_external_account { +.Form-title--is_external_account, +.Form-title--roleType { height:15px; color: @default-interface-txt; background-color: @default-list-header-bg; diff --git a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html index cc2c3afbba..e364b3110a 100644 --- a/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html +++ b/awx/ui/client/src/access/add-rbac-resource/rbac-resource.partial.html @@ -86,7 +86,7 @@ {{ obj.name }} - + {{ obj.type }}
diff --git a/awx/ui/client/src/access/add-rbac.block.less b/awx/ui/client/src/access/add-rbac.block.less index 98d3e44eb4..eca34cfdc4 100644 --- a/awx/ui/client/src/access/add-rbac.block.less +++ b/awx/ui/client/src/access/add-rbac.block.less @@ -129,15 +129,6 @@ overflow: hidden; } -.AddPermissions-roleType { - padding: 0px 6px; - font-size: 10px; - color: @default-interface-txt; - text-transform: uppercase; - background-color: @default-bg; - margin-left: 6px; -} - .AddPermissions-roleSelect { width: ~"calc(70% - 40px)"; margin-right: 20px; @@ -201,8 +192,7 @@ margin: 20px 0; font-size: 12px; width: 100%; - padding: 15px; - padding-top: 10px; + padding: 20px; margin-bottom: 15px; border-radius: 4px; border: 1px solid @login-notice-border; diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less index 3ebf847364..ca509b190f 100644 --- a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.block.less @@ -187,8 +187,7 @@ margin: 20px 0; font-size: 12px; width: 100%; - padding: 15px; - padding-top: 10px; + padding: 20px; margin-bottom: 15px; border-radius: 4px; border: 1px solid @login-notice-border; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.block.less b/awx/ui/client/src/shared/smart-search/smart-search.block.less index 7552dd01cb..2c2a317a92 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.block.less +++ b/awx/ui/client/src/shared/smart-search/smart-search.block.less @@ -191,7 +191,7 @@ margin: 10px 0 0 0; font-size: 12px; width: 100%; - padding: 15px; + padding: 20px; border-radius: 4px; border: 1px solid @d7grey; background-color: @login-notice-bg; From cb85038976db5a2f42320f7710da08e3abee1a64 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Wed, 26 Jul 2017 16:34:08 -0400 Subject: [PATCH 092/342] filter credential_type__search from related search fields in API v1 see: https://github.com/ansible/ansible-tower/issues/6116 --- awx/api/views.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/awx/api/views.py b/awx/api/views.py index e783b94d0b..ff80ad3264 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1614,7 +1614,18 @@ class CredentialTypeActivityStreamList(ActivityStreamEnforcementMixin, SubListAP new_in_api_v2 = True -class CredentialList(ListCreateAPIView): +# remove in 3.3 +class CredentialViewMixin(object): + + @property + def related_search_fields(self): + ret = super(CredentialViewMixin, self).related_search_fields + if get_request_version(self.request) == 1 and 'credential_type__search' in ret: + ret.remove('credential_type__search') + return ret + + +class CredentialList(CredentialViewMixin, ListCreateAPIView): model = Credential serializer_class = CredentialSerializerCreate @@ -1649,7 +1660,7 @@ class CredentialOwnerTeamsList(SubListAPIView): return self.model.objects.filter(pk__in=teams) -class UserCredentialsList(SubListCreateAPIView): +class UserCredentialsList(CredentialViewMixin, SubListCreateAPIView): model = Credential serializer_class = UserCredentialSerializerCreate @@ -1666,7 +1677,7 @@ class UserCredentialsList(SubListCreateAPIView): return user_creds & visible_creds -class TeamCredentialsList(SubListCreateAPIView): +class TeamCredentialsList(CredentialViewMixin, SubListCreateAPIView): model = Credential serializer_class = TeamCredentialSerializerCreate @@ -1683,7 +1694,7 @@ class TeamCredentialsList(SubListCreateAPIView): return (team_creds & visible_creds).distinct() -class OrganizationCredentialList(SubListCreateAPIView): +class OrganizationCredentialList(CredentialViewMixin, SubListCreateAPIView): model = Credential serializer_class = OrganizationCredentialSerializerCreate From d1e7e8d237e381ab3c5f476c2330c2594ddc88e8 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 26 Jul 2017 16:48:53 -0400 Subject: [PATCH 093/342] Maintain selected host/group rows on pagination and search changes --- .../related/groups/list/groups-list.controller.js | 10 ++++++++-- .../group-nested-groups-list.controller.js | 10 ++++++++-- .../nested-hosts/group-nested-hosts-list.controller.js | 5 +++++ .../related/hosts/list/host-list.controller.js | 5 +++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js index 78a0969009..cddd4b276b 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js @@ -33,7 +33,7 @@ $scope.inventory_id = $stateParams.inventory_id; $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], buildStatusIndicators); + _.forEach($scope[list.name], processRow); }); $scope.$on('selectedOrDeselected', function(e, value) { @@ -54,11 +54,17 @@ } - function buildStatusIndicators(group){ + function processRow(group){ if (group === undefined || group === null) { group = {}; } + angular.forEach($scope.groupsSelected, function(selectedGroup){ + if(selectedGroup.id === group.id) { + group.isSelected = true; + } + }); + let hosts_status; hosts_status = GetHostsStatusMsg({ diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js index d5e5c39ffb..0b645ba12d 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js @@ -34,7 +34,7 @@ $scope.inventory_id = $stateParams.inventory_id; $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], buildStatusIndicators); + _.forEach($scope[list.name], processRow); }); $scope.$on('selectedOrDeselected', function(e, value) { @@ -55,11 +55,17 @@ } - function buildStatusIndicators(group){ + function processRow(group){ if (group === undefined || group === null) { group = {}; } + angular.forEach($scope.groupsSelected, function(selectedGroup){ + if(selectedGroup.id === group.id) { + group.isSelected = true; + } + }); + let hosts_status; hosts_status = GetHostsStatusMsg({ diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js index a4e449f88b..a6adaad5a2 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js @@ -34,6 +34,11 @@ export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePat value.can_disassociate = true; } }); + angular.forEach($scope.hostsSelected, function(selectedHost){ + if(selectedHost.id === value.id) { + value.isSelected = true; + } + }); return value; }); setJobStatus(); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js index d1dfbbaac1..7f51502a20 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js @@ -31,6 +31,11 @@ export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath', $scope[list.name] = _.map($scope.hosts, function(value) { value.inventory_name = value.summary_fields.inventory.name; value.inventory_id = value.summary_fields.inventory.id; + angular.forEach($scope.hostsSelected, function(selectedHost){ + if(selectedHost.id === value.id) { + value.isSelected = true; + } + }); return value; }); setJobStatus(); From d5026e891ed164bbe685ddcfaede1165eb1aed98 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Wed, 26 Jul 2017 17:15:17 -0400 Subject: [PATCH 094/342] Prevent draggable selection on CodeMirror --- .../src/job-results/host-event/host-event.controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/job-results/host-event/host-event.controller.js b/awx/ui/client/src/job-results/host-event/host-event.controller.js index e09e33740a..c54a14844a 100644 --- a/awx/ui/client/src/job-results/host-event/host-event.controller.js +++ b/awx/ui/client/src/job-results/host-event/host-event.controller.js @@ -104,7 +104,9 @@ minHeight: 450, minWidth: 600 }); - $('.modal-dialog').draggable(); + $('.modal-dialog').draggable({ + cancel: '.CodeMirror' + }); $('#HostEvent').on('hidden.bs.modal', function () { $scope.closeHostEvent(); From 98cf28d9f1cfc51720b25c03caf4ce9f939aaf6b Mon Sep 17 00:00:00 2001 From: gconsidine Date: Wed, 26 Jul 2017 17:49:05 -0400 Subject: [PATCH 095/342] Update model interaction --- .../credentials/add-credentials.controller.js | 5 +- .../edit-credentials.controller.js | 11 ++-- awx/ui/client/features/credentials/index.js | 10 +--- awx/ui/client/lib/models/Base.js | 52 +++++++++++++++++-- awx/ui/client/lib/models/Credential.js | 6 +-- awx/ui/client/lib/models/CredentialType.js | 29 ++++------- awx/ui/client/lib/models/Me.js | 6 +-- awx/ui/client/lib/models/Organization.js | 7 +-- .../list/credentials-list.controller.js | 2 +- 9 files changed, 82 insertions(+), 46 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 4e829f37ee..79fa9afd0c 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -31,9 +31,10 @@ function AddCredentialsController (models, $state, strings) { vm.form.inputs = { _get: id => { - let type = credentialType.getById(id); + let type = credentialType.graft(id); + type.mergeInputProperties(); - return credentialType.mergeInputProperties(type); + return type.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 19b05a834b..e49dfe51ce 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -56,14 +56,15 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.inputs = { _get (id) { - let type = credentialType.getById(id); - let inputs = credentialType.mergeInputProperties(type); + let type = credentialType.graft(id); + + type.mergeInputProperties(); - if (type.id === credential.get('credential_type')) { - inputs = credential.assignInputGroupValues(inputs); + if (type.get('id') === credential.get('credential_type')) { + return credential.assignInputGroupValues(type.get('inputs.fields')); } - return inputs; + return type.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index c939c98d5d..077e9562e1 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -5,7 +5,6 @@ import CredentialsStrings from './credentials.strings' function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) { let id = $stateParams.credential_id; - let models; let promises = { me: new Me('get'), @@ -22,14 +21,9 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O promises.credential = new Credential(['get', 'options'], [id, id]); return $q.all(promises) - .then(_models_ => { - models = _models_; + .then(models => { let credentialTypeId = models.credential.get('credential_type'); - - return models.credentialType.graft(credentialTypeId); - }) - .then(selectedCredentialType => { - models.selectedCredentialType = selectedCredentialType; + models.selectedCredentialType = models.credentialType.graft(credentialTypeId); return models; }); diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index d1c8fd2936..0a4cef54fd 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -87,7 +87,34 @@ function get (keys) { return this.find('get', keys); } +function set (method, keys, value) { + if (!value) { + value = keys; + keys = method; + method = 'GET'; + } + + keys = keys.split('.'); + + if (keys.length === 1) { + model[keys[0]] = value; + } else { + let property = keys.splice(-1); + keys = keys.join('.'); + + let model = this.find(method, keys) + + model[property] = value; + } +} + function match (method, key, value) { + if(!value) { + value = key; + key = method; + method = 'GET'; + } + let model = this.model[method.toUpperCase()]; if (!model) { @@ -149,20 +176,39 @@ function normalizePath (resource) { return `${version}${resource}/`; } -function getById (id) { +function graft (id) { let item = this.get('results').filter(result => result.id === id); - return item ? item[0] : undefined; + item = item ? item[0] : undefined; + + if (!item) { + return undefined; + } + + return new this.Constructor('get', item, true); +} + +function create (method, resource, graft) { + this.promise = this.request(method, resource); + + if (graft) { + return this; + } + + return this.promise + .then(() => this); } function BaseModel (path) { this.model = {}; this.get = get; + this.set = set; this.options = options; this.find = find; this.match = match; this.normalizePath = normalizePath; - this.getById = getById; + this.graft = graft; + this.create = create; this.request = request; this.http = { get: httpGet.bind(this), diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 19354bf8f0..9937ebe281 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -37,15 +37,15 @@ function clearTypeInputs () { delete this.model.GET.inputs; } -function CredentialModel (method, resource) { +function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); + this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); this.clearTypeInputs = clearTypeInputs.bind(this); - return this.request(method, resource) - .then(() => this); + return this.create(method, resource, graft); } function CredentialModelLoader (_BaseModel_ ) { diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index f0ab0d6f0f..e132c05ebb 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -14,33 +14,26 @@ function categorizeByKind () { })); } -function mergeInputProperties (type) { - return type.inputs.fields.map(field => { - if (!type.inputs.required || type.inputs.required.indexOf(field.id) === -1) { - field.required = false; - } else { - field.required = true; - } +function mergeInputProperties () { + let required = this.get('inputs.required'); - return field; + return this.get('inputs.fields').map((field, i) => { + if (!required || required.indexOf(field.id) === -1) { + this.set(`inputs.fields[${i}].required`, false); + } else { + this.set(`inputs.fields[${i}].required`, true); + } }); } -function graft (id) { - let data = this.getById(id); - - return new CredentialTypeModel('get', data); -} - -function CredentialTypeModel (method, id) { +function CredentialTypeModel (method, resource, graft) { BaseModel.call(this, 'credential_types'); + this.Constructor = CredentialTypeModel; this.categorizeByKind = categorizeByKind.bind(this); this.mergeInputProperties = mergeInputProperties.bind(this); - this.graft = graft.bind(this); - return this.request(method, id) - .then(() => this); + return this.create(method, resource, graft); } function CredentialTypeModelLoader (_BaseModel_) { diff --git a/awx/ui/client/lib/models/Me.js b/awx/ui/client/lib/models/Me.js index 2a36a5b2be..9393132e8e 100644 --- a/awx/ui/client/lib/models/Me.js +++ b/awx/ui/client/lib/models/Me.js @@ -4,13 +4,13 @@ function getSelf () { return this.get('results[0]'); } -function MeModel (method) { +function MeModel (method, resource, graft) { BaseModel.call(this, 'me'); + this.Constructor = MeModel; this.getSelf = getSelf.bind(this); - return this.request(method) - .then(() => this); + return this.create(method, resource, graft); } function MeModelLoader (_BaseModel_) { diff --git a/awx/ui/client/lib/models/Organization.js b/awx/ui/client/lib/models/Organization.js index 7dc7758f78..53448cd80f 100644 --- a/awx/ui/client/lib/models/Organization.js +++ b/awx/ui/client/lib/models/Organization.js @@ -1,10 +1,11 @@ let BaseModel; -function OrganizationModel (method) { +function OrganizationModel (method, resource, graft) { BaseModel.call(this, 'organizations'); - return this.request(method) - .then(() => this); + this.Constructor = OrganizationModel; + + return this.create(method, resource, graft); } function OrganizationModelLoader (_BaseModel_) { diff --git a/awx/ui/client/src/credentials/list/credentials-list.controller.js b/awx/ui/client/src/credentials/list/credentials-list.controller.js index 30acfc38c5..924f58e367 100644 --- a/awx/ui/client/src/credentials/list/credentials-list.controller.js +++ b/awx/ui/client/src/credentials/list/credentials-list.controller.js @@ -46,7 +46,7 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', ' } $scope[list.name].forEach(credential => { - credential.kind = credentialType.getById(credential.credential_type).name; + credential.kind = credentialType.match('id', credential.credential_type).name; }); } From e82b6e932af44dbb64f7477d0c343e5407374498 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 26 Jul 2017 14:49:14 -0700 Subject: [PATCH 096/342] changing title icon to angry spud --- awx/ui/client/assets/favicon.ico | Bin 15086 -> 5430 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/awx/ui/client/assets/favicon.ico b/awx/ui/client/assets/favicon.ico index 550879805122719d52005b50d412f94106718180..2f009cfad00f5fc0aefa39bd9c552923247a8439 100644 GIT binary patch literal 5430 zcmds53sjBy7XLaOl~gCh9BxudlvR>+a|)HyLnZW*rjp*TF^L|85>g&{6~Z-G7Dd-H zCZW6smzi-dX0CDPHgt{QN|H)W{qFvM&B>9I$y#%*yY7Fjeg6B~-?zWt{_k)9_ul`* zaVngE)79l@HsoT39B0aLoS|X+bur2FNj98h#L^S(a+*G9^w8?>+)Gz%_d!Yu4~tBs zk5ZNtz{En5s37h(t1-}~Qg^rq;uddd5f8Tu(9rJNzq`r;p_-aQ$l~&_HD4lpT?Ibd zei=~(l`=`-yd6DyYBRUIcNp2Yg(q&igxF1I5mQjf>nCizi14*1;g!7kUTAc3>F|+~ zcupYHP}S_`F3`}m7U~QNCJIyS)!)k{CbvAA^kNFnAwK&CZrpu{lh=Pl+RjU0?=^Ez z#N?0^*iQ<*Xe$k@{A9@JEKblvlNqHyQW_t(`5Yq1ZhNmAa2WFp1?w8|*Kc2={$&&9 z@45tO{CZh0Bj-kqfnzEZ7a}&baSdC2yqRGznw?w?m+{YFqW=_g5?`QXZxgD%`6t}s z@+uYe7_X|Pq2agS(8K9zmw8?0v#)MHz=7gs%%9f)i=j_3&9e@(Q}01CJ$Y+qeetj{ zUa?6(V}9y0%vx4~s12u)y5l^aJZr#D)iQip_6mO9b(k1@4thowlFs_W9Qt`W!DB)PC>3yZo(sAGi<$M;hvC> zA=XZCnXv}JN#!*IjDy3~MPl-({BlIBuYmrD1RP?^)igzY)U-_lG&Ni$Dx5m)==Py&Bq_I+xMj(&KX?`hH3dR- z9l0LBaWY1kL?R+pj%%iyY~$AvU(HCJi0)NSctd!>aZH!x8ABpRB9@Q-UxrXi?5u0z ze8tSpd%votp7qCGm!`qEEBaPbaOT2wEX*xxQPVSv{FpXSe)*mVjMf$vBWL|CnNUaH z>torVAs$sM$7l1O%c?MGM)G47p+=vNVFRtzhvDj&x}EP5YG3+5ZiDdg&SHjq=N=56U?VR!bt+pX$iVEH}p#Rv{p}wBAe_noD;4W=;DL z8#Vh3>^~{F=x!qAUsgwa7Z<~4;aHVz+x@xEtmeCKXwxD1EQ z%m)EW4)FCr5u4p(zF{^5tvJ%CXKKHI@^d2XU3ZF;jvV(sEM@n6GfN&R;&jEc{=hG@ zgzpR3Gt-xppzO?5oUFQvtX10^4K1ZRs5dZFhS8O*x4{UTS=48A#*AUT$l4RwbmTAC zdGcH2mtIB8hKjdhg85_Qv^4ZH_dsCGB3!(76A9_7>&PFDYJG-A(w-`(Jd#e=9@IOO zQhcAMx$@V!jGjp!?VEC#Vb5HB0<4ZPK5%InEG1WQ@IWIJ2kS?^IDQ?`8_#su>rdZF z+r)*>8#*RZ%+1=!_!^?-$mA7ppxUh7-(nr*RMxh341t=KSUl1*Cv??u{#^lC`w_ab z3}I`^VQzVg?_riNc?lotK{}7?-`z(P)mvsO+ZW=|(|LTtl+X1P7XcB8>HEbdw#gl! z3Jk@(35#R7-&ssCenLMaQy$rI} zRwQn0|6ay+le69d=|Vu*eLUxF`;qmWA-PBS-baqleDHb3O+0zrilvz^JMiUx*t)q9 zlF_v&Dr}F7f`ec2>o>CXRNA8{GhsB_>x2_iZ~-BC$KJwcc9fjC$@`e$v;MRE#H)Ag zDB0VLf7QtFdG-~sbx>~hQ-;-9rXw~s2+lj!;y34Su($gKp*ig_Bws(9Q@2&3wY3dD z{GEI(Yr}*4G9G`D!uRm=ehxFEr?8(^iBEHn%2@vFjHT?_*xn~~M#k6tx=tK>1y|R9 zAY$#Cy~O+yPcgOk)HfY*_RFJI?AY3b*RR`PYgr4c;kE7l82ku6^N(Ue)XFktSjuGM z!>hs zx2pMAXEwC8wc*so`;bPK@%d_)Q?Sy#t!(W~=ACw+nyou2;RUuAHNo869EF92ybY%I zQ{SmKEFZFS4a>Vpn@=NpeK}^*8PjLcJ{UXrCv}C>mE3dgwy!02j}a45i-ik*#;mA( zOplriLu*$In-B=M_|+Ken}|vAd6+w^Ee-7EiX@gsv)swy_Ju)_!nH+(`SsJ%_SyJ~5Wg zaAQ0X5)uMmUth#UT|!#&3*4r>OZfX0-%tJ8RH|R8in8wTj!`SiL-USy)KOMv6X^WM z@O>BV!BD3NgodRcEoB!rZD>Sza6QhSZGpJ&?tI1fQ=K;V$J1K&DBQ@^7|F>ty&m~TBJ z5NhcTvCpxZmU4&oFaLiD>!~7W-&0Q|(;4b2`^2E2CM@_7GBX-5&h}Y@o@n_yXA!D# zwp8z`DF)b)u=_(G6fhn7c2g;D>QGL$ByuFOp=({Lp9e()!uFHDbNlF|h6_~(DeZNc z?^(SU4Rv`)zR>8t0l&|a6o2k!4l`QHPF<2wFVn63|N9`nd5db#-ljc!53+e9`QPFA zn(>N$S^HL(Ni+dy77?isPk$v;bD2b{L@I|JIZl+taaCEI3MC&=8^;ZnDNr-VO(4o7 fLNljMvwEhCQwLF18z&+fTm@O?U_$zWh#&s}oD}E_ literal 15086 zcmeHOd5m3C7{66JsUoC=mxV%ma%oHt+saVdqW>Z8_Ez;f+z`5 zgY*xb_uiDGLxuh^+D1bcQXNHW?`~?^d49ih&v)PV?%Zi7YNX-bYo++aY`Z`s*#9s|SC;DLx!heEu0jtD{e zH;q6VfiwbX1kwnk5lADDMj(tpKJZU&4LmNLdOU@|yKZ)F1XfqQKP4=+$k#k_hZYmTb*wg^ahpko8u^eJBQg6KExr zw|nz}wih$rRKpi|uWKJd+r6(q+f)eri<0ycN87=(9Hq0^;GV1bV8b=QZ$W%MkNZ$) z^hbcU9)&iRv=7Smke1*>h3(RY^+DM#@`*ku+l4RkfqiOhFSi}}z*vK}A+lX~&=2rq zQV%@n`Hc6x#3s>pX#>7Oo^cnO1XlRaeD{fafM;0(Ung{p2YyQrTGngN<(!>*)1ME# z`!F63g8b47*}(e|{dX+wkI8-vzU~U%vfXnM@D)Df1NY{*JoLZzUe4PS_+m`HtB`ds z(J=)5`!bLJiO}*5-q~e5$Cf)fq6O}=LMvvyTS!Aa!3X~e{C4jbUIjNvTZD+!(~8zT zYh2C2#KR9);Y%1#4aDmQbpCE^|GI5Lv%d}LcR;sJXwbJxWN1I}&~+1VF(3RZNW*x% zNB9L2FAw`0_)eh}Gwvg#p*~G;fWOYp9k;j02n7BgvES&+dM_(|@Hw=fw0y>WN7F>V zK==@T!@^kp+7b*q3jBYH494|?!mA$1mm(X&t;e5j#>PQ~{pf>Nb$i+d zzT-Q780cM!XPJ6mJG0^Ved98$gGbvBoidim@7@!mek*6&#Hhpj=(cJn=fPm_0a`|Wz+CuRRP>iv(_ zZ0(^*K7c22{I>?~1xg-q=-=NjVtkj@$8|Jtb0Zw)kG|S()}~yCR?G$cXMc0bx#Z5Enc|CzCFcmltkH135^|3T!?#w_X=Ie}`g@-_U4#oes?-(Xv2-JJDa zl=duKm(RKvwmje+k9v78#J;n8I?zXfwpq);?-|_FS-GDz4ev$T#=DREu4{t{>l6KGxe2KP+hqj95o7?Y$}f@ec~pPSGR z#s|)YV!QC<8;wrw!wUS?jvzfgexS?jn_KR$9fJ2o%Dyg+ZOU+O4t%uE40-&=NlO^Z z>$$Ect+z*wrRLuD?8=A8c4-fLjAyK!6?=Z=Lu7j-gFO{w%-WOvD0o8U!+Pc#G_ALH z{_c9SU1ah>?dcMC!3pHPJ4v2CVEmphd>W!3F5|aEbykkmF3;%^9$KBJfXD*L8eK zc*5A#$PyaZi$`{T9l3jKZT|4^oH@wfV;`vDjOxBi>@<7WpZ=Y8MM zy|fKc{QJRw+-thg_WdS|?fGWR#_kHl^)t^X<3;khs7JrQzQ*5t*TZiVzIXV>2L5@r z-L?A6+1>@&0KE(F8+kI)jqfk-XC$9lzP0w79erDPEr6aYBseQI7q={$X4Cp$)LB z$GmH7z&)D^-fhgo)`r()KA^0z0dswlZryhJo<<;zz<)miVSZ-of2XW@#mm(V8Zc$< zIo_1^dN0>rKgpIR)aRp2Vwv1tzkgeMeN9Jhrvd9e9$kZ0Cv3gtQi*vAF0QLkqCCaU zsH9It1k5~)`@Lo^#@tCr2CS7IvUA2L%!_vrXYL$hAM-&lY=6u()9lm4c zpU$}jwk!p{8E_%j3s#7S(K>${^6+h> zu@lg=C-RxvYJ=aFJk~e76G}h0SK-|&>}x)N>J_FZ;=HIsbM&ptQzqS?m( m^(mR0Df6abd3OW;7qu35Xr6JL$6o?u;X8g6OF Date: Wed, 26 Jul 2017 18:10:33 -0400 Subject: [PATCH 097/342] Enforce extra_vars override hierachy --- awx/main/models/mixins.py | 38 +++++++++++-------- awx/main/models/projects.py | 14 +++++-- .../unit/models/test_job_template_unit.py | 19 +++++++++- .../tests/unit/models/test_survey_models.py | 6 +-- awx/main/utils/common.py | 2 +- 5 files changed, 55 insertions(+), 24 deletions(-) diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 5ec4a4337d..cb0a2236d4 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -16,8 +16,8 @@ from awx.main.utils import parse_yaml_or_json from awx.main.fields import JSONField -__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin', - 'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin', +__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin', + 'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin', 'TaskManagerInventoryUpdateMixin',] @@ -111,20 +111,29 @@ class SurveyJobTemplateMixin(models.Model): vars.append(survey_element['variable']) return vars - def _update_unified_job_kwargs(self, **kwargs): + def _update_unified_job_kwargs(self, create_kwargs, kwargs): ''' Combine extra_vars with variable precedence order: JT extra_vars -> JT survey defaults -> runtime extra_vars + + :param create_kwargs: key-worded arguments to be updated and later used for creating unified job. + :type create_kwargs: dict + :param kwargs: request parameters used to override unified job template fields with runtime values. + :type kwargs: dict + :return: modified create_kwargs. + :rtype: dict ''' # Job Template extra_vars extra_vars = self.extra_vars_dict + survey_defaults = {} + # transform to dict if 'extra_vars' in kwargs: - kwargs_extra_vars = kwargs['extra_vars'] - kwargs_extra_vars = parse_yaml_or_json(kwargs_extra_vars) + runtime_extra_vars = kwargs['extra_vars'] + runtime_extra_vars = parse_yaml_or_json(runtime_extra_vars) else: - kwargs_extra_vars = {} + runtime_extra_vars = {} # Overwrite with job template extra vars with survey default vars if self.survey_enabled and 'spec' in self.survey_spec: @@ -133,22 +142,23 @@ class SurveyJobTemplateMixin(models.Model): variable_key = survey_element.get('variable') if survey_element.get('type') == 'password': - if variable_key in kwargs_extra_vars and default: - kw_value = kwargs_extra_vars[variable_key] + if variable_key in runtime_extra_vars and default: + kw_value = runtime_extra_vars[variable_key] if kw_value.startswith('$encrypted$') and kw_value != default: - kwargs_extra_vars[variable_key] = default + runtime_extra_vars[variable_key] = default if default is not None: data = {variable_key: default} errors = self._survey_element_validation(survey_element, data) if not errors: - extra_vars[variable_key] = default + survey_defaults[variable_key] = default + extra_vars.update(survey_defaults) # Overwrite job template extra vars with explicit job extra vars # and add on job extra vars - extra_vars.update(kwargs_extra_vars) - kwargs['extra_vars'] = json.dumps(extra_vars) - return kwargs + extra_vars.update(runtime_extra_vars) + create_kwargs['extra_vars'] = json.dumps(extra_vars) + return create_kwargs def _survey_element_validation(self, survey_element, data): errors = [] @@ -291,5 +301,3 @@ class TaskManagerProjectUpdateMixin(TaskManagerUpdateOnLaunchMixin): class TaskManagerInventoryUpdateMixin(TaskManagerUpdateOnLaunchMixin): class Meta: abstract = True - - diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index 827b131aed..9fe704018b 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -377,10 +377,18 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin): def _can_update(self): return bool(self.scm_type) - def _update_unified_job_kwargs(self, **kwargs): + def _update_unified_job_kwargs(self, create_kwargs, kwargs): + ''' + :param create_kwargs: key-worded arguments to be updated and later used for creating unified job. + :type create_kwargs: dict + :param kwargs: request parameters used to override unified job template fields with runtime values. + :type kwargs: dict + :return: modified create_kwargs. + :rtype: dict + ''' if self.scm_delete_on_next_update: - kwargs['scm_delete_on_update'] = True - return kwargs + create_kwargs['scm_delete_on_update'] = True + return create_kwargs def create_project_update(self, **kwargs): return self.create_unified_job(**kwargs) diff --git a/awx/main/tests/unit/models/test_job_template_unit.py b/awx/main/tests/unit/models/test_job_template_unit.py index a6086b7b9d..2a4d48e71d 100644 --- a/awx/main/tests/unit/models/test_job_template_unit.py +++ b/awx/main/tests/unit/models/test_job_template_unit.py @@ -97,7 +97,7 @@ def test_job_template_survey_mixin(job_template_factory): obj = objects.job_template obj.survey_enabled = True obj.survey_spec = {'spec': [{'default':'my_default', 'type':'password', 'variable':'my_variable'}]} - kwargs = obj._update_unified_job_kwargs(extra_vars={'my_variable':'$encrypted$'}) + kwargs = obj._update_unified_job_kwargs({}, {'extra_vars': {'my_variable':'$encrypted$'}}) assert kwargs['extra_vars'] == '{"my_variable": "my_default"}' @@ -113,10 +113,25 @@ def test_job_template_survey_mixin_length(job_template_factory): obj.survey_enabled = True obj.survey_spec = {'spec': [{'default':'my_default', 'type':'password', 'variable':'my_variable'}, {'type':'password', 'variable':'my_other_variable'}]} - kwargs = obj._update_unified_job_kwargs(extra_vars={'my_variable':'$encrypted$'}) + kwargs = obj._update_unified_job_kwargs({}, {'extra_vars': {'my_variable':'$encrypted$'}}) assert kwargs['extra_vars'] == '{"my_variable": "my_default"}' +def test_job_template_survey_mixin_survey_runtime_has_highest_priority(job_template_factory): + objects = job_template_factory( + 'survey_mixin_test', + organization='org1', + inventory='inventory1', + credential='cred1', + persisted=False, + ) + obj = objects.job_template + obj.survey_enabled = True + obj.survey_spec = {'spec': [{'default':'foo', 'type':'password', 'variable':'my_variable'}]} + kwargs = obj._update_unified_job_kwargs({}, {'extra_vars': {'my_variable': 'bar'}}) + assert kwargs['extra_vars'] == '{"my_variable": "bar"}' + + def test_job_template_can_start_with_callback_extra_vars_provided(job_template_factory): objects = job_template_factory( 'callback_extra_vars_test', diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py index af2d0e3735..d9642a9e17 100644 --- a/awx/main/tests/unit/models/test_survey_models.py +++ b/awx/main/tests/unit/models/test_survey_models.py @@ -86,7 +86,7 @@ def test_update_kwargs_survey_invalid_default(survey_spec_factory): spec['spec'][0]['min'] = 3 spec['spec'][0]['default'] = 1 jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True, extra_vars="var2: 2") - defaulted_extra_vars = jt._update_unified_job_kwargs() + defaulted_extra_vars = jt._update_unified_job_kwargs({}, {}) assert 'extra_vars' in defaulted_extra_vars # Make sure we did not set the invalid default of 1 assert json.loads(defaulted_extra_vars['extra_vars'])['var2'] == 2 @@ -115,7 +115,7 @@ def test_optional_survey_question_defaults( }, ]) jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True) - defaulted_extra_vars = jt._update_unified_job_kwargs() + defaulted_extra_vars = jt._update_unified_job_kwargs({}, {}) element = spec['spec'][0] if expect_use: assert jt._survey_element_validation(element, {element['variable']: element['default']}) == [] @@ -140,7 +140,7 @@ class TestWorkflowSurveys: survey_enabled=True, extra_vars="var1: 5" ) - updated_extra_vars = wfjt._update_unified_job_kwargs() + updated_extra_vars = wfjt._update_unified_job_kwargs({}, {}) assert 'extra_vars' in updated_extra_vars assert json.loads(updated_extra_vars['extra_vars'])['var1'] == 3 assert wfjt.can_start_without_user_input() diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 80a72c9cb2..5687e68cee 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -424,7 +424,7 @@ def copy_model_by_class(obj1, Class2, fields, kwargs): # Apply class-specific extra processing for origination of unified jobs if hasattr(obj1, '_update_unified_job_kwargs') and obj1.__class__ != Class2: - new_kwargs = obj1._update_unified_job_kwargs(**create_kwargs) + new_kwargs = obj1._update_unified_job_kwargs(create_kwargs, kwargs) else: new_kwargs = create_kwargs From b97fe3315239878aeb03d57ed721ba78862e263b Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 26 Jul 2017 17:21:59 -0700 Subject: [PATCH 098/342] making request for inventory information for remediation flow --- .../insights/insights.controller.js | 4 ++-- .../insights/insights.partial.html | 2 +- .../edit/inventory-edit.controller.js | 4 ++-- .../standard-inventory/inventory.form.js | 2 +- .../job-template-add.controller.js | 4 ++-- awx/ui/client/src/templates/main.js | 22 +++++++++++++------ 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js index 88c70ee00a..0e143d0b35 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js @@ -42,8 +42,8 @@ function (data, $scope, moment, $state, InventoryData, InsightsService) { window.open(`https://access.redhat.com/insights/inventory?machine=${$scope.$parent.host.insights_system_id}`, '_blank'); }; - $scope.remediateInventory = function(inv_id, inv_name, insights_credential){ - $state.go('templates.addJobTemplate', {inventory_id: inv_id, inventory_name:inv_name, credential_id: insights_credential}); + $scope.remediateInventory = function(inv_id, insights_credential){ + $state.go('templates.addJobTemplate', {inventory_id: inv_id, credential_id: insights_credential}); }; $scope.formCancel = function(){ diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html index f89fb237f8..5bd39fb816 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html +++ b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html @@ -88,6 +88,6 @@
- +
diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js index a709d5a919..2af0a612b0 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js +++ b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js @@ -105,8 +105,8 @@ function InventoriesEdit($scope, $location, $state.go('inventories'); }; - $scope.remediateInventory = function(inv_id, inv_name, insights_credential){ - $state.go('templates.addJobTemplate', {inventory_id: inv_id, inventory_name:inv_name, credential_id: insights_credential}); + $scope.remediateInventory = function(inv_id, insights_credential){ + $state.go('templates.addJobTemplate', {inventory_id: inv_id, credential_id: insights_credential}); }; } diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js index 0be972390c..5ee49cf0b7 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js @@ -200,7 +200,7 @@ function(i18n, InventoryCompletedJobsList) { }, relatedButtons: { remediate_inventory: { - ngClick: 'remediateInventory(id, name, insights_credential)', + ngClick: 'remediateInventory(id, insights_credential)', ngShow: 'is_insights && mode !== "add"', label: i18n._('Remediate Inventory'), class: 'Form-primaryButton' diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js index 5e5fd2ae7d..85d7480957 100644 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js @@ -197,8 +197,8 @@ }; if(Inventory){ - $scope.inventory = Inventory.inventory_id; - $scope.inventory_name = Inventory.inventory_name; + $scope.inventory = Inventory.id; + $scope.inventory_name = Inventory.name; } if(Project){ $scope.project = Project.id; diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index ecb07913c9..4ebb8f186b 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -44,7 +44,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. addJobTemplate = stateDefinitions.generateTree({ name: 'templates.addJobTemplate', - url: '/add_job_template?inventory_id&inventory_name&credential_id', + url: '/add_job_template?inventory_id&credential_id', modes: ['add'], form: 'JobTemplateForm', controllers: { @@ -52,13 +52,21 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. }, resolve: { add: { - Inventory: ['$stateParams', - function($stateParams){ + Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', + function($stateParams, Rest, GetBasePath, ProcessErrors){ if($stateParams.inventory_id){ - let obj = {}; - obj.inventory_id = Number($stateParams.inventory_id); - obj.inventory_name = $stateParams.inventory_name; - return obj; + let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; + Rest.setUrl(path); + return Rest.get(). + then(function(data){ + return data.data; + }).catch(function(response) { + ProcessErrors(null, response.data, response.status, null, { + hdr: 'Error!', + msg: 'Failed to get inventory info. GET returned status: ' + + response.status + }); + }); } }], Project: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', From c6d675449b625a96f414cc11b4d3a7f24e0bc4d0 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 27 Jul 2017 09:29:20 -0400 Subject: [PATCH 099/342] Fixed first/last pagination controls --- awx/ui/client/src/shared/paginate/paginate.controller.js | 3 ++- awx/ui/client/src/shared/paginate/paginate.partial.html | 4 ++-- awx/ui/client/src/templates/inventory-sources.list.js | 1 + awx/ui/client/src/templates/main.js | 1 + awx/ui/client/src/templates/templates.list.js | 1 + 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/shared/paginate/paginate.controller.js b/awx/ui/client/src/shared/paginate/paginate.controller.js index a8ff92572a..f4002f6ac5 100644 --- a/awx/ui/client/src/shared/paginate/paginate.controller.js +++ b/awx/ui/client/src/shared/paginate/paginate.controller.js @@ -7,6 +7,7 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q $scope.pageSize = pageSize; $scope.basePageSize = parseInt(pageSize) === 5 ? 5 : 20; + $scope.maxVisiblePages = $scope.maxVisiblePages ? parseInt($scope.maxVisiblePages) : 10; function init() { @@ -94,7 +95,7 @@ export default ['$scope', '$stateParams', '$state', '$filter', 'GetBasePath', 'Q function calcPageRange(current, last) { let result = [], - maxVisiblePages = $scope.maxVisiblePages ? parseInt($scope.maxVisiblePages) : 10, + maxVisiblePages = parseInt($scope.maxVisiblePages), pagesLeft, pagesRight; if(maxVisiblePages % 2) { diff --git a/awx/ui/client/src/shared/paginate/paginate.partial.html b/awx/ui/client/src/shared/paginate/paginate.partial.html index 9fab876eff..eacaf56269 100644 --- a/awx/ui/client/src/shared/paginate/paginate.partial.html +++ b/awx/ui/client/src/shared/paginate/paginate.partial.html @@ -2,7 +2,7 @@
    -
  • +
  • @@ -24,7 +24,7 @@
  • -
  • +
  • diff --git a/awx/ui/client/src/templates/inventory-sources.list.js b/awx/ui/client/src/templates/inventory-sources.list.js index 6179471684..712a81835f 100644 --- a/awx/ui/client/src/templates/inventory-sources.list.js +++ b/awx/ui/client/src/templates/inventory-sources.list.js @@ -12,6 +12,7 @@ export default { listTitle: 'INVENTORY SOURCES', index: false, hover: true, + searchBarFullWidth: true, fields: { name: { diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js index ecb07913c9..b3f6b4e932 100644 --- a/awx/ui/client/src/templates/main.js +++ b/awx/ui/client/src/templates/main.js @@ -671,6 +671,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates. delete list.fields.last_updated; list.fields.name.columnClass = "col-md-11"; list.maxVisiblePages = 5; + list.searchBarFullWidth = true; return list; } diff --git a/awx/ui/client/src/templates/templates.list.js b/awx/ui/client/src/templates/templates.list.js index 2e7f60293a..c508c46b17 100644 --- a/awx/ui/client/src/templates/templates.list.js +++ b/awx/ui/client/src/templates/templates.list.js @@ -17,6 +17,7 @@ export default ['i18n', function(i18n) { selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Use the %s button to create a new job template."), " "), index: false, hover: true, + searchBarFullWidth: true, fields: { name: { From 7f175a2f562d503dbaa8cb9a238f5007e5c1f878 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 27 Jul 2017 10:11:32 -0400 Subject: [PATCH 100/342] Fixed inventory sync status popover links --- .../source-summary-popover/source-summary-popover.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js index a42530cfde..df340ddeda 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js +++ b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js @@ -70,7 +70,7 @@ export default ['templateUrl', '$compile', 'Wait', '$filter', 'i18n', html += ""; html += ""; html += "NA"; - html += "" + $filter('sanitize')(ellipsis(row.name)) + ""; + html += "" + $filter('sanitize')(ellipsis(row.name)) + ""; html += "\n"; } }); From 8e8a4c84f5279c00d535c933dbd6a299dd4650a4 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 27 Jul 2017 10:20:14 -0400 Subject: [PATCH 101/342] Hide view per page when adding users/admins to org --- .../src/organizations/linkout/addUsers/addUsers.controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js index a6e3b58f54..4e4a0a1e23 100644 --- a/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js +++ b/awx/ui/client/src/organizations/linkout/addUsers/addUsers.controller.js @@ -54,7 +54,8 @@ function($scope, $rootScope, ProcessErrors, GetBasePath, generateList, let html = generateList.build({ list: list, mode: 'edit', - title: false + title: false, + hideViewPerPage: true }); $scope.list = list; From 846cba285c0bd93019c06990d0342ce79e5f3b2a Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 27 Jul 2017 10:40:06 -0400 Subject: [PATCH 102/342] Reload state after launching job from portal or home --- .../job-submission-factories/launchjob.factory.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js index c55f9976de..c876ed8706 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js @@ -181,6 +181,9 @@ export default } } } + else { + $state.go('.', null, {reload: true}); + } }) .error(function(data, status) { let template_id = scope.job_template_id; From 5d167cafa2a8dd82382c23dfc60218b108c023ca Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 27 Jul 2017 11:32:09 -0400 Subject: [PATCH 103/342] Revert "Merge pull request #39 from wwitzel3/issue-7260" This reverts commit 6cd059278184bc1693320ad7e4403d08d1457def, reversing changes made to 943a40eb40817a3c4d28bfb11d2acca0dae277ce. --- awx/main/consumers.py | 36 ++++++++++++++++++++---------------- awx/settings/defaults.py | 3 --- awx/sso/views.py | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/awx/main/consumers.py b/awx/main/consumers.py index 39020099d1..ff55507939 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -1,14 +1,16 @@ import json import logging +import urllib from channels import Group, channel_layers -from channels.sessions import enforce_ordering, channel_session, channel_and_http_session +from channels.sessions import channel_session +from channels.handler import AsgiRequest from django.conf import settings from django.core.serializers.json import DjangoJSONEncoder from django.contrib.auth.models import User -from django.contrib.sessions.models import Session +from awx.main.models.organization import AuthToken logger = logging.getLogger('awx.main.consumers') @@ -20,21 +22,24 @@ def discard_groups(message): Group(group).discard(message.reply_channel) -@channel_and_http_session +@channel_session def ws_connect(message): - if message.http_session.session_key is None: - raise ValueError('No valid session key to get auth from') + connect_text = {'accept':False, 'user':None} - session = Session.objects.get(session_key=message.http_session.session_key) - session_data = session.get_decoded() - - try: - user = User.objects.get(pk=session_data['_auth_user_id']) - except User.DoesNotExist: - raise ValueError('No valid user for the session key') - - message.channel_session['user_id'] = user.pk - message.reply_channel.send({"text": json.dumps({'accept': True, 'user': user.pk})}) + message.content['method'] = 'FAKE' + request = AsgiRequest(message) + token = request.COOKIES.get('token', None) + if token is not None: + token = urllib.unquote(token).strip('"') + try: + auth_token = AuthToken.objects.get(key=token) + if auth_token.in_valid_tokens: + message.channel_session['user_id'] = auth_token.user_id + connect_text['accept'] = True + connect_text['user'] = auth_token.user_id + except AuthToken.DoesNotExist: + logger.error("auth_token provided was invalid.") + message.reply_channel.send({"text": json.dumps(connect_text)}) @channel_session @@ -42,7 +47,6 @@ def ws_disconnect(message): discard_groups(message) -@enforce_ordering @channel_session def ws_receive(message): from awx.main.access import consumer_access diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index d5fa6c4559..b8dfc97f28 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -189,9 +189,6 @@ JOB_EVENT_MAX_QUEUE_SIZE = 10000 # Disallow sending session cookies over insecure connections SESSION_COOKIE_SECURE = True -# Do not allow non-browser clients to read the CSRF cookie. -CSRF_COOKIE_HTTPONLY = True - # Disallow sending csrf cookies over insecure connections CSRF_COOKIE_SECURE = True diff --git a/awx/sso/views.py b/awx/sso/views.py index f2389f67f5..9e680186a9 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -60,7 +60,7 @@ class CompleteView(BaseRedirectView): logger.info(smart_text(u"User {} logged in".format(self.request.user.username))) request.session['auth_token_key'] = token.key token_key = urllib.quote('"%s"' % token.key) - response.set_cookie('token', value=token_key, httponly=True) + response.set_cookie('token', token_key) token_expires = token.expires.astimezone(utc).strftime('%Y-%m-%dT%H:%M:%S') token_expires = '%s.%03dZ' % (token_expires, token.expires.microsecond / 1000) token_expires = urllib.quote('"%s"' % token_expires) From c8b014292927523013ed2b3e77707f9e022fd6cb Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 27 Jul 2017 11:39:00 -0400 Subject: [PATCH 104/342] Fix test failures --- tools/docker-compose/unit-tests/entrypoint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/docker-compose/unit-tests/entrypoint.sh b/tools/docker-compose/unit-tests/entrypoint.sh index 63190314b9..1adfc079a4 100644 --- a/tools/docker-compose/unit-tests/entrypoint.sh +++ b/tools/docker-compose/unit-tests/entrypoint.sh @@ -1,9 +1,9 @@ #!/bin/bash # Code duplicated from start_development.sh -cp -R /tmp/ansible_awx.egg-info /awx_devel/ || true -sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/ansible_awx.egg-info/PKG-INFO -cp /tmp/ansible-awx.egg-link /venv/awx/lib/python2.7/site-packages/ansible-awx.egg-link +cp -R /tmp/awx.egg-info /awx_devel/ || true +sed -i "s/placeholder/$(git describe --long | sed 's/\./\\./g')/" /awx_devel/awx.egg-info/PKG-INFO +cp /tmp/awx.egg-link /venv/awx/lib/python2.7/site-packages/awx.egg-link cp -f awx/settings/local_settings.py.docker_compose awx/settings/local_settings.py From 60da24d82fbcf06c8d4629edaf309aa85c39efea Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Thu, 27 Jul 2017 11:19:23 -0400 Subject: [PATCH 105/342] fix a few activity stream bugs related to setting creation/update * when a setting is created, only create *one* activity stream record for the creation, not one for create and another for update (similar to https://github.com/ansible/tower/pull/53) * add code to hide `$encrypted$` activity stream content see: https://github.com/ansible/ansible-tower/issues/7320 --- awx/conf/models.py | 6 ++++-- awx/main/utils/common.py | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/awx/conf/models.py b/awx/conf/models.py index 5c26e17c54..afdacb1757 100644 --- a/awx/conf/models.py +++ b/awx/conf/models.py @@ -65,8 +65,10 @@ class Setting(CreatedModifiedModel): # After saving a new instance for the first time, set the encrypted # field and save again. if encrypted and new_instance: - self.value = self._saved_value - self.save(update_fields=['value']) + from awx.main.signals import disable_activity_stream + with disable_activity_stream(): + self.value = self._saved_value + self.save(update_fields=['value']) @classmethod def get_cache_key(self, key): diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 1005ad74c8..58d795567f 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -16,6 +16,7 @@ import urlparse import threading import contextlib import tempfile +import six # Decorator from decorator import decorator @@ -325,7 +326,10 @@ def _convert_model_field_for_display(obj, field_name, password_fields=None): return '-{}'.format(obj._meta.verbose_name, getattr(obj, '{}_id'.format(field_name))) if password_fields is None: password_fields = set(getattr(type(obj), 'PASSWORD_FIELDS', [])) | set(['password']) - if field_name in password_fields: + if field_name in password_fields or ( + isinstance(field_val, six.string_types) and + field_val.startswith('$encrypted$') + ): return u'hidden' if hasattr(obj, 'display_%s' % field_name): field_val = getattr(obj, 'display_%s' % field_name)() From 1d0e125aadc9286676174a79b1800a288e705fe0 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 26 Jul 2017 15:38:50 -0400 Subject: [PATCH 106/342] make password survey field behave like text field related to https://github.com/ansible/ansible-tower/issues/6083 --- awx/main/models/mixins.py | 15 +++++------ awx/main/tests/factories/tower.py | 10 +++++--- .../tests/unit/models/test_survey_models.py | 25 +++++++++++-------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index cb0a2236d4..e13b56fa87 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -170,13 +170,14 @@ class SurveyJobTemplateMixin(models.Model): errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']], survey_element['variable'])) return errors - if not data[survey_element['variable']] == '$encrypted$' and not survey_element['type'] == 'password': - if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']): - errors.append("'%s' value %s is too small (length is %s must be at least %s)." % - (survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min'])) - if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > int(survey_element['max']): - errors.append("'%s' value %s is too large (must be no more than %s)." % - (survey_element['variable'], data[survey_element['variable']], survey_element['max'])) + + if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']): + errors.append("'%s' value %s is too small (length is %s must be at least %s)." % + (survey_element['variable'], data[survey_element['variable']], len(data[survey_element['variable']]), survey_element['min'])) + if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > int(survey_element['max']): + errors.append("'%s' value %s is too large (must be no more than %s)." % + (survey_element['variable'], data[survey_element['variable']], survey_element['max'])) + elif survey_element['type'] == 'integer': if survey_element['variable'] in data: if type(data[survey_element['variable']]) != int: diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py index aa273227d2..a8f20f941f 100644 --- a/awx/main/tests/factories/tower.py +++ b/awx/main/tests/factories/tower.py @@ -139,7 +139,7 @@ def create_instance_group(name, instances=None): return mk_instance_group(name=name, instance=instances) -def create_survey_spec(variables=None, default_type='integer', required=True): +def create_survey_spec(variables=None, default_type='integer', required=True, min=None, max=None): ''' Returns a valid survey spec for a job template, based on the input argument specifying variable name(s) @@ -174,10 +174,14 @@ def create_survey_spec(variables=None, default_type='integer', required=True): spec_item.setdefault('question_description', "A question about %s." % var_name) if spec_item['type'] == 'integer': spec_item.setdefault('default', 0) - spec_item.setdefault('max', spec_item['default'] + 100) - spec_item.setdefault('min', spec_item['default'] - 100) + spec_item.setdefault('max', max or spec_item['default'] + 100) + spec_item.setdefault('min', min or spec_item['default'] - 100) else: spec_item.setdefault('default', '') + if min: + spec_item.setdefault('min', min) + if max: + spec_item.setdefault('max', max) spec.append(spec_item) survey_spec = {} diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py index d9642a9e17..967ac406c1 100644 --- a/awx/main/tests/unit/models/test_survey_models.py +++ b/awx/main/tests/unit/models/test_survey_models.py @@ -93,24 +93,29 @@ def test_update_kwargs_survey_invalid_default(survey_spec_factory): @pytest.mark.survey -@pytest.mark.parametrize("question_type,default,expect_use,expect_value", [ - ("multiplechoice", "", False, 'N/A'), # historical bug - ("multiplechoice", "zeb", False, 'N/A'), # zeb not in choices - ("multiplechoice", "coffee", True, 'coffee'), - ("multiselect", None, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered - ("multiselect", "", False, 'N/A'), - ("multiselect", ["zeb"], False, 'N/A'), - ("multiselect", ["milk"], True, ["milk"]), - ("multiselect", ["orange\nmilk"], False, 'N/A'), # historical bug +@pytest.mark.parametrize("question_type,default,min,max,expect_use,expect_value", [ + ("text", "", 0, 0, True, ''), # default used + ("text", "", 1, 0, False, 'N/A'), # value less than min length + ("password", "", 1, 0, False, 'N/A'), # passwords behave the same as text + ("multiplechoice", "", 0, 0, False, 'N/A'), # historical bug + ("multiplechoice", "zeb", 0, 0, False, 'N/A'), # zeb not in choices + ("multiplechoice", "coffee", 0, 0, True, 'coffee'), + ("multiselect", None, 0, 0, False, 'N/A'), # NOTE: Behavior is arguable, value of [] may be prefered + ("multiselect", "", 0, 0, False, 'N/A'), + ("multiselect", ["zeb"], 0, 0, False, 'N/A'), + ("multiselect", ["milk"], 0, 0, True, ["milk"]), + ("multiselect", ["orange\nmilk"], 0, 0, False, 'N/A'), # historical bug ]) def test_optional_survey_question_defaults( - survey_spec_factory, question_type, default, expect_use, expect_value): + survey_spec_factory, question_type, default, min, max, expect_use, expect_value): spec = survey_spec_factory([ { "required": False, "default": default, "choices": "orange\nmilk\nchocolate\ncoffee", "variable": "c", + "min": min, + "max": max, "type": question_type }, ]) From f20f4f40a015a6593fe698fa6d065312e6cb71bb Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 27 Jul 2017 10:39:43 -0400 Subject: [PATCH 107/342] trim insights content to only what the UI needs --- awx/api/views.py | 4 +- awx/main/tests/data/insights.json | 724 +++++++++++++++++++++ awx/main/tests/data/insights.py | 9 + awx/main/tests/unit/utils/test_insights.py | 24 + awx/main/utils/insights.py | 42 ++ 5 files changed, 802 insertions(+), 1 deletion(-) create mode 100644 awx/main/tests/data/insights.json create mode 100644 awx/main/tests/data/insights.py create mode 100644 awx/main/tests/unit/utils/test_insights.py create mode 100644 awx/main/utils/insights.py diff --git a/awx/api/views.py b/awx/api/views.py index e783b94d0b..62c0082678 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -74,6 +74,7 @@ from awx.main.utils import ( decrypt_field, ) from awx.main.utils.filters import SmartFilter +from awx.main.utils.insights import filter_insights_api_response from awx.api.permissions import * # noqa from awx.api.renderers import * # noqa @@ -2097,7 +2098,8 @@ class HostInsights(GenericAPIView): return (dict(error=_('Failed to gather reports and maintenance plans from Insights API at URL {}. Server responded with {} status code and message {}').format(url, res.status_code, res.content)), status.HTTP_500_INTERNAL_SERVER_ERROR) try: - return (dict(insights_content=res.json()), status.HTTP_200_OK) + filtered_insights_content = filter_insights_api_response(res.json()) + return (dict(insights_content=filtered_insights_content), status.HTTP_200_OK) except ValueError: return (dict(error=_('Expected JSON response from Insights but instead got {}').format(res.content)), status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/awx/main/tests/data/insights.json b/awx/main/tests/data/insights.json new file mode 100644 index 0000000000..204985ab2f --- /dev/null +++ b/awx/main/tests/data/insights.json @@ -0,0 +1,724 @@ +{ + "toString": "$REDACTED$", + "isCheckingIn": false, + "system_id": "11111111-1111-1111-1111-111111111111", + "display_name": null, + "remote_branch": null, + "remote_leaf": null, + "account_number": "1111111", + "hostname": "$REDACTED$", + "parent_id": null, + "system_type_id": 105, + "last_check_in": "2017-07-21T07:07:29.000Z", + "stale_ack": false, + "type": "machine", + "product": "rhel", + "created_at": "2017-07-20T17:26:53.000Z", + "updated_at": "2017-07-21T07:07:29.000Z", + "unregistered_at": null, + "reports": [{ + "details": { + "vulnerable_setting": "hosts: files dns myhostname", + "affected_package": "glibc-2.17-105.el7", + "error_key": "GLIBC_CVE_2015_7547" + }, + "id": 955802695, + "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A critical security flaw in the glibc library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.

    \n", + "generic_html": "

    The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

    \n", + "more_info_html": "\n", + "severity": "ERROR", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", + "error_key": "GLIBC_CVE_2015_7547", + "plugin": "CVE_2015_7547_glibc", + "description": "Remote code execution vulnerability in libresolv via crafted DNS response (CVE-2015-7547)", + "summary": "A critical security flaw in the `glibc` library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.", + "generic": "The `glibc` library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the `libresolv` part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when `libresolv` is called from the nss_dns NSS service module. This flaw is known as [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).", + "reason": "

    This host is vulnerable because it has vulnerable package glibc-2.17-105.el7 installed and DNS is enabled in /etc/nsswitch.conf:

    \n
    hosts:      files dns myhostname\n

    The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

    \n", + "type": null, + "more_info": "* For more information about the flaw see [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", + "active": true, + "node_id": "2168451", + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2016-10-31T04:08:35.000Z", + "rec_impact": 4, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends updating glibc and restarting the affected system:

    \n
    # yum update glibc\n# reboot\n

    Alternatively, you can restart all affected services, but because this vulnerability affects a large amount of applications on the system, the best solution is to restart the system.

    \n" + }, + "maintenance_actions": [{ + "done": false, + "id": 305205, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 305955, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "affected_kernel": "3.10.0-327.el7", + "error_key": "KERNEL_CVE-2016-0728" + }, + "id": 955802705, + "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as CVE-2016-0728.

    \n", + "generic_html": "

    A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

    \n

    Red Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

    \n", + "more_info_html": "\n", + "severity": "WARN", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", + "error_key": "KERNEL_CVE-2016-0728", + "plugin": "CVE_2016_0728_kernel", + "description": "Kernel key management subsystem vulnerable to local privilege escalation (CVE-2016-0728)", + "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).", + "generic": "A vulnerability in the Linux kernel rated **Important** was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n\nRed Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the [systemtap patch](https://bugzilla.redhat.com/attachment.cgi?id=1116284&action=edit) to update your running kernel.", + "reason": "

    A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

    \n

    The host is vulnerable as it is running kernel-3.10.0-327.el7.

    \n", + "type": null, + "more_info": "* For more information about the flaws and versions of the package that are vulnerable see [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", + "active": true, + "node_id": "2130791", + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2016-10-31T04:08:37.000Z", + "rec_impact": 2, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends that you update kernel and reboot. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

    \n
    # yum update kernel\n# reboot\n-or-\n# debuginfo-install kernel     (or equivalent)\n# stap -vgt -Gfix_p=1 -Gtrace_p=0 cve20160728e.stp\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305215, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 306205, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "processes_listening_int": [ + ["neutron-o", "127.0.0.1", "6633"], + ["ovsdb-ser", "127.0.0.1", "6640"] + ], + "processes_listening_ext": [ + ["CPU", "0.0.0.0", "5900"], + ["libvirtd", "", "::16509"], + ["master", "", ":1:25"], + ["qemu-kvm", "0.0.0.0", "5900"], + ["vnc_worke", "0.0.0.0", "5900"], + ["worker", "0.0.0.0", "5900"] + ], + "error_key": "OPENSSL_CVE_2016_0800_DROWN_LISTENING", + "processes_listening": [ + ["CPU", "0.0.0.0", "5900"], + ["libvirtd", "", "::16509"], + ["master", "", ":1:25"], + ["neutron-o", "127.0.0.1", "6633"], + ["ovsdb-ser", "127.0.0.1", "6640"], + ["qemu-kvm", "0.0.0.0", "5900"], + ["vnc_worke", "0.0.0.0", "5900"], + ["worker", "0.0.0.0", "5900"] + ], + "processes_names": ["/usr/bin/", "CPU", "ceilomete", "gmain", "handler6", "libvirtd", "master", "neutron-o", "neutron-r", "nova-comp", "ovs-vswit", "ovsdb-cli", "ovsdb-ser", "pickup", "privsep-h", "qemu-kvm", "qmgr", "redhat-ac", "revalidat", "tuned", "urcu3", "virtlogd", "vnc_worke", "worker"], + "vulnerable_package": "openssl-libs-1.0.1e-42.el7_1.9" + }, + "id": 955802715, + "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_DROWN_LISTENING", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned CVE-2016-0800 and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.

    \n", + "generic_html": "

    A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

    \n

    A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

    \n", + "more_info_html": "\n", + "severity": "ERROR", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_DROWN_LISTENING", + "error_key": "OPENSSL_CVE_2016_0800_DROWN_LISTENING", + "plugin": "CVE_2016_0800_openssl_drown", + "description": "OpenSSL with externally listening processes vulnerable to session decryption (CVE-2016-0800/DROWN)", + "summary": "A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800) and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.", + "generic": "A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.\n\nA more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see [CVE-2015-0293](https://access.redhat.com/security/cve/CVE-2015-0293)).", + "reason": "

    This host is vulnerable because it has vulnerable package openssl-libs-1.0.1e-42.el7_1.9 installed.

    \n

    It also runs the following processes that use OpenSSL libraries:

    \n
    • /usr/bin/
    • CPU
    • ceilomete
    • gmain
    • handler6
    • libvirtd
    • master
    • neutron-o
    • neutron-r
    • nova-comp
    • ovs-vswit
    • ovsdb-cli
    • ovsdb-ser
    • pickup
    • privsep-h
    • qemu-kvm
    • qmgr
    • redhat-ac
    • revalidat
    • tuned
    • urcu3
    • virtlogd
    • vnc_worke
    • worker
    \n\n\n\n\n

    The following processes that use OpenSSL libraries are listening on the sockets bound to public IP addresses:

    \n
    • CPU (0.0.0.0)
    • libvirtd ()
    • master ()
    • qemu-kvm (0.0.0.0)
    • vnc_worke (0.0.0.0)
    • worker (0.0.0.0)
    \n\n\n\n\n\n\n\n\n

    A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

    \n

    A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

    \n", + "type": null, + "more_info": "* For more information about the flaw see [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", + "active": true, + "node_id": "2174451", + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2016-10-31T04:08:33.000Z", + "rec_impact": 3, + "rec_likelihood": 4, + "resolution": "

    Red Hat recommends that you update openssl and restart the affected system:

    \n
    # yum update openssl\n# reboot\n

    Alternatively, you can restart all affected services (that is, the ones linked to the openssl library), especially those listening on public IP addresses.

    \n" + }, + "maintenance_actions": [{ + "done": false, + "id": 305225, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 306435, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "vulnerable_kernel": "3.10.0-327.el7", + "package_name": "kernel", + "error_key": "KERNEL_CVE_2016_5195_2" + }, + "id": 955802725, + "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195_2", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.

    \n", + "generic_html": "

    A race condition was found in the way Linux kernel's memory subsystem handled breakage of the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.

    \n

    A process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild.

    \n

    Red Hat recommends that you update the kernel package.

    \n", + "more_info_html": "\n", + "severity": "WARN", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195_2", + "error_key": "KERNEL_CVE_2016_5195_2", + "plugin": "CVE_2016_5195_kernel", + "description": "Kernel vulnerable to privilege escalation via permission bypass (CVE-2016-5195)", + "summary": "A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.", + "generic": "A race condition was found in the way Linux kernel's memory subsystem handled breakage of the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.\n\nA process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild. \n\nRed Hat recommends that you update the kernel package.\n", + "reason": "

    A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally have read-only access to and thus increase their privileges on the system.

    \n

    This host is affected because it is running kernel 3.10.0-327.el7.

    \n", + "type": null, + "more_info": "* For more information about the flaw see [CVE-2016-5195](https://access.redhat.com/security/cve/CVE-2016-5195)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", + "active": true, + "node_id": "2706661", + "category": "Security", + "retired": false, + "reboot_required": true, + "publish_date": "2016-10-31T04:08:33.000Z", + "rec_impact": 2, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends that you update the kernel package and restart the system:

    \n
    # yum update kernel\n# reboot\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305235, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 306705, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "mitigation_conf": "no", + "sysctl_live_ack_limit": "100", + "package_name": "kernel", + "sysctl_live_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit = 100", + "error_key": "KERNEL_CVE_2016_5696_URGENT", + "vulnerable_kernel": "3.10.0-327.el7", + "sysctl_conf_ack_limit": "100", + "sysctl_conf_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit = 100 # Implicit default", + "mitigation_live": "no" + }, + "id": 955802735, + "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the RFC 5961 challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

    \n", + "generic_html": "

    A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

    \n

    Red Hat recommends that you update the kernel package or apply mitigations.

    \n", + "more_info_html": "\n", + "severity": "ERROR", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", + "error_key": "KERNEL_CVE_2016_5696_URGENT", + "plugin": "CVE_2016_5696_kernel", + "description": "Kernel vulnerable to man-in-the-middle via payload injection", + "summary": "A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the [RFC 5961](https://tools.ietf.org/html/rfc5961) challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.", + "generic": "A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack ([RFC 5961](https://tools.ietf.org/html/rfc5961)) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", + "reason": "

    A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

    \n

    This host is affected because it is running kernel 3.10.0-327.el7.

    \n

    Your currently loaded kernel configuration contains this setting:

    \n
    net.ipv4.tcp_challenge_ack_limit = 100\n

    Your currently stored kernel configuration is:

    \n
    net.ipv4.tcp_challenge_ack_limit = 100  # Implicit default\n

    There is currently no mitigation applied and your system is vulnerable.

    \n", + "type": null, + "more_info": "* For more information about the flaw see [CVE-2016-5696](https://access.redhat.com/security/cve/CVE-2016-5696)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", + "active": true, + "node_id": "2438571", + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2016-10-31T04:08:32.000Z", + "rec_impact": 4, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends that you update the kernel package and restart the system:

    \n
    # yum update kernel\n# reboot\n

    or

    \n

    Alternatively, this issue can be addressed by applying the following mitigations until the machine is restarted with the updated kernel package.

    \n

    Edit /etc/sysctl.conf file as root, add the mitigation configuration, and reload the kernel configuration:

    \n
    # echo "net.ipv4.tcp_challenge_ack_limit = 2147483647" >> /etc/sysctl.conf \n# sysctl -p\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305245, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 306975, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 316055, + "maintenance_plan": { + "maintenance_id": 30575, + "name": "Fix the problem", + "description": null, + "start": null, + "end": null, + "created_by": "asdavis@redhat.com", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "kernel_left_fully_exploitable": true, + "vulnerable_kernel_version_release": "3.10.0-327.el7", + "kernel_kpatch_applied": false, + "kernel_vulnerable": true, + "glibc_left_fully_exploitable": true, + "vulnerable_glibc": { + "PACKAGE_NAMES": ["glibc"], + "PACKAGES": ["glibc-2.17-105.el7"] + }, + "kernel_stap_applied": false, + "error_key": "CVE_2017_1000364_KERNEL_CVE_2017_1000366_GLIBC_EXPLOITABLE", + "vulnerable_kernel_name": "kernel", + "nothing_left_fully_exploitable": false, + "glibc_vulnerable": true + }, + "id": 955802745, + "rule_id": "CVE_2017_1000366_glibc|CVE_2017_1000364_KERNEL_CVE_2017_1000366_GLIBC_EXPLOITABLE", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A flaw was found in the way memory is being allocated on the stack for user space binaries. It has been assigned CVE-2017-1000364 and CVE-2017-1000366. An unprivileged local user can use this flaw to execute arbitrary code as root and increase their privileges on the system.

    \n", + "generic_html": "

    A flaw was found in the way memory is being allocated on the stack for user space binaries. It has been assigned CVE-2017-1000364 and CVE-2017-1000366. An unprivileged local user can use this flaw to execute arbitrary code as root and increase their privileges on the system.

    \n

    If heap and stack memory regions are adjacent to each other, an attacker can use this flaw to jump over the heap/stack gap, cause controlled memory corruption on process stack or heap, and thus increase their privileges on the system.

    \n

    An attacker must have access to a local account on the system.

    \n

    Red Hat recommends that you update the kernel and glibc.

    \n", + "more_info_html": "\n", + "severity": "WARN", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2017_1000366_glibc|CVE_2017_1000364_KERNEL_CVE_2017_1000366_GLIBC_EXPLOITABLE", + "error_key": "CVE_2017_1000364_KERNEL_CVE_2017_1000366_GLIBC_EXPLOITABLE", + "plugin": "CVE_2017_1000366_glibc", + "description": "Kernel and glibc vulnerable to local privilege escalation via stack and heap memory clash (CVE-2017-1000364 and CVE-2017-1000366)", + "summary": "A flaw was found in the way memory is being allocated on the stack for user space binaries. It has been assigned [CVE-2017-1000364](https://access.redhat.com/security/cve/CVE-2017-1000364) and [CVE-2017-1000366](https://access.redhat.com/security/cve/CVE-2017-1000366). An unprivileged local user can use this flaw to execute arbitrary code as root and increase their privileges on the system.\n", + "generic": "A flaw was found in the way memory is being allocated on the stack for user space binaries. It has been assigned CVE-2017-1000364 and CVE-2017-1000366. An unprivileged local user can use this flaw to execute arbitrary code as root and increase their privileges on the system.\n\nIf heap and stack memory regions are adjacent to each other, an attacker can use this flaw to jump over the heap/stack gap, cause controlled memory corruption on process stack or heap, and thus increase their privileges on the system. \n\nAn attacker must have access to a local account on the system.\n\nRed Hat recommends that you update the kernel and glibc.\n", + "reason": "

    A flaw was found in kernel and glibc in the way memory is being allocated on the stack for user space binaries.

    \n

    The host is affected because it is running kernel-3.10.0-327.el7 and using glibc-2.17-105.el7.

    \n", + "type": null, + "more_info": "* For more information about the flaw, see [the vulnerability article](https://access.redhat.com/security/vulnerabilities/stackguard) and [CVE-2017-1000364](https://access.redhat.com/security/cve/CVE-2017-1000364) and [CVE-2017-1000366](https://access.redhat.com/security/cve/CVE-2017-1000366).\n* To learn how to upgrade packages, see [What is yum and how do I use it?](https://access.redhat.com/solutions/9934).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", + "active": true, + "node_id": null, + "category": "Security", + "retired": false, + "reboot_required": true, + "publish_date": "2017-06-19T15:00:00.000Z", + "rec_impact": 2, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends updating the kernel and glibc packages and rebooting the system.

    \n
    # yum update kernel glibc\n# reboot\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305255, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 307415, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "PACKAGE_NAMES": ["sudo"], + "PACKAGES": ["sudo-1.8.6p7-16.el7"], + "error_key": "CVE_2017_1000367_SUDO" + }, + "id": 955802755, + "rule_id": "CVE_2017_1000367_sudo|CVE_2017_1000367_SUDO", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A local privilege escalation flaw was found in sudo. A local user having sudo access on the system,\ncould use this flaw to execute arbitrary commands as root. This issue was reported as\nCVE-2017-1000367

    \n", + "generic_html": "

    A local privilege escalation flaw was found in sudo. All versions of sudo package shipped with RHEL 5, 6 and 7 are vulnerable\nto a local privilege escalation vulnerability. A flaw was found in the way get_process_ttyname() function obtained\ninformation about the controlling terminal of the sudo process from the status file in the proc filesystem.\nThis allows a local user who has any level of sudo access on the system to execute arbitrary commands as root or\nin certain conditions escalate his privileges to root.

    \n

    Red Hat recommends that you update update the sudo package.

    \n", + "more_info_html": "\n", + "severity": "WARN", + "ansible": true, + "ansible_fix": true, + "ansible_mitigation": false, + "rule_id": "CVE_2017_1000367_sudo|CVE_2017_1000367_SUDO", + "error_key": "CVE_2017_1000367_SUDO", + "plugin": "CVE_2017_1000367_sudo", + "description": "sudo vulnerable to local privilege escalation via process TTY name parsing (CVE-2017-1000367)", + "summary": "A local privilege escalation flaw was found in `sudo`. A local user having sudo access on the system,\ncould use this flaw to execute arbitrary commands as root. This issue was reported as\n[CVE-2017-1000367](https://access.redhat.com/security/cve/CVE-2017-1000367)", + "generic": "A local privilege escalation flaw was found in `sudo`. All versions of sudo package shipped with RHEL 5, 6 and 7 are vulnerable\nto a local privilege escalation vulnerability. A flaw was found in the way `get_process_ttyname()` function obtained\ninformation about the controlling terminal of the sudo process from the status file in the proc filesystem.\nThis allows a local user who has any level of sudo access on the system to execute arbitrary commands as root or\nin certain conditions escalate his privileges to root.\n\nRed Hat recommends that you update update the `sudo` package.\n", + "reason": "

    This machine is vulnerable because it has vulnerable sudo package sudo-1.8.6p7-16.el7 installed.

    \n", + "type": null, + "more_info": "* For more information about the remote code execution flaw [CVE-2017-1000367](https://access.redhat.com/security/cve/CVE-2017-1000367) see [knowledge base article](https://access.redhat.com/security/vulnerabilities/3059071).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* To better understand [sudo](https://www.sudo.ws/), see [Sudo in a Nutshell](https://www.sudo.ws/intro.html)\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", + "active": true, + "node_id": "3059071", + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2017-05-30T13:30:00.000Z", + "rec_impact": 2, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends that you update the sudo package.

    \n
    # yum update sudo\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305265, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 308075, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "mod_loading_disabled": false, + "package_name": "kernel", + "error_key": "KERNEL_CVE_2017_2636", + "vulnerable_kernel": "3.10.0-327.el7", + "mod_loaded": false, + "mitigation_info": true + }, + "id": 955802765, + "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as CVE-2017-2636.

    \n", + "generic_html": "

    A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.

    \n

    An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.

    \n

    An attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.

    \n

    Red Hat recommends that you use the proposed mitigation to disable the N_HDLC module.

    \n", + "more_info_html": "\n", + "severity": "WARN", + "ansible": true, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", + "error_key": "KERNEL_CVE_2017_2636", + "plugin": "CVE_2017_2636_kernel", + "description": "Kernel vulnerable to local privilege escalation via n_hdlc module (CVE-2017-2636)", + "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636).\n", + "generic": "A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.\n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.\n\nAn attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.\n\nRed Hat recommends that you use the proposed mitigation to disable the N_HDLC module.\n", + "reason": "

    A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation.

    \n

    This host is affected because it is running kernel 3.10.0-327.el7.

    \n", + "type": null, + "more_info": "* For more information about the flaw, see [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636) and [CVE-2017-2636 article](https://access.redhat.com/security/vulnerabilities/CVE-2017-2636).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", + "active": true, + "node_id": null, + "category": "Security", + "retired": false, + "reboot_required": false, + "publish_date": "2017-05-16T12:00:00.000Z", + "rec_impact": 2, + "rec_likelihood": 2, + "resolution": "

    Red Hat recommends updating the kernel package and rebooting the system.

    \n
    # yum update kernel\n# reboot\n

    Alternatively, apply one of the following mitigations:

    \n

    Disable loading of N_HDLC kernel module:

    \n
    # echo "install n_hdlc /bin/true" >> /etc/modprobe.d/disable-n_hdlc.conf\n
    " + }, + "maintenance_actions": [{ + "done": false, + "id": 305275, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 308675, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }, { + "details": { + "kvr": "3.10.0-327.el7", + "error_key": "IPMI_LIST_CORRUPTION_CRASH" + }, + "id": 955826995, + "rule_id": "ipmi_list_corruption_crash|IPMI_LIST_CORRUPTION_CRASH", + "system_id": "11111111-1111-1111-1111-111111111111", + "account_number": "1111111", + "uuid": "11111111111111111111111111111111", + "date": "2017-07-21T07:07:29.000Z", + "rule": { + "summary_html": "

    Kernel occasionally panics when running ipmitool command due to a bug in the ipmi message handler.

    \n", + "generic_html": "

    Kernel occasionally panics when running ipmitool due to a bug in the ipmi message handler.

    \n", + "more_info_html": "

    For how to upgrade the kernel to a specific version, refer to How do I upgrade the kernel to a particular version manually?.

    \n", + "severity": "WARN", + "ansible": false, + "ansible_fix": false, + "ansible_mitigation": false, + "rule_id": "ipmi_list_corruption_crash|IPMI_LIST_CORRUPTION_CRASH", + "error_key": "IPMI_LIST_CORRUPTION_CRASH", + "plugin": "ipmi_list_corruption_crash", + "description": "Kernel panic occurs when running ipmitool command with specific kernels", + "summary": "Kernel occasionally panics when running `ipmitool` command due to a bug in the ipmi message handler.\n", + "generic": "Kernel occasionally panics when running `ipmitool` due to a bug in the ipmi message handler.\n", + "reason": "

    This host is running kernel 3.10.0-327.el7 with the IPMI management tool installed.\nKernel panics can occur when running ipmitool.

    \n", + "type": null, + "more_info": "For how to upgrade the kernel to a specific version, refer to [How do I upgrade the kernel to a particular version manually?](https://access.redhat.com/solutions/161803).\n", + "active": true, + "node_id": "2690791", + "category": "Stability", + "retired": false, + "reboot_required": true, + "publish_date": null, + "rec_impact": 3, + "rec_likelihood": 1, + "resolution": "

    Red Hat recommends that you complete the following steps to fix this issue:

    \n
      \n\n
    1. Upgrade kernel to the version 3.10.0-327.36.1.el7 or later:
    2. \n\n\n# yum update kernel\n\n
    3. Restart the host with the new kernel.
    4. \n\n# reboot\n\n
    \n" + }, + "maintenance_actions": [{ + "done": false, + "id": 305285, + "maintenance_plan": { + "maintenance_id": 29315, + "name": "RHEL Demo Infrastructure", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }, { + "done": false, + "id": 310145, + "maintenance_plan": { + "maintenance_id": 29335, + "name": "RHEL Demo All Systems", + "description": null, + "start": null, + "end": null, + "created_by": "$READACTED$", + "silenced": false, + "hidden": false, + "suggestion": null, + "remote_branch": null, + "allow_reboot": true + } + }] + }] +} diff --git a/awx/main/tests/data/insights.py b/awx/main/tests/data/insights.py new file mode 100644 index 0000000000..325dff7ba8 --- /dev/null +++ b/awx/main/tests/data/insights.py @@ -0,0 +1,9 @@ +import json +import os + + +dir_path = os.path.dirname(os.path.realpath(__file__)) + +with open(os.path.join(dir_path, 'insights.json')) as data_file: + TEST_INSIGHTS_PLANS = json.loads(data_file.read()) + diff --git a/awx/main/tests/unit/utils/test_insights.py b/awx/main/tests/unit/utils/test_insights.py new file mode 100644 index 0000000000..fe160e666f --- /dev/null +++ b/awx/main/tests/unit/utils/test_insights.py @@ -0,0 +1,24 @@ +# Copyright (c) 2017 Ansible Tower by Red Hat +# All Rights Reserved. + + +from awx.main.utils.insights import filter_insights_api_response +from awx.main.tests.data.insights import TEST_INSIGHTS_PLANS + + +def test_filter_insights_api_response(): + actual = filter_insights_api_response(TEST_INSIGHTS_PLANS) + + assert actual['last_check_in'] == '2017-07-21T07:07:29.000Z' + assert len(actual['reports']) == 9 + assert actual['reports'][0]['maintenance_actions'][0]['maintenance_plan']['name'] == "RHEL Demo Infrastructure" + assert actual['reports'][0]['maintenance_actions'][0]['maintenance_plan']['maintenance_id'] == 29315 + assert actual['reports'][0]['rule']['severity'] == 'ERROR' + assert actual['reports'][0]['rule']['description'] == 'Remote code execution vulnerability in libresolv via crafted DNS response (CVE-2015-7547)' + assert actual['reports'][0]['rule']['category'] == 'Security' + assert actual['reports'][0]['rule']['summary'] == ("A critical security flaw in the `glibc` library was found. " + "It allows an attacker to crash an application built against " + "that library or, potentially, execute arbitrary code with " + "privileges of the user running the application.") + assert actual['reports'][0]['rule']['ansible_fix'] is False + diff --git a/awx/main/utils/insights.py b/awx/main/utils/insights.py new file mode 100644 index 0000000000..5bca633b3f --- /dev/null +++ b/awx/main/utils/insights.py @@ -0,0 +1,42 @@ +# Copyright (c) 2017 Ansible Tower by Red Hat +# All Rights Reserved. + + +def filter_insights_api_response(json): + new_json = {} + ''' + 'last_check_in', + 'reports.[].rule.severity', + 'reports.[].rule.description', + 'reports.[].rule.category', + 'reports.[].rule.summary', + 'reports.[].rule.ansible_fix', + 'reports.[].maintenance_actions.[].maintenance_plan.name', + 'reports.[].maintenance_actions.[].maintenance_plan.maintenance_id', + ''' + + if 'last_check_in' in json: + new_json['last_check_in'] = json['last_check_in'] + if 'reports' in json: + new_json['reports'] = [] + for rep in json['reports']: + new_report = { + 'rule': {}, + 'maintenance_actions': [] + } + if 'rule' in rep: + for k in ['severity', 'description', 'category', 'summary', 'ansible_fix',]: + if k in rep['rule']: + new_report['rule'][k] = rep['rule'][k] + + for action in rep.get('maintenance_actions', []): + new_action = {'maintenance_plan': {}} + if 'maintenance_plan' in action: + for k in ['name', 'maintenance_id']: + if k in action['maintenance_plan']: + new_action['maintenance_plan'][k] = action['maintenance_plan'][k] + new_report['maintenance_actions'].append(new_action) + + new_json['reports'].append(new_report) + return new_json + From 323d7757dd6b371d3cfdb385bd1bd059a9f053d7 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Wed, 26 Jul 2017 12:12:43 -0400 Subject: [PATCH 108/342] Move Satellite 6 .ini configurations from env variables to source_vars --- awx/main/tasks.py | 16 +++++++++++----- awx/main/tests/unit/test_tasks.py | 3 +++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index c95ff9710c..d47a6e92f2 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1611,8 +1611,7 @@ class RunInventoryUpdate(BaseTask): ec2_opts.setdefault('route53', 'False') ec2_opts.setdefault('all_instances', 'True') ec2_opts.setdefault('all_rds_instances', 'False') - # TODO: Include this option when boto3 support comes. - #ec2_opts.setdefault('include_rds_clusters', 'False') + ec2_opts.setdefault('include_rds_clusters', 'False') ec2_opts.setdefault('rds', 'False') ec2_opts.setdefault('nested_groups', 'True') ec2_opts.setdefault('elasticache', 'False') @@ -1654,10 +1653,17 @@ class RunInventoryUpdate(BaseTask): section = 'foreman' cp.add_section(section) + group_patterns = '[]' + group_prefix = 'foreman_' foreman_opts = dict(inventory_update.source_vars_dict.items()) foreman_opts.setdefault('ssl_verify', 'False') for k, v in foreman_opts.items(): - cp.set(section, k, unicode(v)) + if k == 'satellite6_group_patterns' and isinstance(v, basestring): + group_patterns = v + elif k == 'satellite6_group_prefix' and isinstance(v, basestring): + group_prefix = v + else: + cp.set(section, k, unicode(v)) credential = inventory_update.credential if credential: @@ -1667,9 +1673,9 @@ class RunInventoryUpdate(BaseTask): section = 'ansible' cp.add_section(section) - cp.set(section, 'group_patterns', os.environ.get('SATELLITE6_GROUP_PATTERNS', [])) + cp.set(section, 'group_patterns', group_patterns) cp.set(section, 'want_facts', True) - cp.set(section, 'group_prefix', os.environ.get('SATELLITE6_GROUP_PREFIX', 'foreman_')) + cp.set(section, 'group_prefix', group_prefix) section = 'cache' cp.add_section(section) diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 8aae8dc27b..fbf8ba60dd 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -1348,6 +1348,7 @@ class TestInventoryUpdateCredentials(TestJobExecution): self.instance.credential.inputs['password'] = encrypt_field( self.instance.credential, 'password' ) + self.instance.source_vars = '{"satellite6_group_patterns": "[a,b,c]", "satellite6_group_prefix": "hey_"}' def run_pexpect_side_effect(*args, **kwargs): args, cwd, env, stdout = args @@ -1356,6 +1357,8 @@ class TestInventoryUpdateCredentials(TestJobExecution): assert config.get('foreman', 'url') == 'https://example.org' assert config.get('foreman', 'user') == 'bob' assert config.get('foreman', 'password') == 'secret' + assert config.get('ansible', 'group_patterns') == '[a,b,c]' + assert config.get('ansible', 'group_prefix') == 'hey_' return ['successful', 0] self.run_pexpect.side_effect = run_pexpect_side_effect From 5f58f500cc2ae58d8f4b8c891e9f0bd6e3dfdd5f Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Thu, 27 Jul 2017 14:49:09 -0400 Subject: [PATCH 109/342] fix color of schedule forms and bugs found in color audit --- awx/ui/client/legacy-styles/ansible-ui.less | 2 +- awx/ui/client/legacy-styles/forms.less | 2 +- .../related/sources/sources.form.js | 40 ++++++++++++++----- .../smart-inventory/smart-inventory.form.js | 2 +- .../standard-inventory/inventory.form.js | 2 +- .../src/organizations/organizations.form.js | 2 +- .../instance-groups.directive.js | 5 ++- .../instance-groups.partial.html | 6 ++- .../date-picker/date-picker.block.less | 9 +++-- .../system-tracking-container.block.less | 1 + .../job_templates/job-template.form.js | 5 ++- .../multi-credential.block.less | 8 ++-- .../multi-credential.directive.js | 3 +- .../multi-credential.partial.html | 11 +++-- .../templates/labels/labelsList.block.less | 1 + .../shared/question-definition.form.js | 2 +- 16 files changed, 65 insertions(+), 36 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index bfabb4c7ed..04c245684d 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -2145,7 +2145,7 @@ tr td button i { } .select2-container--default .select2-selection--single { - background-color: @field-secondary-bg; + background-color: @f2grey; border: 1px solid @d7grey; border-radius: 4px; } diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index fdde7e18a4..67932c9d95 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -678,7 +678,7 @@ input[type='radio']:checked:before { height: inherit; min-height: 30px; max-height: 120px; - overflow-y: scroll; + overflow-y: hidden; } .Form-variableHeightButtonGroup { diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js index b25d3a2860..302dd8135e 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js @@ -43,6 +43,9 @@ return { activeEditState: 'inventories.edit.inventory_sources.edit', detailsClick: "$state.go('inventories.edit.inventory_sources.edit')", well: false, + subFormTitles: { + sourceSubForm: i18n._('Source Details'), + }, fields: { name: { label: i18n._('Name'), @@ -64,7 +67,8 @@ return { ngOptions: 'source.label for source in source_type_options track by source.value', ngChange: 'sourceChange(source)', ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - ngModel: 'source' + ngModel: 'source', + hasSubForm: true }, credential: { label: i18n._('Credential'), @@ -79,6 +83,7 @@ return { reqExpression: "cloudCredentialRequired", init: "false" }, + subForm: 'sourceSubForm', ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', watchBasePath: "credentialBasePath" }, @@ -98,7 +103,8 @@ return { init: "false" }, ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - watchBasePath: "projectBasePath" + watchBasePath: "projectBasePath", + subForm: 'sourceSubForm' }, inventory_file: { label: i18n._('Inventory File'), @@ -116,7 +122,8 @@ return { dataTitle: i18n._('Inventory File'), dataPlacement: 'right', dataContainer: "body", - includeInventoryFileNotFoundError: true + includeInventoryFileNotFoundError: true, + subForm: 'sourceSubForm' }, source_regions: { label: i18n._('Regions'), @@ -129,7 +136,8 @@ return { awPopOver: "

    " + i18n._("Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, or choose") + "" + i18n._("All") + " " + i18n._("to include all regions. Only Hosts associated with the selected regions will be updated.") + "

    ", dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' }, instance_filters: { label: i18n._('Instance Filters'), @@ -140,7 +148,8 @@ return { awPopOverWatch: 'instanceFilterPopOver', awPopOver: '{{ instanceFilterPopOver }}', dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' }, group_by: { label: i18n._('Only Group By'), @@ -153,7 +162,8 @@ return { awPopOverWatch: 'groupByPopOver', awPopOver: '{{ groupByPopOver }}', dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' }, inventory_script: { label : i18n._("Custom Inventory Script"), @@ -168,6 +178,7 @@ return { init: "false" }, ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' }, custom_variables: { id: 'custom_variables', @@ -188,7 +199,8 @@ return { "
    ---
    somevar: somevalue
    password: magic
    \n" + '

    View JSON examples at www.json.org

    ' + '

    View YAML examples at docs.ansible.com

    ', - dataContainer: 'body' + dataContainer: 'body', + subForm: 'sourceSubForm' }, ec2_variables: { id: 'ec2_variables', @@ -211,7 +223,8 @@ return { "
    ---
    somevar: somevalue
    password: magic
    \n" + '

    View JSON examples at www.json.org

    ' + '

    View YAML examples at docs.ansible.com

    ', - dataContainer: 'body' + dataContainer: 'body', + subForm: 'sourceSubForm' }, vmware_variables: { id: 'vmware_variables', @@ -234,7 +247,8 @@ return { "
    ---
    somevar: somevalue
    password: magic
    \n" + '

    View JSON examples at www.json.org

    ' + '

    View YAML examples at docs.ansible.com

    ', - dataContainer: 'body' + dataContainer: 'body', + subForm: 'sourceSubForm' }, openstack_variables: { id: 'openstack_variables', @@ -257,7 +271,8 @@ return { "
    ---
    somevar: somevalue
    password: magic
    \n" + '

    View JSON examples at www.json.org

    ' + '

    View YAML examples at docs.ansible.com

    ', - dataContainer: 'body' + dataContainer: 'body', + subForm: 'sourceSubForm' }, verbosity: { label: i18n._('Verbosity'), @@ -271,12 +286,14 @@ return { dataPlacement: 'right', dataContainer: "body", ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', + subForm: 'sourceSubForm' }, checkbox_group: { label: i18n._('Update Options'), type: 'checkbox_group', ngShow: "source && (source.value !== '' && source.value !== null)", class: 'Form-checkbox--stacked', + subForm: 'sourceSubForm', fields: [{ name: 'overwrite', label: i18n._('Overwrite'), @@ -345,7 +362,8 @@ return { 'and a new inventory sync will be performed.

    ', dataTitle: i18n._('Cache Timeout'), dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + subForm: 'sourceSubForm' } }, diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js index 07cf7db25c..494cb00cd6 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js @@ -78,7 +78,7 @@ export default ['i18n', 'InventoryCompletedJobsList', function(i18n, InventoryCo dataTitle: i18n._('Instance Groups'), dataPlacement: 'right', dataContainer: 'body', - control: '', + control: '', }, smartinventory_variables: { label: i18n._('Variables'), diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js index 0be972390c..c5eaf5c73f 100644 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js +++ b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js @@ -86,7 +86,7 @@ function(i18n, InventoryCompletedJobsList) { dataTitle: i18n._('Instance Groups'), dataPlacement: 'right', dataContainer: 'body', - control: '', + control: '', }, inventory_variables: { realName: 'variables', diff --git a/awx/ui/client/src/organizations/organizations.form.js b/awx/ui/client/src/organizations/organizations.form.js index 42bda9b716..2a79118d70 100644 --- a/awx/ui/client/src/organizations/organizations.form.js +++ b/awx/ui/client/src/organizations/organizations.form.js @@ -41,7 +41,7 @@ export default ['NotificationsList', 'i18n', dataTitle: i18n._('Instance Groups'), dataContainer: 'body', dataPlacement: 'right', - control: '', + control: '', } }, diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js index 2a0277c45e..cdb781b1d9 100644 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js +++ b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js @@ -3,7 +3,8 @@ export default ['templateUrl', '$compile', function(templateUrl, $compile) { return { scope: { - instanceGroups: '=' + instanceGroups: '=', + fieldIsDisabled: '=' }, restrict: 'E', templateUrl: templateUrl('shared/instance-groups-multiselect/instance-groups'), @@ -15,4 +16,4 @@ export default ['templateUrl', '$compile', } }; } -]; \ No newline at end of file +]; diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html index 46eaf47b8c..bb237b77c1 100644 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html +++ b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html @@ -1,10 +1,12 @@
    - - +
    diff --git a/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less b/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less index 5d880c3bc6..670ba31c8f 100644 --- a/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less +++ b/awx/ui/client/src/system-tracking/date-picker/date-picker.block.less @@ -7,25 +7,26 @@ padding: 6px 12px; font-size: 14px; border-radius: 4px 0 0 4px; - border: 1px solid #ccc; + border: 1px solid @b7grey; border-right: 0; background-color: @default-bg; } &-icon:hover { - background-color: @default-border; + background-color: @f6grey; } &-icon:focus, &-icon:active { - background-color: #ccc; + background-color: @f6grey; } &-input { flex: 1 0 auto; border-radius: 0 4px 4px 0; - border: 1px solid #ccc; + border: 1px solid @b7grey; padding: 6px 12px; + background-color: @f2grey; } &-input:focus, diff --git a/awx/ui/client/src/system-tracking/system-tracking-container.block.less b/awx/ui/client/src/system-tracking/system-tracking-container.block.less index 6688d2f255..62d5c1ee05 100644 --- a/awx/ui/client/src/system-tracking/system-tracking-container.block.less +++ b/awx/ui/client/src/system-tracking/system-tracking-container.block.less @@ -18,6 +18,7 @@ .DatePicker-input { width: 100%; flex: none; + background: @f2grey; } .DatePicker-icon { diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js index 1892f474e3..daa43e44b4 100644 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ b/awx/ui/client/src/templates/job_templates/job-template.form.js @@ -135,7 +135,8 @@ function(NotificationsList, CompletedJobsList, i18n) { prompt="ask_credential_on_launch" selected-credentials="selectedCredentials" credential-not-present="credentialNotPresent" - credentials-to-post="credentialsToPost"> + credentials-to-post="credentialsToPost" + field-is-disabled="!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)"> `, required: true, awPopOver: "

    " + i18n._("Select credentials that allow {{BRAND_NAME}} to access the nodes this job will be ran against. You can only select one credential of each type.

    You must select either a machine (SSH) credential or \"Prompt on launch\". \"Prompt on launch\" requires you to select a machine credential at run time.

    If you select credentials AND check the \"Prompt on launch\" box, you make the selected credentials the defaults that can be updated at run time.") + "

    ", @@ -208,7 +209,7 @@ function(NotificationsList, CompletedJobsList, i18n) { dataTitle: i18n._('Instance Groups'), dataContainer: 'body', dataPlacement: 'right', - control: '', + control: '', }, job_tags: { label: i18n._('Job Tags'), diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less index 694e298a0e..8553d4ce30 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less @@ -41,13 +41,14 @@ padding: 2px 10px; margin: 4px 0px; font-size: 12px; - color: @default-interface-txt; - background-color: @default-bg; margin-right: 10px; max-width: 100%; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; + background-color: @default-link; + color: @default-bg; + padding-left: 15px; } .MultiCredential-tag--deletable { @@ -56,9 +57,8 @@ border-bottom-left-radius: 0px; border-right: 0; max-width: ~"calc(100% - 23px)"; - background-color: @default-link; - color: @default-bg; margin-right: 10px; + padding-left: 10px; } .MultiCredential-deleteContainer { diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js index a2cf5ea85d..1e0d477fa1 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js @@ -12,7 +12,8 @@ export default ['templateUrl', '$compile', selectedCredentials: '=', prompt: '=', credentialNotPresent: '=', - credentialsToPost: '=' + credentialsToPost: '=', + fieldIsDisabled: '=' }, restrict: 'E', templateUrl: templateUrl('templates/job_templates/multi-credential/multi-credential'), diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html index b52f7da8fd..e08f7432eb 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html @@ -3,7 +3,8 @@ @@ -13,6 +14,7 @@ 'ng-invalid': credentialNotPresent, 'ng-dirty': fieldDirty }" + ng-disabled="fieldIsDisabled" style="padding: 4px 6px;">
    @@ -20,12 +22,13 @@
    + ng-click="removeCredential(tag.id)" + ng-hide="fieldIsDisabled">
    -
    +
    {{ tag.kind }} diff --git a/awx/ui/client/src/templates/labels/labelsList.block.less b/awx/ui/client/src/templates/labels/labelsList.block.less index 8cd9523c1c..d503a354fa 100644 --- a/awx/ui/client/src/templates/labels/labelsList.block.less +++ b/awx/ui/client/src/templates/labels/labelsList.block.less @@ -88,6 +88,7 @@ .LabelList-lookupTags { display: flex; padding: 0 12px; + overflow-y: hidden; } .LabelList-lookupTags--disabled { diff --git a/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js b/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js index f57a5fe019..70664e99e7 100644 --- a/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js +++ b/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js @@ -302,7 +302,7 @@ export default }, buttons: { question_cancel : { - label: 'Cancel', + label: 'Clear', 'class' : 'btn btn-default Form-cancelButton', ngClick: 'generateAddQuestionForm()', ngDisabled: 'survey_question_form.$pristine' From e8bd477f1ea1f86249d56e4e925cd76ec16ac8e5 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Wed, 26 Jul 2017 15:06:04 -0400 Subject: [PATCH 110/342] Make up default values for tower configurations --- awx/conf/views.py | 9 ++++++++- awx/main/conf.py | 3 +++ awx/sso/conf.py | 12 ++++++++++++ docs/tower_configuration.md | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/awx/conf/views.py b/awx/conf/views.py index e476f5b0cf..70110453f5 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -11,7 +11,7 @@ from django.http import Http404 from django.utils.translation import ugettext_lazy as _ # Django REST Framework -from rest_framework.exceptions import PermissionDenied +from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.response import Response from rest_framework import serializers from rest_framework import status @@ -180,6 +180,13 @@ class SettingLoggingTest(GenericAPIView): obj = type('Settings', (object,), defaults)() serializer = self.get_serializer(obj, data=request.data) serializer.is_valid(raise_exception=True) + # Special validation specific to logging test. + errors = {} + for key in ['LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_HOST']: + if not request.data.get(key, ''): + errors[key] = 'This field is required.' + if errors: + raise ValidationError(errors) if request.data.get('LOG_AGGREGATOR_PASSWORD', '').startswith('$encrypted$'): serializer.validated_data['LOG_AGGREGATOR_PASSWORD'] = getattr( diff --git a/awx/main/conf.py b/awx/main/conf.py index 01fdb902a0..7c0f23e5e5 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -329,6 +329,7 @@ register( 'LOG_AGGREGATOR_HOST', field_class=fields.CharField, allow_null=True, + default=None, label=_('Logging Aggregator'), help_text=_('Hostname/IP where external logs will be sent to.'), category=_('Logging'), @@ -338,6 +339,7 @@ register( 'LOG_AGGREGATOR_PORT', field_class=fields.IntegerField, allow_null=True, + default=None, label=_('Logging Aggregator Port'), help_text=_('Port on Logging Aggregator to send logs to (if required and not' ' provided in Logging Aggregator).'), @@ -350,6 +352,7 @@ register( field_class=fields.ChoiceField, choices=['logstash', 'splunk', 'loggly', 'sumologic', 'other'], allow_null=True, + default=None, label=_('Logging Aggregator Type'), help_text=_('Format messages for the chosen log aggregator.'), category=_('Logging'), diff --git a/awx/sso/conf.py b/awx/sso/conf.py index b7bb4734a6..c5b4bfe25d 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -535,6 +535,7 @@ register( 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', field_class=fields.CharField, allow_blank=True, + default='', label=_('Google OAuth2 Key'), help_text=_('The OAuth2 key from your web application at https://console.developers.google.com/.'), category=_('Google OAuth2'), @@ -546,6 +547,7 @@ register( 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET', field_class=fields.CharField, allow_blank=True, + default='', label=_('Google OAuth2 Secret'), help_text=_('The OAuth2 secret from your web application at https://console.developers.google.com/.'), category=_('Google OAuth2'), @@ -627,6 +629,7 @@ register( 'SOCIAL_AUTH_GITHUB_KEY', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub OAuth2 Key'), help_text=_('The OAuth2 key (Client ID) from your GitHub developer application.'), category=_('GitHub OAuth2'), @@ -637,6 +640,7 @@ register( 'SOCIAL_AUTH_GITHUB_SECRET', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub OAuth2 Secret'), help_text=_('The OAuth2 secret (Client Secret) from your GitHub developer application.'), category=_('GitHub OAuth2'), @@ -691,6 +695,7 @@ register( 'SOCIAL_AUTH_GITHUB_ORG_KEY', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Organization OAuth2 Key'), help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), category=_('GitHub Organization OAuth2'), @@ -701,6 +706,7 @@ register( 'SOCIAL_AUTH_GITHUB_ORG_SECRET', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Organization OAuth2 Secret'), help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), category=_('GitHub Organization OAuth2'), @@ -712,6 +718,7 @@ register( 'SOCIAL_AUTH_GITHUB_ORG_NAME', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Organization Name'), help_text=_('The name of your GitHub organization, as used in your ' 'organization\'s URL: https://github.com//.'), @@ -766,6 +773,7 @@ register( 'SOCIAL_AUTH_GITHUB_TEAM_KEY', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Team OAuth2 Key'), help_text=_('The OAuth2 key (Client ID) from your GitHub organization application.'), category=_('GitHub Team OAuth2'), @@ -776,6 +784,7 @@ register( 'SOCIAL_AUTH_GITHUB_TEAM_SECRET', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Team OAuth2 Secret'), help_text=_('The OAuth2 secret (Client Secret) from your GitHub organization application.'), category=_('GitHub Team OAuth2'), @@ -787,6 +796,7 @@ register( 'SOCIAL_AUTH_GITHUB_TEAM_ID', field_class=fields.CharField, allow_blank=True, + default='', label=_('GitHub Team ID'), help_text=_('Find the numeric team ID using the Github API: ' 'http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/.'), @@ -841,6 +851,7 @@ register( 'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY', field_class=fields.CharField, allow_blank=True, + default='', label=_('Azure AD OAuth2 Key'), help_text=_('The OAuth2 key (Client ID) from your Azure AD application.'), category=_('Azure AD OAuth2'), @@ -851,6 +862,7 @@ register( 'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET', field_class=fields.CharField, allow_blank=True, + default='', label=_('Azure AD OAuth2 Secret'), help_text=_('The OAuth2 secret (Client Secret) from your Azure AD application.'), category=_('Azure AD OAuth2'), diff --git a/docs/tower_configuration.md b/docs/tower_configuration.md index ca5fdfee8c..acdbbfbbdd 100644 --- a/docs/tower_configuration.md +++ b/docs/tower_configuration.md @@ -84,6 +84,8 @@ Here is the details of each argument: During Tower bootstrapping, All settings registered in `conf.py` modules of Tower Django apps will be loaded (registered). The set of Tower configuration settings will form a new top-level of `django.conf.settings` object. Later all Tower configuration settings will be available as attributes of it, just like normal Django settings. Note Tower configuration settings take higher priority over normal settings, meaning if a setting `FOOBAR` is both defined in a settings file and registered in a `conf.py`, the registered attribute will be used over the defined attribute every time. +Note when registering new configurations, it is desired to provide a default value if it is possible to do so, as Tower configuration UI has a 'revert all' functionality that revert all settings to it's default value. + Starting from 3.2, Tower configuration supports category-specific validation functions. They should also be defined under `conf.py` in the form ```python def custom_validate(serializer, attrs): From b69321a91ef0d751a631da1df0a43f8fd6839d39 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Wed, 26 Jul 2017 13:53:33 -0400 Subject: [PATCH 111/342] Allow parsing null JSON data --- awx/api/parsers.py | 4 +++- awx/main/tests/unit/api/test_parsers.py | 29 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 awx/main/tests/unit/api/test_parsers.py diff --git a/awx/api/parsers.py b/awx/api/parsers.py index 468962976c..8115763ae9 100644 --- a/awx/api/parsers.py +++ b/awx/api/parsers.py @@ -57,8 +57,10 @@ class JSONParser(parsers.JSONParser): try: data = stream.read().decode(encoding) + if not data: + return {} obj = json.loads(data, object_pairs_hook=OrderedDict) - if not isinstance(obj, dict): + if not isinstance(obj, dict) and obj is not None: raise ParseError(_('JSON parse error - not a JSON object')) return obj except ValueError as exc: diff --git a/awx/main/tests/unit/api/test_parsers.py b/awx/main/tests/unit/api/test_parsers.py new file mode 100644 index 0000000000..7b3ee6db6b --- /dev/null +++ b/awx/main/tests/unit/api/test_parsers.py @@ -0,0 +1,29 @@ +import pytest +import StringIO + +# AWX +from awx.api.parsers import JSONParser + +# Django REST Framework +from rest_framework.exceptions import ParseError + + +@pytest.mark.parametrize( + 'input_, output', [ + ('{"foo": "bar"}', {'foo': 'bar'}), + ('null', None), + ('', {}), + ] +) +def test_jsonparser_valid_input(input_, output): + input_stream = StringIO.StringIO(input_) + assert JSONParser().parse(input_stream) == output + input_stream.close() + + +@pytest.mark.parametrize('invalid_input', ['1', '"foobar"', '3.14', '{"foo": "bar",}']) +def test_json_parser_invalid_input(invalid_input): + input_stream = StringIO.StringIO(invalid_input) + with pytest.raises(ParseError): + JSONParser().parse(input_stream) + input_stream.close() From c0f01e671b2a80352aff4f3b0874027ef084885c Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Thu, 27 Jul 2017 15:31:39 -0400 Subject: [PATCH 112/342] Fix UX items related to Notifications --- awx/ui/client/src/app.js | 3 ++- .../src/notifications/add/add.controller.js | 12 ++++++++++ .../src/notifications/edit/edit.controller.js | 14 +++++++++++- .../notificationTemplates.form.js | 2 +- .../notifications/notifications.block.less | 22 +++++++++++++++++++ awx/ui/client/src/shared/form-generator.js | 1 + 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 8d3c38e6e7..520c9bf7e1 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -151,7 +151,8 @@ var awApp = angular.module('awApp', [ .config(['ngToastProvider', function(ngToastProvider) { ngToastProvider.configure({ animation: 'slide', - dismissOnTimeout: true, + dismissOnTimeout: false, + dismissButton: true, timeout: 4000 }); }]) diff --git a/awx/ui/client/src/notifications/add/add.controller.js b/awx/ui/client/src/notifications/add/add.controller.js index 03592cc48f..57d02a66f7 100644 --- a/awx/ui/client/src/notifications/add/add.controller.js +++ b/awx/ui/client/src/notifications/add/add.controller.js @@ -134,10 +134,22 @@ export default ['Rest', 'Wait', 'NotificationsFormObject', $scope.emailOptionsChange = function () { if ($scope.email_options === 'use_ssl') { + if ($scope.use_ssl) { + $scope.email_options = null; + $scope.use_ssl = false; + return; + } + $scope.use_ssl = true; $scope.use_tls = false; } else if ($scope.email_options === 'use_tls') { + if ($scope.use_tls) { + $scope.email_options = null; + $scope.use_tls = false; + return; + } + $scope.use_ssl = false; $scope.use_tls = true; } diff --git a/awx/ui/client/src/notifications/edit/edit.controller.js b/awx/ui/client/src/notifications/edit/edit.controller.js index 79f23b23ea..5b49665d84 100644 --- a/awx/ui/client/src/notifications/edit/edit.controller.js +++ b/awx/ui/client/src/notifications/edit/edit.controller.js @@ -205,10 +205,22 @@ export default ['Rest', 'Wait', $scope.emailOptionsChange = function () { if ($scope.email_options === 'use_ssl') { + if ($scope.use_ssl) { + $scope.email_options = null; + $scope.use_ssl = false; + return; + } + $scope.use_ssl = true; $scope.use_tls = false; } else if ($scope.email_options === 'use_tls') { + if ($scope.use_tls) { + $scope.email_options = null; + $scope.use_tls = false; + return; + } + $scope.use_ssl = false; $scope.use_tls = true; } @@ -263,7 +275,7 @@ export default ['Rest', 'Wait', Rest.setUrl(url + id + '/'); Rest.put(params) .success(function() { - $state.go($state.current, null, { reload: true }); + $state.go('notifications', {}, { reload: true }); Wait('stop'); }) .error(function(data, status) { diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js index dcec05ad78..eaf79f3c9d 100644 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ b/awx/ui/client/src/notifications/notificationTemplates.form.js @@ -398,7 +398,7 @@ export default ['i18n', function(i18n) { type: 'radio_group', subForm: 'typeSubForm', ngShow: "notification_type.value == 'email'", - ngChange: "emailOptionsChange()", + ngClick: "emailOptionsChange()", ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', options: [{ value: 'use_tls', diff --git a/awx/ui/client/src/notifications/notifications.block.less b/awx/ui/client/src/notifications/notifications.block.less index 27ad2bbc56..cfa5104524 100644 --- a/awx/ui/client/src/notifications/notifications.block.less +++ b/awx/ui/client/src/notifications/notifications.block.less @@ -25,3 +25,25 @@ .NotificationsForm-radioButtons{ display:block!important; } + +#notification_templates_table .name-column { + padding-left: 10px; +} + +.notificationsList { + margin-top: 0; +} + +.ng-toast { + .alert { + margin: 0; + } + .close { + position: absolute; + right: 5px; + top: 0px; + color: white; + opacity: 1; + text-shadow: none; + } +} \ No newline at end of file diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index 771af06646..b332dc0f27 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1287,6 +1287,7 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "value=\"" + field.options[i].value + "\" "; html += "ng-model=\"" + fld + "\" "; html += (field.ngChange) ? this.attr(field, 'ngChange') : ""; + html += (field.ngClick) ? this.attr(field, 'ngClick') : ""; html += (field.ngDisabled) ? `ng-disabled="${field.ngDisabled}"` : ""; html += (field.readonly) ? "disabled " : ""; html += (field.required) ? "required " : ""; From 1112557c791735e15ed5de6071efdda1df290738 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 25 Jul 2017 15:35:44 -0400 Subject: [PATCH 113/342] set capacity to 0 if instance has not checked in lately --- awx/main/isolated/isolated_manager.py | 18 +++++-- .../management/commands/register_instance.py | 2 +- awx/main/models/ha.py | 10 ++++ awx/main/tasks.py | 48 ++++++++++++++----- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/awx/main/isolated/isolated_manager.py b/awx/main/isolated/isolated_manager.py index 1ac035ad4b..9627d1fe8d 100644 --- a/awx/main/isolated/isolated_manager.py +++ b/awx/main/isolated/isolated_manager.py @@ -406,15 +406,25 @@ class IsolatedManager(object): try: task_result = result['plays'][0]['tasks'][0]['hosts'][instance.hostname] except (KeyError, IndexError): - logger.exception('Failed to read status from isolated instance {}.'.format(instance.hostname)) - continue + task_result = {} if 'capacity' in task_result: instance.version = task_result['version'] + if instance.capacity == 0 and task_result['capacity']: + logger.warning('Isolated instance {} has re-joined.'.format(instance.hostname)) instance.capacity = int(task_result['capacity']) instance.save(update_fields=['capacity', 'version', 'modified']) + elif instance.capacity == 0: + logger.debug('Isolated instance {} previously marked as lost, could not re-join.'.format( + instance.hostname)) else: - logger.warning('Could not update capacity of {}, msg={}'.format( - instance.hostname, task_result.get('msg', 'unknown failure'))) + logger.warning('Could not update status of isolated instance {}, msg={}'.format( + instance.hostname, task_result.get('msg', 'unknown failure') + )) + if instance.is_lost(isolated=True): + instance.capacity = 0 + instance.save(update_fields=['capacity']) + logger.error('Isolated instance {} last checked in at {}, marked as lost.'.format( + instance.hostname, instance.modified)) @staticmethod def wrap_stdout_handle(instance, private_data_dir, stdout_handle, event_data_key='job_id'): diff --git a/awx/main/management/commands/register_instance.py b/awx/main/management/commands/register_instance.py index 0a14206ef4..9cf28a95b8 100644 --- a/awx/main/management/commands/register_instance.py +++ b/awx/main/management/commands/register_instance.py @@ -30,7 +30,7 @@ class Command(BaseCommand): with advisory_lock('instance_registration_%s' % hostname): instance = Instance.objects.filter(hostname=hostname) if instance.exists(): - print("Instance already registered {}".format(instance[0])) + print("Instance already registered {}".format(instance[0].hostname)) return instance = Instance(uuid=self.uuid, hostname=hostname) instance.save() diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index 2790f89ca6..509e37b4ac 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -5,6 +5,8 @@ from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ +from django.conf import settings +from django.utils.timezone import now, timedelta from solo.models import SingletonModel @@ -53,6 +55,14 @@ class Instance(models.Model): # NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing return "awx" + def is_lost(self, ref_time=None, isolated=False): + if ref_time is None: + ref_time = now() + grace_period = 120 + if isolated: + grace_period = settings.AWX_ISOLATED_PERIODIC_CHECK * 2 + return self.modified < ref_time - timedelta(seconds=grace_period) + class InstanceGroup(models.Model): """A model representing a Queue/Group of AWX Instances.""" diff --git a/awx/main/tasks.py b/awx/main/tasks.py index c95ff9710c..b7c9644a89 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -33,7 +33,7 @@ from celery.signals import celeryd_init, worker_process_init # Django from django.conf import settings -from django.db import transaction, DatabaseError, IntegrityError +from django.db import transaction, DatabaseError, IntegrityError, OperationalError from django.utils.timezone import now, timedelta from django.utils.encoding import smart_str from django.core.mail import send_mail @@ -184,32 +184,54 @@ def purge_old_stdout_files(self): def cluster_node_heartbeat(self): logger.debug("Cluster node heartbeat task.") nowtime = now() - inst = Instance.objects.filter(hostname=settings.CLUSTER_HOST_ID) - if inst.exists(): - inst = inst[0] - inst.capacity = get_system_task_capacity() - inst.version = awx_application_version - inst.save() + instance_list = list(Instance.objects.filter(rampart_groups__controller__isnull=True).distinct()) + this_inst = None + lost_instances = [] + for inst in list(instance_list): + if inst.hostname == settings.CLUSTER_HOST_ID: + this_inst = inst + instance_list.remove(inst) + elif inst.is_lost(ref_time=nowtime): + lost_instances.append(inst) + instance_list.remove(inst) + if this_inst: + startup_event = this_inst.is_lost(ref_time=nowtime) + if this_inst.capacity == 0: + logger.warning('Rejoining the cluster as instance {}.'.format(this_inst.hostname)) + this_inst.capacity = get_system_task_capacity() + this_inst.version = awx_application_version + this_inst.save(update_fields=['capacity', 'version', 'modified']) + if startup_event: + return else: raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID)) - recent_inst = Instance.objects.filter(modified__gt=nowtime - timedelta(seconds=70)).exclude(hostname=settings.CLUSTER_HOST_ID) # IFF any node has a greater version than we do, then we'll shutdown services - for other_inst in recent_inst: + for other_inst in instance_list: if other_inst.version == "": continue if Version(other_inst.version.split('-', 1)[0]) > Version(awx_application_version) and not settings.DEBUG: logger.error("Host {} reports version {}, but this node {} is at {}, shutting down".format(other_inst.hostname, other_inst.version, - inst.hostname, - inst.version)) + this_inst.hostname, + this_inst.version)) # Set the capacity to zero to ensure no Jobs get added to this instance. # The heartbeat task will reset the capacity to the system capacity after upgrade. - inst.capacity = 0 - inst.save() + this_inst.capacity = 0 + this_inst.save(update_fields=['capacity']) stop_local_services(['uwsgi', 'celery', 'beat', 'callback', 'fact']) # We wait for the Popen call inside stop_local_services above # so the line below will rarely if ever be executed. raise RuntimeError("Shutting down.") + for other_inst in lost_instances: + if other_inst.capacity == 0: + continue + try: + other_inst.capacity = 0 + other_inst.save(update_fields=['capacity']) + logger.error("Host {} last checked in at {}, marked as lost.".format( + other_inst.hostname, other_inst.modified)) + except (IntegrityError, OperationalError): + pass # another instance is updating the lost instance @task(bind=True, base=LogErrorsTask) From 5a540a877e468e8d3693abc70851db443faa54f8 Mon Sep 17 00:00:00 2001 From: Aaron Tan Date: Thu, 27 Jul 2017 16:29:03 -0400 Subject: [PATCH 114/342] Prevent unprivileged users from deleting inventory sources --- awx/main/access.py | 3 ++- awx/main/tests/functional/test_rbac_inventory.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/awx/main/access.py b/awx/main/access.py index 91121928be..b5a81e443a 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -795,7 +795,8 @@ class InventorySourceAccess(BaseAccess): update_on_project_update=True, source='scm').exists()) def can_delete(self, obj): - if not (self.user.is_superuser or not (obj and obj.inventory and self.user.can_access(Inventory, 'admin', obj.inventory, None))): + if not self.user.is_superuser and \ + not (obj and obj.inventory and self.user.can_access(Inventory, 'admin', obj.inventory, None)): return False active_jobs_qs = InventoryUpdate.objects.filter(inventory_source=obj, status__in=ACTIVE_STATES) if active_jobs_qs.exists(): diff --git a/awx/main/tests/functional/test_rbac_inventory.py b/awx/main/tests/functional/test_rbac_inventory.py index 99937899a8..2172f90d6d 100644 --- a/awx/main/tests/functional/test_rbac_inventory.py +++ b/awx/main/tests/functional/test_rbac_inventory.py @@ -93,6 +93,20 @@ def test_inventory_update_org_admin(inventory_update, org_admin): assert access.can_delete(inventory_update) +@pytest.mark.parametrize("role_field,allowed", [ + (None, False), + ('admin_role', True), + ('update_role', False), + ('adhoc_role', False), + ('use_role', False) +]) +@pytest.mark.django_db +def test_inventory_source_delete(inventory_source, alice, role_field, allowed): + if role_field: + getattr(inventory_source.inventory, role_field).members.add(alice) + assert allowed == InventorySourceAccess(alice).can_delete(inventory_source), '{} test failed'.format(role_field) + + # See companion test in tests/functional/api/test_inventory.py::test_inventory_update_access_called @pytest.mark.parametrize("role_field,allowed", [ (None, False), From f3130e06c976b1573e2b83cbdaed1c4be6aa409f Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 27 Jul 2017 16:30:03 -0400 Subject: [PATCH 115/342] Add i18n flag file back to makefile This was accidentally deleted during the repo split --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 7542e41193..340ba2719a 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,8 @@ SETUP_TAR_CHECKSUM=$(NAME)-setup-CHECKSUM UI_DEPS_FLAG_FILE = awx/ui/.deps_built UI_RELEASE_FLAG_FILE = awx/ui/.release_built +I18N_FLAG_FILE = .i18n_built + .DEFAULT_GOAL := build .PHONY: clean clean-tmp clean-venv rebase push requirements requirements_dev \ From 1b8b6e1159768a4d93961410aba98a974e7432c3 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 27 Jul 2017 16:32:09 -0400 Subject: [PATCH 116/342] Remove default goal from makefile There is no build target anymore --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 340ba2719a..bf7e18ee4b 100644 --- a/Makefile +++ b/Makefile @@ -78,8 +78,6 @@ UI_RELEASE_FLAG_FILE = awx/ui/.release_built I18N_FLAG_FILE = .i18n_built -.DEFAULT_GOAL := build - .PHONY: clean clean-tmp clean-venv rebase push requirements requirements_dev \ develop refresh adduser migrate dbchange dbshell runserver celeryd \ receiver test test_unit test_ansible test_coverage coverage_html \ From 97b7321920d0b4efc083943900dc3612eb41a680 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Thu, 27 Jul 2017 16:46:40 -0400 Subject: [PATCH 117/342] Cleaning up Makefile Removing references to and definitions of unused targets --- Makefile | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index bf7e18ee4b..944aea9b5f 100644 --- a/Makefile +++ b/Makefile @@ -78,15 +78,12 @@ UI_RELEASE_FLAG_FILE = awx/ui/.release_built I18N_FLAG_FILE = .i18n_built -.PHONY: clean clean-tmp clean-venv rebase push requirements requirements_dev \ +.PHONY: clean clean-tmp clean-venv requirements requirements_dev \ develop refresh adduser migrate dbchange dbshell runserver celeryd \ receiver test test_unit test_ansible test_coverage coverage_html \ - test_jenkins dev_build release_build release_clean sdist rpmtar mock-rpm \ - mock-srpm rpm-sign deb deb-src debian debsign pbuilder \ - reprepro setup_tarball virtualbox-ovf virtualbox-centos-7 \ - virtualbox-centos-6 clean-bundle setup_bundle_tarball \ + dev_build release_build release_clean sdist \ ui-docker-machine ui-docker ui-release ui-devel \ - ui-test ui-deps ui-test-ci ui-test-saucelabs jlaska VERSION + ui-test ui-deps ui-test-ci ui-test-saucelabs VERSION # remove ui build artifacts clean-ui: @@ -130,14 +127,6 @@ guard-%: exit 1; \ fi -# Fetch from origin, rebase local commits on top of origin commits. -rebase: - git pull --rebase origin master - -# Push changes to origin. -push: - git push origin master - virtualenv: virtualenv_ansible virtualenv_awx virtualenv_ansible: @@ -413,10 +402,6 @@ coverage_html: test_tox: tox -v -# Run unit tests to produce output for Jenkins. -# Alias existing make target so old versions run against Jekins the same way -test_jenkins : test_coverage - # Make fake data DATA_GEN_PRESET = "" bulk_data: From bf42021a322b1528cfa030dd9b6c32cf329c52ba Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 27 Jul 2017 16:47:23 -0400 Subject: [PATCH 118/342] Add search to models w/ search on lookup input --- .../credentials/add-credentials.controller.js | 5 +- .../edit-credentials.controller.js | 19 ++-- awx/ui/client/features/credentials/index.js | 23 +++-- .../lib/components/input/base.controller.js | 24 ++++-- .../lib/components/input/lookup.directive.js | 81 ++++++++++++----- .../lib/components/input/lookup.partial.html | 2 +- awx/ui/client/lib/models/Base.js | 86 +++++++++++++++---- awx/ui/client/lib/models/Credential.js | 5 -- 8 files changed, 172 insertions(+), 73 deletions(-) diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 79fa9afd0c..e1f925a60b 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -31,10 +31,9 @@ function AddCredentialsController (models, $state, strings) { vm.form.inputs = { _get: id => { - let type = credentialType.graft(id); - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index e49dfe51ce..8c2c79626a 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -5,7 +5,6 @@ function EditCredentialsController (models, $state, $scope, strings) { let credential = models.credential; let credentialType = models.credentialType; let organization = models.organization; - let selectedCredentialType = models.selectedCredentialType; vm.mode = 'edit'; vm.strings = strings; @@ -50,21 +49,19 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.credential_type._resource = 'credential_type'; vm.form.credential_type._model = credentialType; vm.form.credential_type._route = 'credentials.edit.credentialType'; - vm.form.credential_type._value = selectedCredentialType.get('id'); - vm.form.credential_type._displayValue = selectedCredentialType.get('name'); + vm.form.credential_type._value = credentialType.get('id'); + vm.form.credential_type._displayValue = credentialType.get('name'); vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); vm.form.inputs = { _get (id) { - let type = credentialType.graft(id); - - type.mergeInputProperties(); + credentialType.mergeInputProperties(); - if (type.get('id') === credential.get('credential_type')) { - return credential.assignInputGroupValues(type.get('inputs.fields')); + if (credentialType.get('id') === credential.get('credential_type')) { + return credential.assignInputGroupValues(credentialType.get('inputs.fields')); } - return type.get('inputs.fields'); + return credentialType.get('inputs.fields'); }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', @@ -73,8 +70,8 @@ function EditCredentialsController (models, $state, $scope, strings) { vm.form.save = data => { data.user = me.getSelf().id; - credential.clearTypeInputs(); - + credential.unset('inputs'); + return credential.request('put', data); }; diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 077e9562e1..9e0662cdcd 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -7,13 +7,13 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O let id = $stateParams.credential_id; let promises = { - me: new Me('get'), - credentialType: new CredentialType('get'), - organization: new Organization('get') + me: new Me('get') }; if (!id) { promises.credential = new Credential('options'); + promises.credentialType = new CredentialType(); + promises.organization = new Organization(); return $q.all(promises) } @@ -22,10 +22,21 @@ function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, O return $q.all(promises) .then(models => { - let credentialTypeId = models.credential.get('credential_type'); - models.selectedCredentialType = models.credentialType.graft(credentialTypeId); + let typeId = models.credential.get('credential_type'); + let orgId = models.credential.get('organization'); - return models; + let dependents = { + credentialType: new CredentialType('get', typeId), + organization: new Organization('get', orgId) + }; + + return $q.all(dependents) + .then(related => { + models.credentialType = related.credentialType; + models.organization = related.organization; + + return models; + }); }); } diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js index 43a382210f..8d92711660 100644 --- a/awx/ui/client/lib/components/input/base.controller.js +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -67,16 +67,22 @@ function BaseInputController (strings) { }; }; - vm.check = () => { - let result = vm.validate(); - - if (scope.state._touched || !scope.state._required) { - scope.state._rejected = !result.isValid; - scope.state._isValid = result.isValid; - scope.state._message = result.message; - - form.check(); + vm.updateValidationState = result => { + if (!scope.state._touched && scope.state._required) { + return; } + + scope.state._rejected = !result.isValid; + scope.state._isValid = result.isValid; + scope.state._message = result.message; + + form.check(); + }; + + vm.check = result => { + result = result || vm.validate(); + + vm.updateValidationState(result); }; vm.toggleRevertReplace = () => { diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 362547a9d9..792709035b 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -1,3 +1,6 @@ +const DEFAULT_DEBOUNCE = 250; +const DEFAULT_KEY = 'name'; + function atInputLookupLink (scope, element, attrs, controllers) { let formController = controllers[0]; let inputController = controllers[1]; @@ -9,28 +12,40 @@ function atInputLookupLink (scope, element, attrs, controllers) { inputController.init(scope, element, formController); } -function AtInputLookupController (baseInputController, $state, $stateParams) { +function AtInputLookupController (baseInputController, $q, $state, $stateParams) { let vm = this || {}; let scope; + let model; + let search; vm.init = (_scope_, element, form) => { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; + model = scope.state._model; + scope.state._debounce = scope.state._debounce || DEFAULT_DEBOUNCE; + search = scope.state._search || { + key: DEFAULT_KEY, + config: { + unique: true + } + }; scope.$watch(scope.state._resource, vm.watchResource); - scope.state._validate = vm.checkOnInput; vm.check(); }; vm.watchResource = () => { + if (!scope[scope.state._resource]) { + return; + } + if (scope[scope.state._resource] !== scope.state._value) { - scope.state._value = scope[scope.state._resource]; scope.state._displayValue = scope[`${scope.state._resource}_name`]; - vm.check(); + vm.search(); } }; @@ -49,32 +64,54 @@ function AtInputLookupController (baseInputController, $state, $stateParams) { scope[scope.state._resource] = undefined; }; - vm.checkOnInput = () => { - if (!scope.state._touched) { - return { isValid: true }; + vm.searchAfterDebounce = () => { + vm.isDebouncing = true; + + vm.debounce = window.setTimeout(() => { + vm.isDebouncing = false; + vm.search(); + }, scope.state._debounce); + }; + + vm.resetDebounce = () => { + clearTimeout(vm.debounce); + vm.searchAfterDebounce(); + }; + + vm.search = () => { + return model.search({ [search.key]: scope.state._displayValue }, search.config) + .then(found => { + if (!found) { + return vm.reset(); + } + + scope[scope.state._resource] = model.get('id'); + scope.state._value = model.get('id'); + scope.state._displayValue = model.get('name'); + }) + .catch(() => vm.reset()) + .finally(() => { + let isValid = scope.state._value !== undefined; + let message = isValid ? '' : vm.strings.get('lookup.NOT_FOUND'); + + vm.check({ isValid, message }); + }); + }; + + vm.searchOnInput = () => { + if (vm.isDebouncing) { + return vm.resetDebounce(); } - let result = scope.state._model.match('get', 'name', scope.state._displayValue); + scope.state._touched = true; - if (result) { - scope[scope.state._resource] = result.id; - scope.state._value = result.id; - scope.state._displayValue = result.name; - - return { isValid: true }; - } - - vm.reset(); - - return { - isValid: false, - message: vm.strings.get('lookup.NOT_FOUND') - }; + vm.searchAfterDebounce(); }; } AtInputLookupController.$inject = [ 'BaseInputController', + '$q', '$state', '$stateParams' ]; diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html index 39900afcc1..21ebf03b5d 100644 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ b/awx/ui/client/lib/components/input/lookup.partial.html @@ -16,7 +16,7 @@ ng-model="state._displayValue" ng-attr-tabindex="{{ tab || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}" - ng-change="vm.check()" + ng-change="vm.searchOnInput()" ng-disabled="state._disabled || form.disabled" />
    diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 0a4cef54fd..fea1fbd413 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -11,16 +11,41 @@ function request (method, resource) { return this.http[method](resource); } -function httpGet (resource) { - this.method = this.method || 'GET'; - +function search (params, config) { let req = { - method: this.method, + method: 'GET', + url: this.path, + params + }; + + return $http(req) + .then(res => { + if (!res.data.count) { + return false; + } + + if (config.unique) { + if (res.data.count !== 1) { + return false; + } + + this.model.GET = res.data.results[0]; + } else { + this.model.GET = res.data; + } + + return true; + }); +} + +function httpGet (resource) { + let req = { + method: 'GET', url: this.path }; if (typeof resource === 'object') { - this.model[this.method] = resource; + this.model.GET = resource; return $q.resolve(); } else if (resource) { @@ -43,9 +68,9 @@ function httpPost (data) { }; return $http(req).then(res => { - this.model.GET = res.data; + this.model.GET = res.data; - return res; + return res; }); } @@ -87,6 +112,28 @@ function get (keys) { return this.find('get', keys); } +function unset (method, keys) { + if (!keys) { + keys = method; + method = 'GET'; + } + + method = method.toUpperCase(); + keys = keys.split('.'); + + if (!keys.length) { + delete this.model[method]; + } else if (keys.length === 1) { + delete this.model[method][keys[0]]; + } else { + let property = keys.splice(-1); + keys = keys.join('.'); + + let model = this.find(method, keys) + delete model[property]; + } +} + function set (method, keys, value) { if (!value) { value = keys; @@ -189,6 +236,10 @@ function graft (id) { } function create (method, resource, graft) { + if (!method) { + return this; + } + this.promise = this.request(method, resource); if (graft) { @@ -200,21 +251,24 @@ function create (method, resource, graft) { } function BaseModel (path) { - this.model = {}; - this.get = get; - this.set = set; - this.options = options; - this.find = find; - this.match = match; - this.normalizePath = normalizePath; - this.graft = graft; this.create = create; + this.find = find; + this.get = get; + this.graft = graft; + this.match = match; + this.model = {}; + this.normalizePath = normalizePath; + this.options = options; this.request = request; + this.search = search; + this.set = set; + this.unset = unset; + this.http = { get: httpGet.bind(this), options: httpOptions.bind(this), post: httpPost.bind(this), - put: httpPut.bind(this) + put: httpPut.bind(this), }; this.path = this.normalizePath(path); diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 9937ebe281..0599513b84 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -33,17 +33,12 @@ function assignInputGroupValues (inputs) { }); } -function clearTypeInputs () { - delete this.model.GET.inputs; -} - function CredentialModel (method, resource, graft) { BaseModel.call(this, 'credentials'); this.Constructor = CredentialModel; this.createFormSchema = createFormSchema.bind(this); this.assignInputGroupValues = assignInputGroupValues.bind(this); - this.clearTypeInputs = clearTypeInputs.bind(this); return this.create(method, resource, graft); } From 8f9b7597ae5531caa066c7931f312eef9ec08c4e Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Thu, 27 Jul 2017 16:56:30 -0400 Subject: [PATCH 119/342] fix ng-class syntax --- .../multi-credential/multi-credential.partial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html index e08f7432eb..6d51ae9ba1 100644 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html +++ b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html @@ -28,7 +28,7 @@
    + ng-class="{'MultiCredential-tag--deletable': !fieldIsDisabled}"> {{ tag.kind }} From 6239df6778ae89e7ee2274894fe1f41effb3330e Mon Sep 17 00:00:00 2001 From: gconsidine Date: Thu, 27 Jul 2017 18:07:06 -0400 Subject: [PATCH 120/342] Add read-only credential form depending on access --- .../client/features/credentials/_index.less | 2 +- .../credentials/add-credentials.controller.js | 2 + .../edit-credentials.controller.js | 4 +- .../lib/components/form/action.partial.html | 2 +- .../lib/components/form/form.directive.js | 2 + .../lib/components/input/lookup.directive.js | 2 + awx/ui/client/lib/models/Base.js | 46 +++++++++++++++++++ awx/ui/client/lib/models/Credential.js | 11 +++-- 8 files changed, 64 insertions(+), 7 deletions(-) diff --git a/awx/ui/client/features/credentials/_index.less b/awx/ui/client/features/credentials/_index.less index 4f4f37cd91..87f746b2c3 100644 --- a/awx/ui/client/features/credentials/_index.less +++ b/awx/ui/client/features/credentials/_index.less @@ -1,3 +1,3 @@ .at-CredentialsPermissions { - margin-top: 20px; + margin-top: 50px; } diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index e1f925a60b..c991f62b0f 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -19,6 +19,8 @@ function AddCredentialsController (models, $state, strings) { omit: ['user', 'team', 'inputs'] }); + vm.form.disabled = !credential.isCreatable(); + vm.form.organization._resource = 'organization'; vm.form.organization._route = 'credentials.add.organization'; vm.form.organization._model = organization; diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 8c2c79626a..602972ff73 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -35,10 +35,12 @@ function EditCredentialsController (models, $state, $scope, strings) { // Only exists for permissions compatibility $scope.credential_obj = credential.get(); - vm.form = credential.createFormSchema('put', { + vm.form = credential.createFormSchema({ omit: ['user', 'team', 'inputs'] }); + vm.form.disabled = !credential.isEditable(); + vm.form.organization._resource = 'organization'; vm.form.organization._model = organization; vm.form.organization._route = 'credentials.edit.organization'; diff --git a/awx/ui/client/lib/components/form/action.partial.html b/awx/ui/client/lib/components/form/action.partial.html index 8affd3a414..245c649de1 100644 --- a/awx/ui/client/lib/components/form/action.partial.html +++ b/awx/ui/client/lib/components/form/action.partial.html @@ -1,5 +1,5 @@ diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index 7d7aa2e30d..e8b80898aa 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -27,6 +27,8 @@ function AtFormController (eventService, strings) { form = _form_; modal = scope[scope.ns].modal; + vm.state.disabled = scope.state.disabled; + vm.setListeners(); }; diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 792709035b..58ab894737 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -79,6 +79,8 @@ function AtInputLookupController (baseInputController, $q, $state, $stateParams) }; vm.search = () => { + scope.state._touched = true; + return model.search({ [search.key]: scope.state._displayValue }, search.config) .then(found => { if (!found) { diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index fea1fbd413..4fa46e50a0 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -217,12 +217,55 @@ function find (method, keys) { return value; } +function has (method, keys) { + if (!keys) { + keys = method; + method = 'GET'; + } + + method = method.toUpperCase(); + + let value; + switch (method) { + case 'OPTIONS': + value = this.options(keys); + break; + default: + value = this.get(keys); + } + + return value !== undefined && value !== null; +} + function normalizePath (resource) { let version = '/api/v2/'; return `${version}${resource}/`; } +function isEditable () { + let canEdit = this.get('summary_fields.user_capabilities.edit'); + + if (canEdit) { + return true; + } + + if (this.has('options', 'actions.PUT')) { + return true; + } + + return false; + +} + +function isCreatable () { + if (this.has('options', 'actions.POST')) { + return true; + } + + return false; +} + function graft (id) { let item = this.get('results').filter(result => result.id === id); @@ -255,6 +298,9 @@ function BaseModel (path) { this.find = find; this.get = get; this.graft = graft; + this.has = has; + this.isEditable = isEditable; + this.isCreatable = isCreatable; this.match = match; this.model = {}; this.normalizePath = normalizePath; diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 0599513b84..06b824b410 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -3,18 +3,21 @@ const ENCRYPTED_VALUE = '$encrypted$'; let BaseModel; function createFormSchema (method, config) { + if (!config) { + config = method; + method = 'GET'; + } + let schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); if (config && config.omit) { - config.omit.forEach(key => { - delete schema[key]; - }); + config.omit.forEach(key => delete schema[key]); } for (let key in schema) { schema[key].id = key; - if (method === 'put') { + if (this.has(key)) { schema[key]._value = this.get(key); } } From 0d7419f5c4fa1fadc9712969f1af0861b566fef3 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Thu, 27 Jul 2017 15:19:54 -0700 Subject: [PATCH 121/342] marking job results and survey maker for translation --- .../job-results-stdout.partial.html | 10 +- .../src/job-results/job-results.controller.js | 6 +- .../src/job-results/job-results.partial.html | 102 ++++++------ .../src/job-results/parse-stdout.service.js | 4 +- .../src/partials/survey-maker-modal.html | 2 +- .../src/templates/survey-maker/shared/main.js | 2 +- .../shared/question-definition.form.js | 147 +++++++++--------- 7 files changed, 134 insertions(+), 139 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 7b193b0032..63ae96d90c 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -40,11 +40,11 @@
    The standard output is too large to display. Please specify additional filters to narrow the standard out.
    + ng-show="tooManyEvents" translate>The standard output is too large to display. Please specify additional filters to narrow the standard out.
    Too much previous output to display. Showing running standard output.
    + ng-show="tooManyPastEvents" translate>Too much previous output to display. Showing running standard output.
    Job details are not available for this job. Please download to view standard out.
    + ng-show="showLegacyJobErrorMessage" translate>Job details are not available for this job. Please download to view standard out.
    @@ -58,8 +58,8 @@ ng-show="stdoutOverflowed">
    - ^ TOP + ^ TOP
    -
\ No newline at end of file +
diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js index 52bd109ad2..128238fbe2 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -264,9 +264,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // button will jump to the bottom of the standard out pane, // not follow lines as they come in if ($scope.jobFinished) { - $scope.followTooltip = "Jump to last line of standard out."; + $scope.followTooltip = i18n._("Jump to last line of standard out."); } else { - $scope.followTooltip = "Currently following standard out as it comes in. Click to unfollow."; + $scope.followTooltip = i18n._("Currently following standard out as it comes in. Click to unfollow."); } $scope.events = {}; @@ -316,7 +316,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy if (change === 'finishedTime' && !$scope.job.finished) { $scope.job.finished = mungedEvent.finishedTime; $scope.jobFinished = true; - $scope.followTooltip = "Jump to last line of standard out."; + $scope.followTooltip = i18n._("Jump to last line of standard out."); if ($scope.followEngaged) { if (!$scope.followScroll) { $scope.followScroll = function() { diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html index 7df67da3bd..8e8cd9b00d 100644 --- a/awx/ui/client/src/job-results/job-results.partial.html +++ b/awx/ui/client/src/job-results/job-results.partial.html @@ -14,7 +14,7 @@
+ class="JobResults-panelHeaderText" translate> DETAILS
@@ -26,7 +26,7 @@ data-placement="top" mode="all" ng-click="relaunchJob()" - aw-tool-tip="Relaunch using the same parameters" + aw-tool-tip="{{'Relaunch using the same parameters' | translate}}" data-original-title="" title=""> @@ -39,7 +39,7 @@ ng-click="cancelJob()" ng-show="job_status == 'running' || job_status=='pending' " - aw-tool-tip="Cancel" + aw-tool-tip="{{'Cancel' | translate}}" data-original-title="" title=""> @@ -51,7 +51,7 @@ ng-click="deleteJob()" ng-hide="job_status == 'running' || job_status == 'pending' || !job.summary_fields.user_capabilities.delete" - aw-tool-tip="Delete" + aw-tool-tip="{{'Delete' | translate}}" data-original-title="" title=""> @@ -64,21 +64,21 @@
-
-