From d2e49329ddc5ce75cd5ffb9492cfff23894ae031 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Tue, 12 Jan 2016 11:26:55 -0500 Subject: [PATCH 01/57] Fix old python support in scan_services Python 2.4/RHEL5 doesn't support in-line conditionals so this breaks it out into a normal multi-line condition --- awx/plugins/library/scan_services.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py index 22a5352b2c..be343291b5 100644 --- a/awx/plugins/library/scan_services.py +++ b/awx/plugins/library/scan_services.py @@ -66,7 +66,10 @@ class ServiceScanService(BaseService): if len(line_data) < 4: continue # Skipping because we expected more data service_name = " ".join(line_data[3:]) - service_state = "running" if line_data[1] == "+" else "stopped" + if line_data[1] == "+": + service_state = "running" + else: + service_state = "stopped" services.append({"name": service_name, "state": service_state, "source": "sysv"}) rc, stdout, stderr = self.module.run_command("%s list" % initctl_path) real_stdout = stdout.replace("\r","") From 70d89354618c1eb156c7b17da105db8ec01b817f Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 14 Jan 2016 11:59:27 -0500 Subject: [PATCH 02/57] Fix v2 playbook_on_start method signature --- awx/plugins/callback/job_event_callback.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index 9fdfa4e23a..838b3fde1d 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -326,9 +326,9 @@ class JobCallbackModule(BaseCallbackModule): def playbook_on_start(self): self._log_event('playbook_on_start') - def v2_playbook_on_start(self): - # since there is no task/play info, this is currently identical - # to the v1 callback which does the same thing + def v2_playbook_on_start(self, playbook): + # NOTE: the playbook parameter was added late in Ansible 2.0 development + # so we don't currently utilize but could later. self.playbook_on_start() def playbook_on_notify(self, host, handler): From be1cceff03c8a6a0cda1c38fd5a6decb33cabb4c Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Fri, 15 Jan 2016 11:39:05 -0500 Subject: [PATCH 03/57] Bump 2.4.4 release branch version --- awx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/__init__.py b/awx/__init__.py index e3cb8020b1..72b8f62cbf 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -6,7 +6,7 @@ import sys import warnings import site -__version__ = '2.4.3' +__version__ = '2.4.4' __all__ = ['__version__'] From 53d025c6d4f118b9f23942ce82081df5dc0ff1c7 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Fri, 15 Jan 2016 13:40:03 -0500 Subject: [PATCH 04/57] Fixes rhel 7.2 websocket issue Rev gevent and greenlet, on another side a fix has been applied to gevent-socketio. --- requirements/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 369daf43d5..addd05760c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -30,7 +30,7 @@ dogpile.cache==0.5.6 dogpile.core==0.4.1 enum34==1.0.4 #functools32==3.2.3-2 -gevent==1.0.2 +gevent==1.1rc3 gevent-websocket==0.9.3 git+https://github.com/chrismeyersfsu/django-jsonfield.git@tower_0.9.12#egg=django-jsonfield git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic @@ -40,7 +40,7 @@ git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy git+https://github.com/chrismeyersfsu/python-keystoneclient.git@1.3.0#egg=python-keystoneclient git+https://github.com/chrismeyersfsu/shade.git@tower_0.5.0#egg=shade git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize -greenlet==0.4.7 +greenlet==0.4.9 httplib2==0.9 idna==2.0 importlib==1.0.3 From 7ca73a4df1c43f4076553c07b0909734072d5a0f Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Fri, 15 Jan 2016 15:43:17 -0500 Subject: [PATCH 05/57] Rev PBR to 0.11.1 to fix up some dependencies All openstack modules claim they'll work with this version --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index addd05760c..8158d952df 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -70,7 +70,7 @@ oslo.serialization==1.4.0 oslo.utils==1.4.0 os-networksv2-python-novaclient-ext==0.25 os-virtual-interfacesv2-python-novaclient-ext==0.19 -pbr==0.10.0 +pbr==0.11.1 pexpect==3.1 pip==1.5.4 prettytable==0.7.2 From fd8c07660532f8a8714c5f750ef85978b4570626 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 4 Dec 2015 11:20:05 -0500 Subject: [PATCH 06/57] store yaml output, test to cover bug, and docs update --- awx/api/templates/api/job_template_launch.md | 9 ++++---- awx/main/models/jobs.py | 4 +++- awx/main/tests/jobs/job_launch.py | 24 +++++++++++++++++++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/awx/api/templates/api/job_template_launch.md b/awx/api/templates/api/job_template_launch.md index 90846940a8..1dddde4210 100644 --- a/awx/api/templates/api/job_template_launch.md +++ b/awx/api/templates/api/job_template_launch.md @@ -18,10 +18,11 @@ The response will include the following fields: associated with the job template. If not then one should be supplied when launching the job (boolean, read-only) -Make a POST request to this resource to launch the job_template. If any -passwords or variables are required, they must be passed via POST data. -If `credential_needed_to_start` is `True` then the `credential` field is -required as well. +Make a POST request to this resource to launch the job_template. If any +passwords or extra variables (extra_vars) are required, they must be passed +via POST data, with extra_vars given as a YAML or JSON string and escaped +parentheses. If `credential_needed_to_start` is `True` then the `credential` +field is required as well. If successful, the response status code will be 202. If any required passwords are not provided, a 400 status code will be returned. If the job cannot be diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index ebc6a0fa78..51f577bac5 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -4,6 +4,7 @@ # Python import hmac import json +import yaml import logging # Django @@ -304,7 +305,8 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): kwargs_extra_vars = json.loads(kwargs_extra_vars) except Exception: try: - yaml.safe_load(kwargs_extra_vars) + kwargs_extra_vars = yaml.safe_load(kwargs_extra_vars) + assert type(kwargs_extra_vars) is dict except: kwargs_extra_vars = {} else: diff --git a/awx/main/tests/jobs/job_launch.py b/awx/main/tests/jobs/job_launch.py index b0a0a35abe..66a0ca5a72 100644 --- a/awx/main/tests/jobs/job_launch.py +++ b/awx/main/tests/jobs/job_launch.py @@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse # AWX from awx.main.models import * # noqa from .base import BaseJobTestMixin +import yaml __all__ = ['JobTemplateLaunchTest', 'JobTemplateLaunchPasswordsTest'] @@ -70,6 +71,28 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TestCase): j = Job.objects.get(pk=response['job']) self.assertTrue(j.status == 'new') + def test_launch_extra_vars_json(self): + # Sending extra_vars as a JSON string, implicit credentials + with self.current_user(self.user_sue): + data = dict(extra_vars = '{\"a\":3}') + response = self.post(self.launch_url, data, expect=202) + j = Job.objects.get(pk=response['job']) + ev_dict = yaml.load(j.extra_vars) + self.assertIn('a', ev_dict) + if 'a' in ev_dict: + self.assertEqual(ev_dict['a'], 3) + + def test_launch_extra_vars_yaml(self): + # Sending extra_vars as a JSON string, implicit credentials + with self.current_user(self.user_sue): + data = dict(extra_vars = 'a: 3') + response = self.post(self.launch_url, data, expect=202) + j = Job.objects.get(pk=response['job']) + ev_dict = yaml.load(j.extra_vars) + self.assertIn('a', ev_dict) + if 'a' in ev_dict: + self.assertEqual(ev_dict['a'], 3) + def test_credential_explicit(self): # Explicit, credential with self.current_user(self.user_sue): @@ -195,4 +218,3 @@ class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TestCase): with self.current_user(self.user_sue): response = self.post(self.launch_url, {'ssh_password': ''}, expect=400) self.assertIn('ssh_password', response['passwords_needed_to_start']) - From 6c0af3ebd9031545846cf46fec74bf003c977dcd Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 19 Jan 2016 09:54:25 -0500 Subject: [PATCH 07/57] Add special case to scan_services that fixes SLES11 scan failure issue --- awx/plugins/library/scan_services.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py index be343291b5..accbd232b4 100644 --- a/awx/plugins/library/scan_services.py +++ b/awx/plugins/library/scan_services.py @@ -97,6 +97,13 @@ class ServiceScanService(BaseService): #print '%s --status-all | grep -E "is (running|stopped)"' % service_path p = re.compile('(?P.*?)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)') rc, stdout, stderr = self.module.run_command('%s' % chkconfig_path, use_unsafe_shell=True) + # extra flags needed for SLES11 + if not any(p.match(line) for line in stdout.split('\n')): + # If p pattern is not found but p_simple is, we have single-column ouptut + p_simple = re.compile('(?P.*?)\s+(?Pon|off)') + if any(p_simple.match(line) for line in stdout.split('\n')): + # Try extra flags " -l --allservices" to output all columns + rc, stdout, stderr = self.module.run_command('%s -l --allservices' % chkconfig_path, use_unsafe_shell=True) for line in stdout.split('\n'): m = p.match(line) if m: From ed99d1eb1591dba04f7b77cad2f103a42b0f1c3e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 19 Jan 2016 12:45:16 -0500 Subject: [PATCH 08/57] fix 2nd instance of in-line conditional for RHEL5 scan issue --- awx/plugins/library/scan_services.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py index accbd232b4..a786b791bf 100644 --- a/awx/plugins/library/scan_services.py +++ b/awx/plugins/library/scan_services.py @@ -149,8 +149,12 @@ class SystemctlScanService(BaseService): line_data = line.split() if len(line_data) != 2: continue + if line_data[1] == "enabled": + state_val = "running" + else: + state_val = "stopped" services.append({"name": line_data[0], - "state": "running" if line_data[1] == "enabled" else "stopped", + "state": state_val, "source": "systemd"}) return services From 8978c5bf5465dd4f481b09c2d8cb512e847e1765 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 20 Jan 2016 10:45:42 -0800 Subject: [PATCH 09/57] Re-Enabling Pendo User Guides --- awx/ui/templates/ui/index.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/awx/ui/templates/ui/index.html b/awx/ui/templates/ui/index.html index ea180697f2..7587a79fba 100644 --- a/awx/ui/templates/ui/index.html +++ b/awx/ui/templates/ui/index.html @@ -30,8 +30,7 @@ window.pendo_options = { // This is required to be able to load data client side - usePendoAgentAPI: true, - disableGuides: true + usePendoAgentAPI: true }; From de7be08856db9e5e7b454df127e08dfa119f08a2 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 20 Jan 2016 15:11:49 -0500 Subject: [PATCH 10/57] Obey no_log with ansible 2.0 Fixes #645 --- awx/plugins/callback/job_event_callback.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index 838b3fde1d..b4eaae083d 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -37,6 +37,7 @@ import logging import os import pwd import urlparse +import re # Requests import requests @@ -126,6 +127,31 @@ class BaseCallbackModule(object): self._init_connection() if self.context is None: self._start_connection() + if 'res' in event_data \ + and event_data['res'].get('_ansible_no_log', False): + res = event_data['res'] + if 'stdout' in res and res['stdout']: + res['stdout'] = '' + if 'stdout_lines' in res and res['stdout_lines']: + res['stdout_lines'] = [''] + if 'stderr' in res and res['stderr']: + res['stderr'] = '' + if 'stderr_lines' in res and res['stderr_lines']: + res['stderr_lines'] = [''] + if res.get('cmd', None) and re.search(r'\s', res['cmd']): + res['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$', + r'\1 ', + res['cmd']) + if 'invocation' in res \ + and 'module_args' in res['invocation'] \ + and '_raw_params' in res['invocation']['module_args'] \ + and re.search(r'\s', + res['invocation']['module_args']['_raw_params']): + res['invocation']['module_args']['_raw_params'] = \ + re.sub(r'^(([^\s\\]|\\\s)+).*$', + r'\1 ', + res['invocation']['module_args']['_raw_params']) + msg['event_data']['res'] = res self.socket.send_json(msg) self.socket.recv() From 32366b5544981bce765e9b05dfe2411ed54fde61 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 20 Jan 2016 15:12:52 -0500 Subject: [PATCH 11/57] Fix for rax cache timeout issue 2.4.0 level of rax.py introduces a default cache timeout of 600s, this fixes that and includes a new rax.py inventory module that has been submitted upstream. --- awx/main/tasks.py | 1 + awx/plugins/inventory/rax.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index eef1590d1a..e8e36a8e10 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1193,6 +1193,7 @@ class RunInventoryUpdate(BaseTask): elif inventory_update.source == 'rax': env['RAX_CREDS_FILE'] = cloud_credential env['RAX_REGION'] = inventory_update.source_regions or 'all' + env['RAX_CACHE_MAX_AGE'] = 0 # Set this environment variable so the vendored package won't # complain about not being able to determine its version number. env['PBR_VERSION'] = '0.5.21' diff --git a/awx/plugins/inventory/rax.py b/awx/plugins/inventory/rax.py index 0028f54d20..4ac6b0f47e 100755 --- a/awx/plugins/inventory/rax.py +++ b/awx/plugins/inventory/rax.py @@ -355,9 +355,12 @@ def get_cache_file_path(regions): def _list(regions, refresh_cache=True): + cache_max_age = int(get_config(p, 'rax', 'cache_max_age', + 'RAX_CACHE_MAX_AGE', 600)) + if (not os.path.exists(get_cache_file_path(regions)) or refresh_cache or - (time() - os.stat(get_cache_file_path(regions))[-1]) > 600): + (time() - os.stat(get_cache_file_path(regions))[-1]) > cache_max_age): # Cache file doesn't exist or older than 10m or refresh cache requested _list_into_cache(regions) From 483fc507d6acfc04619e60bcc33f9a5360189cd6 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 25 Jan 2016 16:51:33 -0500 Subject: [PATCH 12/57] tasks env vars should be strings This was causing rax unit test failures which are now alleviated --- 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 e8e36a8e10..f1e1e56e56 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -1193,7 +1193,7 @@ class RunInventoryUpdate(BaseTask): elif inventory_update.source == 'rax': env['RAX_CREDS_FILE'] = cloud_credential env['RAX_REGION'] = inventory_update.source_regions or 'all' - env['RAX_CACHE_MAX_AGE'] = 0 + env['RAX_CACHE_MAX_AGE'] = "0" # Set this environment variable so the vendored package won't # complain about not being able to determine its version number. env['PBR_VERSION'] = '0.5.21' From 60e242cb26d182c3ac811066a55c1f4263960f90 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Wed, 20 Jan 2016 16:34:30 -0500 Subject: [PATCH 13/57] RHEL5 compatibility and handling of error scenarios --- awx/plugins/library/scan_services.py | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py index a786b791bf..779d44effd 100644 --- a/awx/plugins/library/scan_services.py +++ b/awx/plugins/library/scan_services.py @@ -97,13 +97,23 @@ class ServiceScanService(BaseService): #print '%s --status-all | grep -E "is (running|stopped)"' % service_path p = re.compile('(?P.*?)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)') rc, stdout, stderr = self.module.run_command('%s' % chkconfig_path, use_unsafe_shell=True) - # extra flags needed for SLES11 - if not any(p.match(line) for line in stdout.split('\n')): - # If p pattern is not found but p_simple is, we have single-column ouptut + # Check for special cases where stdout does not fit pattern + match_any = False + for line in stdout.split('\n'): + if p.match(line): + match_any = True + if not match_any: p_simple = re.compile('(?P.*?)\s+(?Pon|off)') - if any(p_simple.match(line) for line in stdout.split('\n')): - # Try extra flags " -l --allservices" to output all columns + match_any = False + for line in stdout.split('\n'): + if p_simple.match(line): + match_any = True + if match_any: + # Try extra flags " -l --allservices" needed for SLES11 rc, stdout, stderr = self.module.run_command('%s -l --allservices' % chkconfig_path, use_unsafe_shell=True) + elif '--list' in stderr: + # Extra flag needed for RHEL5 + rc, stdout, stderr = self.module.run_command('%s --list' % chkconfig_path, use_unsafe_shell=True) for line in stdout.split('\n'): m = p.match(line) if m: @@ -116,11 +126,12 @@ class ServiceScanService(BaseService): service_state = 'running' #elif rc in (1,3): else: - service_state = 'stopped' + if 'root' in stderr or 'permission' in stderr.lower() or 'not in sudoers' in stderr.lower(): + service_state = 'unable to scan, requires root' + else: + service_state = 'stopped' service_data = {"name": service_name, "state": service_state, "source": "sysv"} services.append(service_data) - # rc, stdout, stderr = self.module.run_command("%s --list" % chkconfig_path) - # Do something with chkconfig status return services class SystemctlScanService(BaseService): @@ -167,6 +178,8 @@ def main(): svc = svcmod.gather_services() if svc is not None: all_services += svc + if len(all_services) == 0: + module.fail_json(msg="Failed to find any services. Sometimes this solved by running with privilege escalation.") results = dict(ansible_facts=dict(services=all_services)) module.exit_json(**results) From 7afa14aabbf2561e556af6b84ccff3e5f46dae4b Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 27 Jan 2016 11:50:57 -0500 Subject: [PATCH 14/57] bump shade from 0.5.0 to 1.4 --- requirements/requirements.txt | 123 ++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 8158d952df..24ef4311c9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,121 +1,130 @@ -amqp==1.4.5 git+https://github.com/chrismeyersfsu/ansiconv.git@tower_1.0.0#egg=ansiconv +amqp==1.4.5 anyjson==0.3.3 apache-libcloud==0.15.1 appdirs==1.4.0 -argparse==1.2.1 azure==0.9.0 -Babel==1.3 +Babel==2.2.0 billiard==3.3.0.16 boto==2.34.0 celery==3.1.10 -cffi==1.1.2 -cliff==1.13.0 +cffi==1.5.0 +cliff==1.15.0 cmd2==0.6.8 cryptography==0.9.3 d2to1==0.2.11 -defusedxml==0.4.1 +debtcollector==1.2.0 +decorator==4.0.6 Django==1.6.7 +defusedxml==0.4.1 django-auth-ldap==1.2.6 django-celery==3.1.10 django-crum==0.6.1 django-extensions==1.3.3 django-polymorphic==0.5.3 django-radius==1.0.0 -djangorestframework==2.3.13 django-split-settings==0.1.1 django-taggit==0.11.2 -git+https://github.com/matburt/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding -dogpile.cache==0.5.6 -dogpile.core==0.4.1 -enum34==1.0.4 -#functools32==3.2.3-2 -gevent==1.1rc3 -gevent-websocket==0.9.3 +djangorestframework==2.3.13 git+https://github.com/chrismeyersfsu/django-jsonfield.git@tower_0.9.12#egg=django-jsonfield git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic git+https://github.com/chrismeyersfsu/django-rest-framework-mongoengine.git@0c79515257a33a0ce61500b65fa497398628a03d#egg=django-rest-framework-mongoengine +git+https://github.com/matburt/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding +dogpile.cache==0.5.7 +dogpile.core==0.4.1 +enum34==1.1.2 +funcsigs==0.4 +#functools32==3.2.3.post2 +futures==3.0.4 git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio -git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy -git+https://github.com/chrismeyersfsu/python-keystoneclient.git@1.3.0#egg=python-keystoneclient -git+https://github.com/chrismeyersfsu/shade.git@tower_0.5.0#egg=shade -git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize +gevent-websocket==0.9.3 +gevent==1.1rc3 greenlet==0.4.9 -httplib2==0.9 +httplib2==0.9.2 idna==2.0 importlib==1.0.3 -ipaddress==1.0.14 -iso8601==0.1.10 +ip-associations-python-novaclient-ext==0.1 +ipaddress==1.0.16 +git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy +iso8601==0.1.11 isodate==0.5.1 -jsonpatch==1.11 -jsonpointer==1.9 +jsonpatch==1.12 +jsonpointer==1.10 jsonschema==2.5.1 -keyring==4.1 +keyring==8.1.1 +keystoneauth1==2.2.0 kombu==3.0.21 lxml==3.4.4 -M2Crypto==0.22.3 Markdown==2.4.1 -mock==1.0.1 +M2Crypto==0.22.3 +mock==1.3.0 mongoengine==0.9.0 -msgpack-python==0.4.6 -netaddr==0.7.14 +monotonic==0.6 +msgpack-python==0.4.7 +munch==2.0.4 +netaddr==0.7.18 netifaces==0.10.4 oauthlib==1.0.3 ordereddict==1.1 -os-client-config==1.6.1 -os-diskconfig-python-novaclient-ext==0.1.2 -oslo.config==1.9.3 -oslo.i18n==1.5.0 -oslo.serialization==1.4.0 -oslo.utils==1.4.0 +os-client-config==1.14.0 +os-diskconfig-python-novaclient-ext==0.1.3 os-networksv2-python-novaclient-ext==0.25 os-virtual-interfacesv2-python-novaclient-ext==0.19 -pbr==0.11.1 +oslo.config==3.4.0 +oslo.i18n==3.2.0 +oslo.serialization==2.2.0 +oslo.utils==3.4.0 +pbr==1.8.1 pexpect==3.1 -pip==1.5.4 prettytable==0.7.2 psphere==0.5.2 psutil==3.1.1 psycopg2 -pyasn1==0.1.8 +pyasn1==0.1.9 pycparser==2.14 -pycrypto==2.6.1 PyJWT==1.4.0 pymongo==2.8 -pyOpenSSL==0.15.1 -pyparsing==2.0.3 +pyparsing==2.0.7 pyrad==2.0 -pyrax==1.9.3 -python-cinderclient==1.1.1 +pyrax==1.9.7 +python-cinderclient==1.5.0 python-dateutil==2.4.0 -python-glanceclient==0.17.0 -python-ironicclient==0.5.0 +python-glanceclient==1.2.0 +python-heatclient==0.8.1 +python-ironicclient==1.0.0 +python-keystoneclient==2.1.1 python-ldap==2.4.20 -python-neutronclient==2.3.11 -python-novaclient==2.20.0 +python-neutronclient==4.0.0 +python-novaclient==3.2.0 python-openid==2.2.5 +python-openstackclient==2.0.0 python-radius==1.0 -git+https://github.com/matburt/python-social-auth.git@master#egg=python-social-auth python-saml==2.1.4 -python-swiftclient==2.2.0 -python-troveclient==1.0.9 +git+https://github.com/matburt/python-social-auth.git@master#egg=python-social-auth +python-swiftclient==2.7.0 +python-troveclient==1.4.0 pytz==2014.10 pywinrm==0.1.1 PyYAML==3.11 pyzmq==14.5.0 rackspace-auth-openstack==1.3 -rackspace-novaclient==1.4 -rax-default-network-flags-python-novaclient-ext==0.2.3 -rax-scheduled-images-python-novaclient-ext==0.2.1 +rackspace-novaclient==1.5 +rax-default-network-flags-python-novaclient-ext==0.3.2 +rax-scheduled-images-python-novaclient-ext==0.3.1 redis==2.10.3 -requests==2.5.1 requests-oauthlib==0.5.0 -simplejson==3.6.0 +requests==2.5.1 +requestsexceptions==1.1.2 +shade==1.4.0 +simplejson==3.8.1 +git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize six==1.9.0 -South==1.0.2 -stevedore==1.3.0 +stevedore==1.10.0 suds==0.4 -warlock==1.1.0 +South==1.0.2 +unicodecsv==0.14.1 +warlock==1.2.0 wheel==0.24.0 +wrapt==1.10.6 wsgiref==0.1.2 xmltodict==0.9.2 From 94e6d2a72aa80a811becbfdb9772f714944132a8 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 28 Jan 2016 11:34:43 -0500 Subject: [PATCH 15/57] Obey no_log even more when using ansible 2.0 Hopefully fixes #645 this time. New function handles recursing down our results array when it's present, also attempts to proactively protect against future data leaks by only allowing white listed fields through. --- awx/plugins/callback/job_event_callback.py | 65 +++++++++++++--------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index b4eaae083d..b5cdea63e4 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -38,6 +38,7 @@ import os import pwd import urlparse import re +from copy import deepcopy # Requests import requests @@ -47,6 +48,42 @@ import zmq import psutil + +CENSOR_FIELD_WHITELIST=[ + 'msg', + 'failed', + 'changed', + 'results', + 'start', + 'end', + 'delta', + 'cmd', + '_ansible_no_log', + 'cmd', + 'rc', + 'failed_when_result', + 'skip_reason', +] + +def censor(obj): + if obj.get('_ansible_no_log', False): + new_obj = {} + for k in CENSOR_FIELD_WHITELIST: + if k in obj: + new_obj[k] = obj[k] + if k == 'cmd' and k in obj: + if re.search(r'\s', obj['cmd']): + new_obj['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$', + r'\1 ', + obj['cmd']) + new_obj['censored'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" + obj = new_obj + if 'results' in obj: + for i in xrange(len(obj['results'])): + obj['results'][i] = censor(obj['results'][i]) + return obj + + class TokenAuth(requests.auth.AuthBase): def __init__(self, token): @@ -127,31 +164,6 @@ class BaseCallbackModule(object): self._init_connection() if self.context is None: self._start_connection() - if 'res' in event_data \ - and event_data['res'].get('_ansible_no_log', False): - res = event_data['res'] - if 'stdout' in res and res['stdout']: - res['stdout'] = '' - if 'stdout_lines' in res and res['stdout_lines']: - res['stdout_lines'] = [''] - if 'stderr' in res and res['stderr']: - res['stderr'] = '' - if 'stderr_lines' in res and res['stderr_lines']: - res['stderr_lines'] = [''] - if res.get('cmd', None) and re.search(r'\s', res['cmd']): - res['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$', - r'\1 ', - res['cmd']) - if 'invocation' in res \ - and 'module_args' in res['invocation'] \ - and '_raw_params' in res['invocation']['module_args'] \ - and re.search(r'\s', - res['invocation']['module_args']['_raw_params']): - res['invocation']['module_args']['_raw_params'] = \ - re.sub(r'^(([^\s\\]|\\\s)+).*$', - r'\1 ', - res['invocation']['module_args']['_raw_params']) - msg['event_data']['res'] = res self.socket.send_json(msg) self.socket.recv() @@ -185,6 +197,9 @@ class BaseCallbackModule(object): response.raise_for_status() def _log_event(self, event, **event_data): + if 'res' in event_data: + event_data['res'] = censor(deepcopy(event_data['res'])) + if self.callback_consumer_port: self._post_job_event_queue_msg(event, event_data) else: From f8a7fb0ea10807eb5caf91aed8697ee74fef96d2 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Thu, 28 Jan 2016 12:43:49 -0500 Subject: [PATCH 16/57] Added missing 'skipped' field for no_log --- awx/plugins/callback/job_event_callback.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index b5cdea63e4..041ce5bc53 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -62,6 +62,7 @@ CENSOR_FIELD_WHITELIST=[ 'cmd', 'rc', 'failed_when_result', + 'skipped', 'skip_reason', ] From 264fca1f3dca9c0191a8f39f558bf2fba0cc7543 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Thu, 28 Jan 2016 17:35:21 -0500 Subject: [PATCH 17/57] separate pip requirements file for python2.6 * platforms, like centos6, with python2.6 need old raggity packages. Let them have the old raggity packages. --- requirements/requirements_python26.txt | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 requirements/requirements_python26.txt diff --git a/requirements/requirements_python26.txt b/requirements/requirements_python26.txt new file mode 100644 index 0000000000..8158d952df --- /dev/null +++ b/requirements/requirements_python26.txt @@ -0,0 +1,121 @@ +amqp==1.4.5 +git+https://github.com/chrismeyersfsu/ansiconv.git@tower_1.0.0#egg=ansiconv +anyjson==0.3.3 +apache-libcloud==0.15.1 +appdirs==1.4.0 +argparse==1.2.1 +azure==0.9.0 +Babel==1.3 +billiard==3.3.0.16 +boto==2.34.0 +celery==3.1.10 +cffi==1.1.2 +cliff==1.13.0 +cmd2==0.6.8 +cryptography==0.9.3 +d2to1==0.2.11 +defusedxml==0.4.1 +Django==1.6.7 +django-auth-ldap==1.2.6 +django-celery==3.1.10 +django-crum==0.6.1 +django-extensions==1.3.3 +django-polymorphic==0.5.3 +django-radius==1.0.0 +djangorestframework==2.3.13 +django-split-settings==0.1.1 +django-taggit==0.11.2 +git+https://github.com/matburt/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding +dogpile.cache==0.5.6 +dogpile.core==0.4.1 +enum34==1.0.4 +#functools32==3.2.3-2 +gevent==1.1rc3 +gevent-websocket==0.9.3 +git+https://github.com/chrismeyersfsu/django-jsonfield.git@tower_0.9.12#egg=django-jsonfield +git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic +git+https://github.com/chrismeyersfsu/django-rest-framework-mongoengine.git@0c79515257a33a0ce61500b65fa497398628a03d#egg=django-rest-framework-mongoengine +git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio +git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy +git+https://github.com/chrismeyersfsu/python-keystoneclient.git@1.3.0#egg=python-keystoneclient +git+https://github.com/chrismeyersfsu/shade.git@tower_0.5.0#egg=shade +git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize +greenlet==0.4.9 +httplib2==0.9 +idna==2.0 +importlib==1.0.3 +ipaddress==1.0.14 +iso8601==0.1.10 +isodate==0.5.1 +jsonpatch==1.11 +jsonpointer==1.9 +jsonschema==2.5.1 +keyring==4.1 +kombu==3.0.21 +lxml==3.4.4 +M2Crypto==0.22.3 +Markdown==2.4.1 +mock==1.0.1 +mongoengine==0.9.0 +msgpack-python==0.4.6 +netaddr==0.7.14 +netifaces==0.10.4 +oauthlib==1.0.3 +ordereddict==1.1 +os-client-config==1.6.1 +os-diskconfig-python-novaclient-ext==0.1.2 +oslo.config==1.9.3 +oslo.i18n==1.5.0 +oslo.serialization==1.4.0 +oslo.utils==1.4.0 +os-networksv2-python-novaclient-ext==0.25 +os-virtual-interfacesv2-python-novaclient-ext==0.19 +pbr==0.11.1 +pexpect==3.1 +pip==1.5.4 +prettytable==0.7.2 +psphere==0.5.2 +psutil==3.1.1 +psycopg2 +pyasn1==0.1.8 +pycparser==2.14 +pycrypto==2.6.1 +PyJWT==1.4.0 +pymongo==2.8 +pyOpenSSL==0.15.1 +pyparsing==2.0.3 +pyrad==2.0 +pyrax==1.9.3 +python-cinderclient==1.1.1 +python-dateutil==2.4.0 +python-glanceclient==0.17.0 +python-ironicclient==0.5.0 +python-ldap==2.4.20 +python-neutronclient==2.3.11 +python-novaclient==2.20.0 +python-openid==2.2.5 +python-radius==1.0 +git+https://github.com/matburt/python-social-auth.git@master#egg=python-social-auth +python-saml==2.1.4 +python-swiftclient==2.2.0 +python-troveclient==1.0.9 +pytz==2014.10 +pywinrm==0.1.1 +PyYAML==3.11 +pyzmq==14.5.0 +rackspace-auth-openstack==1.3 +rackspace-novaclient==1.4 +rax-default-network-flags-python-novaclient-ext==0.2.3 +rax-scheduled-images-python-novaclient-ext==0.2.1 +redis==2.10.3 +requests==2.5.1 +requests-oauthlib==0.5.0 +simplejson==3.6.0 +six==1.9.0 +South==1.0.2 +stevedore==1.3.0 +suds==0.4 +warlock==1.1.0 +wheel==0.24.0 +wsgiref==0.1.2 +xmltodict==0.9.2 From 6b20539e3839724f4fd0219b625bc0faf80760f4 Mon Sep 17 00:00:00 2001 From: James Laska Date: Fri, 29 Jan 2016 09:03:03 -0500 Subject: [PATCH 18/57] Conditionally install 2.6 python requirements --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ecc7e69210..8f07e9903a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ PYTHON = python +PYTHON_VERSION = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_version; print get_python_version()") SITELIB=$(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") OFFICIAL ?= no PACKER ?= packer @@ -234,7 +235,11 @@ requirements requirements_dev requirements_jenkins: %: real-% # * --user (in conjunction with PYTHONUSERBASE="awx" may be a better option # * --target implies --ignore-installed real-requirements: - pip install -r requirements/requirements.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" + @if [ "$(PYTHON_VERSION)" == "2.6" ]; then \ + pip install -r requirements/requirements_python26.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" \ + else \ + pip install -r requirements/requirements.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" \ + fi real-requirements_dev: pip install -r requirements/requirements_dev.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" From 305b4fdead2bab7d543c4d093f6edf181d58999c Mon Sep 17 00:00:00 2001 From: James Laska Date: Fri, 29 Jan 2016 09:54:48 -0500 Subject: [PATCH 19/57] Typo's are bad and should be vanquished --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8f07e9903a..77fb92f6e4 100644 --- a/Makefile +++ b/Makefile @@ -236,9 +236,9 @@ requirements requirements_dev requirements_jenkins: %: real-% # * --target implies --ignore-installed real-requirements: @if [ "$(PYTHON_VERSION)" == "2.6" ]; then \ - pip install -r requirements/requirements_python26.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" \ + pip install -r requirements/requirements_python26.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python"; \ else \ - pip install -r requirements/requirements.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python" \ + pip install -r requirements/requirements.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python"; \ fi real-requirements_dev: From ad27ae1f7d29f7e6f01c40a37c915d786c7b2929 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 29 Jan 2016 11:19:30 -0500 Subject: [PATCH 20/57] pyrax bumpb new python license --- .../ip_associations_python_novaclient_ext.txt | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/licenses/ip_associations_python_novaclient_ext.txt diff --git a/docs/licenses/ip_associations_python_novaclient_ext.txt b/docs/licenses/ip_associations_python_novaclient_ext.txt new file mode 100644 index 0000000000..137069b823 --- /dev/null +++ b/docs/licenses/ip_associations_python_novaclient_ext.txt @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. From 7d73cb1bcf4a41295c13ee1638dc7aecca0624fc Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 29 Jan 2016 16:25:37 -0500 Subject: [PATCH 21/57] requests needs openssl --- requirements/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 24ef4311c9..c21ade6217 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -81,9 +81,11 @@ psphere==0.5.2 psutil==3.1.1 psycopg2 pyasn1==0.1.9 +pycrypto==2.6.1 pycparser==2.14 PyJWT==1.4.0 pymongo==2.8 +pyOpenSSL==0.15.1 pyparsing==2.0.7 pyrad==2.0 pyrax==1.9.7 From 306d0d401a43164402540f5fdd4e7f16b5e46e42 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 1 Feb 2016 12:16:26 -0500 Subject: [PATCH 22/57] point at packages with source on pypi * --target doesn't use wheels. Some packages only have wheel. Bump back the package versions that have source in them, but not too far back such that the interfaces change. It's a balancing act. --- requirements/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c21ade6217..971d0d1580 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -70,7 +70,7 @@ os-client-config==1.14.0 os-diskconfig-python-novaclient-ext==0.1.3 os-networksv2-python-novaclient-ext==0.25 os-virtual-interfacesv2-python-novaclient-ext==0.19 -oslo.config==3.4.0 +oslo.config==3.3.0 oslo.i18n==3.2.0 oslo.serialization==2.2.0 oslo.utils==3.4.0 @@ -91,7 +91,7 @@ pyrad==2.0 pyrax==1.9.7 python-cinderclient==1.5.0 python-dateutil==2.4.0 -python-glanceclient==1.2.0 +python-glanceclient==1.1.0 python-heatclient==0.8.1 python-ironicclient==1.0.0 python-keystoneclient==2.1.1 @@ -105,7 +105,7 @@ python-saml==2.1.4 git+https://github.com/matburt/python-social-auth.git@master#egg=python-social-auth python-swiftclient==2.7.0 python-troveclient==1.4.0 -pytz==2014.10 +pytz==2015.7 pywinrm==0.1.1 PyYAML==3.11 pyzmq==14.5.0 @@ -116,7 +116,7 @@ rax-scheduled-images-python-novaclient-ext==0.3.1 redis==2.10.3 requests-oauthlib==0.5.0 requests==2.5.1 -requestsexceptions==1.1.2 +requestsexceptions==1.1.1 shade==1.4.0 simplejson==3.8.1 git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize From cced99f75d4a013ce1b9e7b06ee25c3a8a5ae5d4 Mon Sep 17 00:00:00 2001 From: James Laska Date: Tue, 2 Feb 2016 12:45:08 -0500 Subject: [PATCH 23/57] Resolve bug when building with /bin/sh on Ubuntu Ubuntu uses /bin/sh by default. This change properly compares strings in a POSIX compliant manner. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 77fb92f6e4..40eebbe644 100644 --- a/Makefile +++ b/Makefile @@ -235,7 +235,7 @@ requirements requirements_dev requirements_jenkins: %: real-% # * --user (in conjunction with PYTHONUSERBASE="awx" may be a better option # * --target implies --ignore-installed real-requirements: - @if [ "$(PYTHON_VERSION)" == "2.6" ]; then \ + @if [ "$(PYTHON_VERSION)" = "2.6" ]; then \ pip install -r requirements/requirements_python26.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python"; \ else \ pip install -r requirements/requirements.txt --target awx/lib/site-packages/ --install-option="--install-platlib=\$$base/lib/python"; \ From dd927c97faa58e642993936d280cf09e0793fd95 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Fri, 29 Jan 2016 16:27:46 -0500 Subject: [PATCH 24/57] change to warning behavior --- awx/plugins/library/scan_services.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py index 779d44effd..7e3b011e5c 100644 --- a/awx/plugins/library/scan_services.py +++ b/awx/plugins/library/scan_services.py @@ -47,6 +47,7 @@ class BaseService(object): def __init__(self, module): self.module = module + self.incomplete_warning = False class ServiceScanService(BaseService): @@ -127,7 +128,8 @@ class ServiceScanService(BaseService): #elif rc in (1,3): else: if 'root' in stderr or 'permission' in stderr.lower() or 'not in sudoers' in stderr.lower(): - service_state = 'unable to scan, requires root' + self.incomplete_warning = True + continue else: service_state = 'stopped' service_data = {"name": service_name, "state": service_state, "source": "sysv"} @@ -173,14 +175,19 @@ def main(): module = AnsibleModule(argument_spec = dict()) service_modules = (ServiceScanService, SystemctlScanService) all_services = [] + incomplete_warning = False for svc_module in service_modules: svcmod = svc_module(module) svc = svcmod.gather_services() if svc is not None: all_services += svc + if svcmod.incomplete_warning: + incomplete_warning = True if len(all_services) == 0: - module.fail_json(msg="Failed to find any services. Sometimes this solved by running with privilege escalation.") + module.fail_json(msg="Failed to find any services. Sometimes this is due to insufficient privileges.") results = dict(ansible_facts=dict(services=all_services)) + if incomplete_warning: + results['msg'] = "WARNING: Could not find status for all services. Sometimes this is due to insufficient privileges." module.exit_json(**results) main() From 24b33a52c8d944f6cca6db01dbfe64b323854c4a Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 10 Feb 2016 17:01:18 -0500 Subject: [PATCH 25/57] Roll back mock version due to packaging issues --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 971d0d1580..d11279e736 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -57,7 +57,7 @@ kombu==3.0.21 lxml==3.4.4 Markdown==2.4.1 M2Crypto==0.22.3 -mock==1.3.0 +mock==1.0.1 mongoengine==0.9.0 monotonic==0.6 msgpack-python==0.4.7 From 4873e2413f325c11bd6c9377a41e80f00eeea490 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 16 Feb 2016 17:49:34 -0500 Subject: [PATCH 26/57] * Populate browsable API raw data form with submitted request data in response to an update. * Remove fields from browsable API raw data that are set implicitly based on URL / parent object. * Fix issue where a group/host could be assigned to a different inventory. * Update validation to load values from existing instance if not present in new data; allows PATCH requests to succeed. * Remove job_args, job_cwd, job_env, result_stdout and result_traceback fields from job listings. --- awx/api/generics.py | 28 +++++++++++- awx/api/parsers.py | 30 +++++++++++++ awx/api/renderers.py | 16 ++++++- awx/api/serializers.py | 93 +++++++++++++++++++--------------------- awx/api/views.py | 56 +++++++++++++++++++++++- awx/settings/defaults.py | 2 +- 6 files changed, 170 insertions(+), 55 deletions(-) create mode 100644 awx/api/parsers.py diff --git a/awx/api/generics.py b/awx/api/generics.py index 6618263742..605c824468 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -2,6 +2,7 @@ # All Rights Reserved. # Python +from collections import OrderedDict import inspect import logging import time @@ -155,6 +156,22 @@ class APIView(views.APIView): context = self.get_description_context() return render_to_string(template_list, context) + def update_raw_data(self, data): + # Remove the parent key if the view is a sublist, since it will be set + # automatically. + parent_key = getattr(self, 'parent_key', None) + if parent_key: + data.pop(parent_key, None) + + # Use request data as-is when original request is an update and the + # submitted data was rejected. + request_method = getattr(self, '_raw_data_request_method', None) + response_status = getattr(self, '_raw_data_response_status', 0) + if request_method in ('POST', 'PUT', 'PATCH') and response_status in xrange(400, 500): + return self.request.data.copy() + + return data + class GenericAPIView(generics.GenericAPIView, APIView): # Base class for all model-based views. @@ -166,11 +183,14 @@ class GenericAPIView(generics.GenericAPIView, APIView): def get_serializer(self, *args, **kwargs): serializer = super(GenericAPIView, self).get_serializer(*args, **kwargs) # Override when called from browsable API to generate raw data form; - # always remove read only fields from sample raw data. + # update serializer "validated" data to be displayed by the raw data + # form. if hasattr(self, '_raw_data_form_marker'): + # Always remove read only fields from serializer. for name, field in serializer.fields.items(): if getattr(field, 'read_only', None): - del serializer.fields[name] + del serializer.fields[name] + serializer._data = self.update_raw_data(serializer.data) return serializer def get_queryset(self): @@ -439,6 +459,10 @@ class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView): self.update_filter(request, *args, **kwargs) return super(RetrieveUpdateAPIView, self).update(request, *args, **kwargs) + def partial_update(self, request, *args, **kwargs): + self.update_filter(request, *args, **kwargs) + return super(RetrieveUpdateAPIView, self).partial_update(request, *args, **kwargs) + def update_filter(self, request, *args, **kwargs): ''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering ''' pass diff --git a/awx/api/parsers.py b/awx/api/parsers.py new file mode 100644 index 0000000000..94ddbec561 --- /dev/null +++ b/awx/api/parsers.py @@ -0,0 +1,30 @@ +# Python +from collections import OrderedDict +import json + +# Django +from django.conf import settings +from django.utils import six + +# Django REST Framework +from rest_framework import parsers +from rest_framework.exceptions import ParseError + + +class JSONParser(parsers.JSONParser): + """ + Parses JSON-serialized data, preserving order of dictionary keys. + """ + + def parse(self, stream, media_type=None, parser_context=None): + """ + Parses the incoming bytestream as JSON and returns the resulting data. + """ + parser_context = parser_context or {} + encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) + + try: + data = stream.read().decode(encoding) + return json.loads(data, object_pairs_hook=OrderedDict) + except ValueError as exc: + raise ParseError('JSON parse error - %s' % six.text_type(exc)) diff --git a/awx/api/renderers.py b/awx/api/renderers.py index 1897028333..348a8220c4 100644 --- a/awx/api/renderers.py +++ b/awx/api/renderers.py @@ -17,17 +17,29 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer): return renderers.JSONRenderer() return renderer + def get_context(self, data, accepted_media_type, renderer_context): + # Store the associated response status to know how to populate the raw + # data form. + try: + setattr(renderer_context['view'], '_raw_data_response_status', renderer_context['response'].status_code) + return super(BrowsableAPIRenderer, self).get_context(data, accepted_media_type, renderer_context) + finally: + delattr(renderer_context['view'], '_raw_data_response_status') + def get_raw_data_form(self, data, view, method, request): # Set a flag on the view to indiciate to the view/serializer that we're - # creating a raw data form for the browsable API. + # creating a raw data form for the browsable API. Store the original + # request method to determine how to populate the raw data form. try: setattr(view, '_raw_data_form_marker', True) + setattr(view, '_raw_data_request_method', request.method) return super(BrowsableAPIRenderer, self).get_raw_data_form(data, view, method, request) finally: delattr(view, '_raw_data_form_marker') + delattr(view, '_raw_data_request_method') def get_rendered_html_form(self, data, view, method, request): - '''Never show auto-generated form (only raw form).''' + # Never show auto-generated form (only raw form). obj = getattr(view, 'object', None) if not self.show_form_for_method(view, method, request, obj): return diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 572e690ce6..1c8fd17bd8 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -463,24 +463,6 @@ class BaseSerializer(serializers.ModelSerializer): raise ValidationError(d) return attrs - def to_representation(self, obj): - # FIXME: Doesn't get called anymore for an new raw data form! - # When rendering the raw data form, create an instance of the model so - # that the model defaults will be filled in. - view = self.context.get('view', None) - parent_key = getattr(view, 'parent_key', None) - if not obj and hasattr(view, '_raw_data_form_marker'): - obj = self.Meta.model() - # FIXME: Would be nice to include any posted data for the raw data - # form, so that a submission with errors can be modified in place - # and resubmitted. - ret = super(BaseSerializer, self).to_representation(obj) - # Remove parent key from raw form data, since it will be automatically - # set by the sub list create view. - if parent_key and hasattr(view, '_raw_data_form_marker'): - ret.pop(parent_key, None) - return ret - class BaseFactSerializer(DocumentSerializer): @@ -611,6 +593,12 @@ class UnifiedJobListSerializer(UnifiedJobSerializer): class Meta: fields = ('*', '-job_args', '-job_cwd', '-job_env', '-result_traceback', '-result_stdout') + def get_field_names(self, declared_fields, info): + field_names = super(UnifiedJobListSerializer, self).get_field_names(declared_fields, info) + # Meta multiple inheritance and -field_name options don't seem to be + # taking effect above, so remove the undesired fields here. + return tuple(x for x in field_names if x not in ('job_args', 'job_cwd', 'job_env', 'result_traceback', 'result_stdout')) + def get_types(self): if type(self) is UnifiedJobListSerializer: return ['project_update', 'inventory_update', 'job', 'ad_hoc_command', 'system_job'] @@ -995,6 +983,14 @@ class HostSerializer(BaseSerializerWithVariables): 'last_job_host_summary') read_only_fields = ('last_job', 'last_job_host_summary') + def build_relational_field(self, field_name, relation_info): + field_class, field_kwargs = super(HostSerializer, self).build_relational_field(field_name, relation_info) + # Inventory is read-only unless creating a new host. + if self.instance and field_name == 'inventory': + field_kwargs['read_only'] = True + field_kwargs.pop('queryset', None) + return field_class, field_kwargs + def get_related(self, obj): res = super(HostSerializer, self).get_related(obj) res.update(dict( @@ -1053,15 +1049,12 @@ class HostSerializer(BaseSerializerWithVariables): return value def validate(self, attrs): - name = force_text(attrs.get('name', '')) + name = force_text(attrs.get('name', self.instance and self.instance.name or '')) host, port = self._get_host_port_from_name(name) if port: attrs['name'] = host - if self.instance: - variables = force_text(attrs.get('variables', self.instance.variables) or '') - else: - variables = force_text(attrs.get('variables', '')) + variables = force_text(attrs.get('variables', self.instance and self.instance.variables or '')) try: vars_dict = json.loads(variables.strip() or '{}') vars_dict['ansible_ssh_port'] = port @@ -1099,6 +1092,14 @@ class GroupSerializer(BaseSerializerWithVariables): 'total_hosts', 'hosts_with_active_failures', 'total_groups', 'groups_with_active_failures', 'has_inventory_sources') + def build_relational_field(self, field_name, relation_info): + field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info) + # Inventory is read-only unless creating a new group. + if self.instance and field_name == 'inventory': + field_kwargs['read_only'] = True + field_kwargs.pop('queryset', None) + return field_class, field_kwargs + def get_related(self, obj): res = super(GroupSerializer, self).get_related(obj) res.update(dict( @@ -1247,9 +1248,10 @@ class InventorySourceOptionsSerializer(BaseSerializer): # TODO: Validate source, validate source_regions errors = {} - source_script = attrs.get('source_script', None) - if 'source' in attrs and attrs.get('source', '') == 'custom': - if source_script is None or source_script == '': + source = attrs.get('source', self.instance and self.instance.source or '') + source_script = attrs.get('source_script', self.instance and self.instance.source_script or '') + if source == 'custom': + if not source_script is None or source_script == '': errors['source_script'] = 'source_script must be provided' else: try: @@ -1403,15 +1405,19 @@ class PermissionSerializer(BaseSerializer): def validate(self, attrs): # Can only set either user or team. - if attrs.get('user', None) and attrs.get('team', None): + user = attrs.get('user', self.instance and self.instance.user or None) + team = attrs.get('team', self.instance and self.instance.team or None) + if user and team: raise serializers.ValidationError('permission can only be assigned' ' to a user OR a team, not both') # Cannot assign admit/read/write permissions for a project. - if attrs.get('permission_type', None) in ('admin', 'read', 'write') and attrs.get('project', None): + permission_type = attrs.get('permission_type', self.instance and self.instance.permission_type or None) + project = attrs.get('project', self.instance and self.instance.project or None) + if permission_type in ('admin', 'read', 'write') and project: raise serializers.ValidationError('project cannot be assigned for ' 'inventory-only permissions') # Project is required when setting deployment permissions. - if attrs.get('permission_type', None) in ('run', 'check') and not attrs.get('project', None): + if permission_type in ('run', 'check') and not project: raise serializers.ValidationError('project is required when ' 'assigning deployment permissions') @@ -1522,9 +1528,10 @@ class JobOptionsSerializer(BaseSerializer): def validate(self, attrs): if 'project' in self.fields and 'playbook' in self.fields: - project = attrs.get('project', None) - playbook = attrs.get('playbook', '') - if not project and attrs.get('job_type') != PERM_INVENTORY_SCAN: + project = attrs.get('project', self.instance and self.instance.project or None) + playbook = attrs.get('playbook', self.instance and self.instance.playbook or '') + job_type = attrs.get('job_type', self.instance and self.instance.job_type or None) + if not project and job_type != PERM_INVENTORY_SCAN: raise serializers.ValidationError({'project': 'This field is required.'}) if project and playbook and force_text(playbook) not in project.playbooks: raise serializers.ValidationError({'playbook': 'Playbook not found for project'}) @@ -1578,8 +1585,8 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer): return d def validate(self, attrs): - survey_enabled = attrs.get('survey_enabled', False) - job_type = attrs.get('job_type', None) + survey_enabled = attrs.get('survey_enabled', self.instance and self.instance.survey_enabled or False) + job_type = attrs.get('job_type', self.instance and self.instance.job_type or None) if survey_enabled and job_type == PERM_INVENTORY_SCAN: raise serializers.ValidationError({'survey_enabled': 'Survey Enabled can not be used with scan jobs'}) @@ -1737,8 +1744,8 @@ class AdHocCommandSerializer(UnifiedJobSerializer): def get_field_names(self, declared_fields, info): field_names = super(AdHocCommandSerializer, self).get_field_names(declared_fields, info) - # Meta inheritance and -field_name options don't seem to be taking - # effect above, so remove the undesired fields here. + # Meta multiple inheritance and -field_name options don't seem to be + # taking effect above, so remove the undesired fields here. return tuple(x for x in field_names if x not in ('unified_job_template', 'description')) def build_standard_field(self, field_name, model_field): @@ -1770,19 +1777,7 @@ class AdHocCommandSerializer(UnifiedJobSerializer): return res def to_representation(self, obj): - # In raw data form, populate limit field from host/group name. - view = self.context.get('view', None) - parent_model = getattr(view, 'parent_model', None) - if not (obj and obj.pk) and view and hasattr(view, '_raw_data_form_marker'): - if not obj: - obj = self.Meta.model() ret = super(AdHocCommandSerializer, self).to_representation(obj) - # Hide inventory and limit fields from raw data, since they will be set - # automatically by sub list create view. - if not (obj and obj.pk) and view and hasattr(view, '_raw_data_form_marker'): - if parent_model in (Host, Group): - ret.pop('inventory', None) - ret.pop('limit', None) if 'inventory' in ret and (not obj.inventory or not obj.inventory.active): ret['inventory'] = None if 'credential' in ret and (not obj.credential or not obj.credential.active): @@ -1993,7 +1988,7 @@ class JobLaunchSerializer(BaseSerializer): obj = self.context.get('obj') data = self.context.get('data') - credential = attrs.get('credential', None) or (obj and obj.credential) + credential = attrs.get('credential', obj and obj.credential or None) if not credential or not credential.active: errors['credential'] = 'Credential not provided' diff --git a/awx/api/views.py b/awx/api/views.py index 9a41e779ea..aebe4e0a91 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -568,8 +568,21 @@ class AuthTokenView(APIView): serializer_class = AuthTokenSerializer model = AuthToken + def get_serializer(self, *args, **kwargs): + serializer = self.serializer_class(*args, **kwargs) + # Override when called from browsable API to generate raw data form; + # update serializer "validated" data to be displayed by the raw data + # form. + if hasattr(self, '_raw_data_form_marker'): + # Always remove read only fields from serializer. + for name, field in serializer.fields.items(): + if getattr(field, 'read_only', None): + del serializer.fields[name] + serializer._data = self.update_raw_data(serializer.data) + return serializer + def post(self, request): - serializer = self.serializer_class(data=request.data) + serializer = self.get_serializer(data=request.data) if serializer.is_valid(): request_hash = AuthToken.get_request_hash(self.request) try: @@ -1178,6 +1191,19 @@ class HostGroupsList(SubListCreateAttachDetachAPIView): parent_model = Host relationship = 'groups' + def update_raw_data(self, data): + data.pop('inventory', None) + return super(HostGroupsList, self).update_raw_data(data) + + def create(self, request, *args, **kwargs): + # Inject parent host inventory ID into new group data. + data = request.data + # HACK: Make request data mutable. + if getattr(data, '_mutable', None) is False: + data._mutable = True + data['inventory'] = self.get_parent_object().inventory_id + return super(HostGroupsList, self).create(request, *args, **kwargs) + class HostAllGroupsList(SubListAPIView): ''' the list of all groups of which the host is directly or indirectly a member ''' @@ -1334,6 +1360,19 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView): parent_model = Group relationship = 'children' + def update_raw_data(self, data): + data.pop('inventory', None) + return super(GroupChildrenList, self).update_raw_data(data) + + def create(self, request, *args, **kwargs): + # Inject parent group inventory ID into new group data. + data = request.data + # HACK: Make request data mutable. + if getattr(data, '_mutable', None) is False: + data._mutable = True + data['inventory'] = self.get_parent_object().inventory_id + return super(GroupChildrenList, self).create(request, *args, **kwargs) + def unattach(self, request, *args, **kwargs): sub_id = request.data.get('id', None) if sub_id is not None: @@ -1394,8 +1433,14 @@ class GroupHostsList(SubListCreateAttachDetachAPIView): parent_model = Group relationship = 'hosts' + def update_raw_data(self, data): + data.pop('inventory', None) + return super(GroupHostsList, self).update_raw_data(data) + def create(self, request, *args, **kwargs): parent_group = Group.objects.get(id=self.kwargs['pk']) + # Inject parent group inventory ID into new host data. + request.data['inventory'] = parent_group.inventory_id existing_hosts = Host.objects.filter(inventory=parent_group.inventory, name=request.data['name']) if existing_hosts.count() > 0 and ('variables' not in request.data or request.data['variables'] == '' or @@ -2583,6 +2628,15 @@ class AdHocCommandList(ListCreateAPIView): def dispatch(self, *args, **kwargs): return super(AdHocCommandList, self).dispatch(*args, **kwargs) + def update_raw_data(self, data): + # Hide inventory and limit fields from raw data, since they will be set + # automatically by sub list create view. + parent_model = getattr(self, 'parent_model', None) + if parent_model in (Host, Group): + data.pop('inventory', None) + data.pop('limit', None) + return super(AdHocCommandList, self).update_raw_data(data) + def create(self, request, *args, **kwargs): # Inject inventory ID and limit if parent objects is a host/group. if hasattr(self, 'get_parent_object') and not getattr(self, 'parent_key', None): diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 36f39ac3ec..d56c16fbef 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -216,7 +216,7 @@ REST_FRAMEWORK = { 'awx.api.filters.OrderByBackend', ), 'DEFAULT_PARSER_CLASSES': ( - 'rest_framework.parsers.JSONParser', + 'awx.api.parsers.JSONParser', ), 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', From 99150b5a05bdd37e2baa53a8d09848ef5e3073e5 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 16 Feb 2016 20:02:52 -0500 Subject: [PATCH 27/57] Flake8 fixes. --- awx/api/generics.py | 3 +-- awx/api/serializers.py | 2 +- awx/api/views.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/awx/api/generics.py b/awx/api/generics.py index 605c824468..e8c6a1874b 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -2,7 +2,6 @@ # All Rights Reserved. # Python -from collections import OrderedDict import inspect import logging import time @@ -189,7 +188,7 @@ class GenericAPIView(generics.GenericAPIView, APIView): # Always remove read only fields from serializer. for name, field in serializer.fields.items(): if getattr(field, 'read_only', None): - del serializer.fields[name] + del serializer.fields[name] serializer._data = self.update_raw_data(serializer.data) return serializer diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 1c8fd17bd8..0b0526a0ee 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1251,7 +1251,7 @@ class InventorySourceOptionsSerializer(BaseSerializer): source = attrs.get('source', self.instance and self.instance.source or '') source_script = attrs.get('source_script', self.instance and self.instance.source_script or '') if source == 'custom': - if not source_script is None or source_script == '': + if source_script is None or source_script == '': errors['source_script'] = 'source_script must be provided' else: try: diff --git a/awx/api/views.py b/awx/api/views.py index aebe4e0a91..4b316ffd77 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -577,7 +577,7 @@ class AuthTokenView(APIView): # Always remove read only fields from serializer. for name, field in serializer.fields.items(): if getattr(field, 'read_only', None): - del serializer.fields[name] + del serializer.fields[name] serializer._data = self.update_raw_data(serializer.data) return serializer From d8ae1115f09a0990b76e74ca051663176be328cb Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 17 Feb 2016 00:04:28 -0500 Subject: [PATCH 28/57] Fix extra_vars/survey handling since request.data is now an OrderedDict. --- awx/api/serializers.py | 22 +++++++++------------- awx/api/views.py | 6 +++--- awx/main/models/jobs.py | 6 +++--- awx/main/models/unified_jobs.py | 2 +- awx/main/tests/old/projects.py | 1 + 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 0b0526a0ee..ea0fb065f9 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -8,7 +8,6 @@ import re import logging from collections import OrderedDict from dateutil import rrule -from ast import literal_eval from rest_framework_mongoengine.serializers import DocumentSerializer @@ -1955,6 +1954,7 @@ class JobLaunchSerializer(BaseSerializer): variables_needed_to_start = serializers.ReadOnlyField() credential_needed_to_start = serializers.SerializerMethodField() survey_enabled = serializers.SerializerMethodField() + extra_vars = VerbatimField(required=False) class Meta: model = JobTemplate @@ -2001,20 +2001,16 @@ class JobLaunchSerializer(BaseSerializer): except KeyError: errors['passwords_needed_to_start'] = credential.passwords_needed - extra_vars = force_text(attrs.get('extra_vars', {})) - try: - extra_vars = literal_eval(extra_vars) - extra_vars = json.dumps(extra_vars) - except Exception: - pass + extra_vars = attrs.get('extra_vars', {}) - try: - extra_vars = json.loads(extra_vars) - except (ValueError, TypeError): + if isinstance(extra_vars, basestring): try: - extra_vars = yaml.safe_load(extra_vars) - except (yaml.YAMLError, TypeError, AttributeError): - errors['extra_vars'] = 'Must be valid JSON or YAML' + extra_vars = json.loads(extra_vars) + except (ValueError, TypeError): + try: + extra_vars = yaml.safe_load(extra_vars) + except (yaml.YAMLError, TypeError, AttributeError): + errors['extra_vars'] = 'Must be valid JSON or YAML' if not isinstance(extra_vars, dict): extra_vars = {} diff --git a/awx/api/views.py b/awx/api/views.py index 4b316ffd77..65f4f9b965 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -226,7 +226,7 @@ class ApiV1ConfigView(APIView): def post(self, request): if not request.user.is_superuser: return Response(None, status=status.HTTP_404_NOT_FOUND) - if not type(request.data) == dict: + if not isinstance(request.data, dict): return Response({"error": "Invalid license data"}, status=status.HTTP_400_BAD_REQUEST) if "eula_accepted" not in request.data: return Response({"error": "Missing 'eula_accepted' property"}, status=status.HTTP_400_BAD_REQUEST) @@ -1941,13 +1941,13 @@ class JobTemplateSurveySpec(GenericAPIView): return Response(dict(error="'description' missing from survey spec"), status=status.HTTP_400_BAD_REQUEST) if "spec" not in obj.survey_spec: return Response(dict(error="'spec' missing from survey spec"), status=status.HTTP_400_BAD_REQUEST) - if type(obj.survey_spec["spec"]) != list: + if not isinstance(obj.survey_spec["spec"], list): return Response(dict(error="'spec' must be a list of items"), status=status.HTTP_400_BAD_REQUEST) if len(obj.survey_spec["spec"]) < 1: return Response(dict(error="'spec' doesn't contain any items"), status=status.HTTP_400_BAD_REQUEST) idx = 0 for survey_item in obj.survey_spec["spec"]: - if type(survey_item) != dict: + if not isinstance(survey_item, dict): return Response(dict(error="survey element %s is not a json object" % str(idx)), status=status.HTTP_400_BAD_REQUEST) if "type" not in survey_item: return Response(dict(error="'type' missing from survey element %s" % str(idx)), status=status.HTTP_400_BAD_REQUEST) diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 833d20a9b4..facb0f32b4 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -307,7 +307,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions): except Exception: try: kwargs_extra_vars = yaml.safe_load(kwargs_extra_vars) - assert type(kwargs_extra_vars) is dict + assert isinstance(kwargs_extra_vars, dict) except: kwargs_extra_vars = {} else: @@ -487,7 +487,7 @@ class Job(UnifiedJob, JobOptions): def handle_extra_data(self, extra_data): extra_vars = {} - if type(extra_data) == dict: + if isinstance(extra_data, dict): extra_vars = extra_data elif extra_data is None: return @@ -1070,7 +1070,7 @@ class SystemJob(UnifiedJob, SystemJobOptions): def handle_extra_data(self, extra_data): extra_vars = {} - if type(extra_data) == dict: + if isinstance(extra_data, dict): extra_vars = extra_data elif extra_data is None: return diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 86ab0b3143..9b4be868c3 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -321,7 +321,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique): value = value.id create_kwargs[id_field_name] = value elif field_name in kwargs: - if field_name == 'extra_vars' and type(kwargs[field_name]) == dict: + if field_name == 'extra_vars' and isinstance(kwargs[field_name], dict): create_kwargs[field_name] = json.dumps(kwargs['extra_vars']) else: create_kwargs[field_name] = kwargs[field_name] diff --git a/awx/main/tests/old/projects.py b/awx/main/tests/old/projects.py index 45c96ca587..427f3da55f 100644 --- a/awx/main/tests/old/projects.py +++ b/awx/main/tests/old/projects.py @@ -758,6 +758,7 @@ class ProjectsTest(BaseTransactionTest): team_permission['name'] += '2' team_permission['user'] = user.pk self.post(url, team_permission, expect=400, auth=self.get_super_credentials()) + del team_permission['user'] # can list permissions on a user url = reverse('api:user_permissions_list', args=(user.pk,)) From 6bd5d8553b6db0bddfb84c7cccd2e1df28eadb13 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Tue, 16 Feb 2016 08:29:05 -0500 Subject: [PATCH 29/57] fix to allow jobs to run in dev server again --- awx/plugins/inventory/awxrest.py | 5 ++++- awx/settings/defaults.py | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/awx/plugins/inventory/awxrest.py b/awx/plugins/inventory/awxrest.py index 9aec595eff..1f3630b6a6 100755 --- a/awx/plugins/inventory/awxrest.py +++ b/awx/plugins/inventory/awxrest.py @@ -133,7 +133,10 @@ class InventoryScript(object): else: sys.stderr.write('%s\n' % str(e)) if hasattr(e, 'response'): - sys.stderr.write('%s\n' % e.response.content) + if hasattr(e.response, 'content'): + sys.stderr.write('%s\n' % e.response.content) + else: + sys.stderr.write('%s\n' % e.response) sys.exit(1) def main(): diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 36f39ac3ec..7a1fb02a9e 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -662,10 +662,7 @@ ACTIVITY_STREAM_ENABLED = True ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC = False # Internal API URL for use by inventory scripts and callback plugin. -if 'devserver' in INSTALLED_APPS: - INTERNAL_API_URL = 'http://127.0.0.1:%s' % DEVSERVER_DEFAULT_PORT -else: - INTERNAL_API_URL = 'http://127.0.0.1:8000' +INTERNAL_API_URL = 'http://127.0.0.1:%s' % DEVSERVER_DEFAULT_PORT # ZeroMQ callback settings. CALLBACK_CONSUMER_PORT = "tcp://127.0.0.1:5556" From eae92f8751b9c125841061cb0c51dc1d834ee1fd Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 18 Feb 2016 13:02:02 -0500 Subject: [PATCH 30/57] delete management-jobs/schedule, create management-jobs/scheduler, add ui-router states, update references to state names #696 prep for isFactCleanup test in the view - change the way Schedule.js AddSchedule factory recognizes a management job #696 --- awx/ui/client/src/helpers/Schedules.js | 15 +- .../management-jobs/card/card.controller.js | 8 +- .../management-jobs/card/card.partial.html | 2 +- awx/ui/client/src/management-jobs/main.js | 4 +- .../src/management-jobs/schedule/main.js | 15 - .../schedule/schedule.controller.js | 89 --- .../schedule/schedule.partial.html | 6 - .../schedule/schedule.route.js | 51 -- .../src/management-jobs/scheduler/main.js | 52 ++ .../scheduler/schedulerForm.partial.html | 652 ++++++++++++++++++ .../src/scheduler/scheduler.controller.js | 10 +- .../src/scheduler/schedulerAdd.controller.js | 4 +- .../src/scheduler/schedulerEdit.controller.js | 4 +- 13 files changed, 733 insertions(+), 179 deletions(-) delete mode 100644 awx/ui/client/src/management-jobs/schedule/main.js delete mode 100644 awx/ui/client/src/management-jobs/schedule/schedule.controller.js delete mode 100644 awx/ui/client/src/management-jobs/schedule/schedule.partial.html delete mode 100644 awx/ui/client/src/management-jobs/schedule/schedule.route.js create mode 100644 awx/ui/client/src/management-jobs/scheduler/main.js create mode 100644 awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html diff --git a/awx/ui/client/src/helpers/Schedules.js b/awx/ui/client/src/helpers/Schedules.js index 0924170311..14e62cdd4f 100644 --- a/awx/ui/client/src/helpers/Schedules.js +++ b/awx/ui/client/src/helpers/Schedules.js @@ -214,19 +214,20 @@ export default return function(params) { var scope = params.scope, callback= params.callback, - base = $location.path().replace(/^\//, '').split('/')[0], + base = params.base || $location.path().replace(/^\//, '').split('/')[0], url = GetBasePath(base), scheduler; - + console.log('AddSchedule $stateParams: ', $stateParams) if (!Empty($stateParams.template_id)) { url += $stateParams.template_id + '/schedules/'; } - else if (!Empty($stateParams.id)) { + else if (!Empty($stateParams.id) && base != 'system_job_templates') { url += $stateParams.id + '/schedules/'; } - else if (!Empty($stateParams.management_job)) { - url += $stateParams.management_job + '/schedules/'; - if(scope.management_job.id === 4){ + else if (base == 'system_job_templates') { + console.log('at least we know its a mgmt job!') + url += $stateParams.id + '/schedules/'; + if(scope.id === 4){ scope.isFactCleanup = true; scope.keep_unit_choices = [{ "label" : "Days", @@ -538,7 +539,7 @@ export default var scope = params.scope, parent_scope = params.parent_scope, iterator = (params.iterator) ? params.iterator : scope.iterator, - base = $location.path().replace(/^\//, '').split('/')[0]; + base = params.base || $location.path().replace(/^\//, '').split('/')[0]; scope.toggleSchedule = function(event, id) { try { 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 e9d5c4409c..5d32ae3e32 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -24,6 +24,7 @@ export default Rest.setUrl(defaultUrl); Rest.get() .success(function(data){ + console.log(data) $scope.mgmtCards = data.results; Wait('stop'); }) @@ -238,10 +239,9 @@ export default } }; - $scope.configureSchedule = function() { - $state.transitionTo('managementJobsSchedule', { - management_job: this.job_type, - management_job_id: this.card.id + $scope.configureSchedule = function(id) { + $state.transitionTo('managementJobSchedules', { + 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 3b4cfec9ba..17332be2bc 100644 --- a/awx/ui/client/src/management-jobs/card/card.partial.html +++ b/awx/ui/client/src/management-jobs/card/card.partial.html @@ -11,7 +11,7 @@ diff --git a/awx/ui/client/src/management-jobs/main.js b/awx/ui/client/src/management-jobs/main.js index b374b84857..35e6c10c76 100644 --- a/awx/ui/client/src/management-jobs/main.js +++ b/awx/ui/client/src/management-jobs/main.js @@ -5,12 +5,12 @@ *************************************************/ import managementJobsCard from './card/main'; -import managementJobsSchedule from './schedule/main'; +import managementJobsScheduler from './scheduler/main'; import list from './management-jobs.list'; export default angular.module('managementJobs', [ managementJobsCard.name, - managementJobsSchedule.name + managementJobsScheduler.name ]) .factory('managementJobsListObject', list); diff --git a/awx/ui/client/src/management-jobs/schedule/main.js b/awx/ui/client/src/management-jobs/schedule/main.js deleted file mode 100644 index e71036c9d6..0000000000 --- a/awx/ui/client/src/management-jobs/schedule/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './schedule.route'; -import controller from './schedule.controller'; - -export default - angular.module('managementJobsSchedule', []) - .controller('managementJobsScheduleController', controller) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/management-jobs/schedule/schedule.controller.js b/awx/ui/client/src/management-jobs/schedule/schedule.controller.js deleted file mode 100644 index c83b378674..0000000000 --- a/awx/ui/client/src/management-jobs/schedule/schedule.controller.js +++ /dev/null @@ -1,89 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Schedules - * @description This controller's for schedules -*/ - -export default [ - '$scope', '$location', '$stateParams', 'SchedulesList', 'Rest', - 'ProcessErrors', 'GetBasePath', 'Wait','LoadSchedulesScope', 'GetChoices', - 'management_job', '$rootScope', - function($scope, $location, $stateParams, SchedulesList, Rest, - ProcessErrors, GetBasePath, Wait, LoadSchedulesScope, GetChoices, - management_job, $rootScope) { - var base, id, url, parentObject; - $scope.management_job = management_job; - base = $location.path().replace(/^\//, '').split('/')[0]; - - // GetBasePath('management_job') must map to 'system_job_templates' - // to match the api syntax - $rootScope.defaultUrls.management_jobs = 'api/v1/system_job_templates/'; - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - $scope.removePostRefresh = $scope.$on('PostRefresh', function() { - var list = $scope.schedules; - list.forEach(function(element, idx) { - list[idx].play_tip = (element.enabled) ? 'Schedule is Active.'+ - ' Click to temporarily stop.' : 'Schedule is temporarily '+ - 'stopped. Click to activate.'; - }); - }); - - if ($scope.removeParentLoaded) { - $scope.removeParentLoaded(); - } - $scope.removeParentLoaded = $scope.$on('ParentLoaded', function() { - url += "schedules/"; - SchedulesList.well = true; - LoadSchedulesScope({ - parent_scope: $scope, - scope: $scope, - list: SchedulesList, - id: 'management_jobs_schedule', - url: url, - pageSize: 20 - }); - }); - - if ($scope.removeChoicesReady) { - $scope.removeChocesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - // Load the parent object - id = $stateParams.management_job_id; - url = GetBasePath('system_job_templates') + id + '/'; - Rest.setUrl(url); - Rest.get() - .success(function(data) { - parentObject = data; - $scope.$emit('ParentLoaded'); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status }); - }); - }); - - $scope.refreshJobs = function() { - $scope.search(SchedulesList.iterator); - }; - - Wait('start'); - - GetChoices({ - scope: $scope, - url: GetBasePath('system_jobs'), - field: 'type', - variable: 'type_choices', - callback: 'choicesReady' - }); - } -]; diff --git a/awx/ui/client/src/management-jobs/schedule/schedule.partial.html b/awx/ui/client/src/management-jobs/schedule/schedule.partial.html deleted file mode 100644 index 43b01efc55..0000000000 --- a/awx/ui/client/src/management-jobs/schedule/schedule.partial.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
-
- -
-
diff --git a/awx/ui/client/src/management-jobs/schedule/schedule.route.js b/awx/ui/client/src/management-jobs/schedule/schedule.route.js deleted file mode 100644 index 837d6dfb86..0000000000 --- a/awx/ui/client/src/management-jobs/schedule/schedule.route.js +++ /dev/null @@ -1,51 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; - -export default { - name: 'managementJobsSchedule', - route: '/management_jobs/:management_job_id/schedules', - templateUrl: templateUrl('management-jobs/schedule/schedule'), - controller: 'managementJobsScheduleController', - data: { - activityStream: true, - activityStreamTarget: 'schedule' - }, - params: {management_job: null}, - resolve: { - features: ['FeaturesService', function(FeaturesService) { - return FeaturesService.get(); - }], - management_job: - [ '$stateParams', - '$q', - 'Rest', - 'GetBasePath', - 'ProcessErrors', - function($stateParams, $q, rest, getBasePath, ProcessErrors) { - if ($stateParams.management_job) { - return $q.when($stateParams.management_job); - } - - var managementJobId = $stateParams.management_job_id; - - var url = getBasePath('system_job_templates') + managementJobId + '/'; - rest.setUrl(url); - 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 script info. GET returned status: ' + - response.status - }); - }); - } - ] - } -}; diff --git a/awx/ui/client/src/management-jobs/scheduler/main.js b/awx/ui/client/src/management-jobs/scheduler/main.js new file mode 100644 index 0000000000..49498debbd --- /dev/null +++ b/awx/ui/client/src/management-jobs/scheduler/main.js @@ -0,0 +1,52 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + +import {templateUrl} from '../../shared/template-url/template-url.factory'; +import controller from '../../scheduler/scheduler.controller'; +import addController from '../../scheduler/schedulerAdd.controller'; +import editController from '../../scheduler/schedulerEdit.controller'; + +export default + angular.module('managementJobScheduler', []) + .controller('managementJobController', controller) + .controller('managementJobAddController', addController) + .controller('managementJobEditController', editController) + .run(['$stateExtender', function($stateExtender){ + $stateExtender.addState({ + name: 'managementJobSchedules', + route: '/management_jobs/:id/schedules', + templateUrl: templateUrl('scheduler/scheduler'), + controller: 'managementJobController', + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }); + $stateExtender.addState({ + name: 'managementJobSchedules.add', + route: '/add', + templateUrl: templateUrl('management-jobs/scheduler/schedulerForm'), + controller: 'managementJobAddController', + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }); + $stateExtender.addState({ + name: 'managementJobSchedules.edit', + route: '/add', + templateUrl: templateUrl('management-jobs/scheduler/schedulerForm'), + controller: 'managementJobEditController', + resolve: { + features: ['FeaturesService', function(FeaturesService){ + return FeaturesService.get(); + }] + } + }); + }]); \ No newline at end of file diff --git a/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html b/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html new file mode 100644 index 0000000000..a838db96a5 --- /dev/null +++ b/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html @@ -0,0 +1,652 @@ +
+
+
{{ schedulerName || "Add Schedule"}}
+
{{ schedulerName || "Edit Schedule"}}
+
+ +
+
+
+ +
+ +
+ + +
+ A schedule name is required. +
+
+
+ +
+ + + + +
+
+
+
+
+ +
+ + + : + + + + : + + + +
+
+ The time must be in HH24:MM:SS format. +
+
+
+ + +
+
+ + +
+
+
+
+ Frequency Details
+
+
+ + + +
+ Please provide a value between 1 and 999. +
+
+
+
+ +
+ +
+ The day must be between 1 and 31. +
+
+
+
+ +
+
+ + +
+
+
+
+ * + +
+
+ + +
+
+ The day must be between 1 and 31. +
+
+
+
+ +
+
+ + + +
+
+
+ +
+
+ + + + + + + +
+
+
+ Please select one or more days. +
+
+
+ +
+ +
+
+
+ + +
+ Please provide a value between 1 and 999. +
+
+
+ +
+ + + + +
+
+ Please provide a valid date. +
+
+
+
+
+ +
+ Note: For facts collected older than the time period specified, save one fact scan (snapshot) per time window (frequency). For example, facts older than 30 days are purged, while one weekly fact scan is kept. + Caution: Setting both numerical variables to "0" will delete all facts.
+ +
+ + +
A value is required.
+
This is not a valid number.
+
+ +
+
+ +
+
+ +
Please enter the number of days you would like to keep this data.
+
Please enter a valid number.
+
Please enter a non-negative number.
+
Please enter a number smaller than 9999.
+
+
+ +
+
+ +
+
+ +
+
+ +
Please enter the number of days you would like to keep this data.
+
Please enter a valid number.
+
Please enter a non-negative number.
+
Please enter a number smaller than 9999.
+
+
+ +
+
+ +
+
+

+ The scheduler options are invalid or incomplete. +

+
+
+ +
+ {{ rrule_nlp_description }} +
+
+ +
+ + + +
+
+
    +
  • + {{ occurrence.utc }} +
  • +
+
    +
  • + {{ occurrence.local }} +
  • +
+
+ +
+ + +
+
+
diff --git a/awx/ui/client/src/scheduler/scheduler.controller.js b/awx/ui/client/src/scheduler/scheduler.controller.js index d53ea19e28..af7a606168 100644 --- a/awx/ui/client/src/scheduler/scheduler.controller.js +++ b/awx/ui/client/src/scheduler/scheduler.controller.js @@ -19,11 +19,16 @@ export default [ GetBasePath, Wait, Find, LoadDialogPartial, LoadSchedulesScope, GetChoices) { ClearScope(); + console.log($stateParams) var base, e, id, url, parentObject; - base = $location.path().replace(/^\//, '').split('/')[0]; - + if (base == 'management_jobs') { + $scope.base = base = 'system_job_templates'; + } + if ($stateParams.job_type){ + $scope.job_type = $stateParams.job_type; + } if ($scope.removePostRefresh) { $scope.removePostRefresh(); } @@ -87,4 +92,5 @@ export default [ variable: 'type_choices', callback: 'choicesReady' }); + console.log($scope) }]; diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index 26be29c03a..6e3ec321e9 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -6,6 +6,7 @@ export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$s $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); + console.log('schedulerAdd.controller $scope: ', $scope) $scope.$watchGroup(["schedulerName", "schedulerStartDt", @@ -47,7 +48,8 @@ export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$s AddSchedule({ scope: $scope, - callback: 'SchedulesRefresh' + callback: 'SchedulesRefresh', + base: $scope.base ? $scope.base : null }); var callSelect2 = function() { diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index fb8d8052b5..d5754c4acb 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -6,6 +6,7 @@ export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$ $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); + console.log($scope) $scope.$watchGroup(["schedulerName", "schedulerStartDt", @@ -51,7 +52,8 @@ export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$ EditSchedule({ scope: $scope, id: parseInt($stateParams.schedule_id), - callback: 'SchedulesRefresh' + callback: 'SchedulesRefresh', + base: $scope.base ? $scope.base: null }); var callSelect2 = function() { From 247c78c44d217b9cc5877da96dc733f1acbe343e Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 19 Feb 2016 10:59:58 -0500 Subject: [PATCH 31/57] #696 clean up logging --- awx/ui/client/src/helpers/Schedules.js | 4 +--- awx/ui/client/src/scheduler/scheduler.controller.js | 2 -- awx/ui/client/src/scheduler/schedulerAdd.controller.js | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/awx/ui/client/src/helpers/Schedules.js b/awx/ui/client/src/helpers/Schedules.js index 14e62cdd4f..9b7566e678 100644 --- a/awx/ui/client/src/helpers/Schedules.js +++ b/awx/ui/client/src/helpers/Schedules.js @@ -217,7 +217,6 @@ export default base = params.base || $location.path().replace(/^\//, '').split('/')[0], url = GetBasePath(base), scheduler; - console.log('AddSchedule $stateParams: ', $stateParams) if (!Empty($stateParams.template_id)) { url += $stateParams.template_id + '/schedules/'; } @@ -225,9 +224,8 @@ export default url += $stateParams.id + '/schedules/'; } else if (base == 'system_job_templates') { - console.log('at least we know its a mgmt job!') url += $stateParams.id + '/schedules/'; - if(scope.id === 4){ + if($stateParams.id == 4){ scope.isFactCleanup = true; scope.keep_unit_choices = [{ "label" : "Days", diff --git a/awx/ui/client/src/scheduler/scheduler.controller.js b/awx/ui/client/src/scheduler/scheduler.controller.js index af7a606168..d1209b91da 100644 --- a/awx/ui/client/src/scheduler/scheduler.controller.js +++ b/awx/ui/client/src/scheduler/scheduler.controller.js @@ -19,7 +19,6 @@ export default [ GetBasePath, Wait, Find, LoadDialogPartial, LoadSchedulesScope, GetChoices) { ClearScope(); - console.log($stateParams) var base, e, id, url, parentObject; base = $location.path().replace(/^\//, '').split('/')[0]; @@ -92,5 +91,4 @@ export default [ variable: 'type_choices', callback: 'choicesReady' }); - console.log($scope) }]; diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index 6e3ec321e9..a278af8a28 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -6,7 +6,6 @@ export default ['$compile', '$state', '$stateParams', 'AddSchedule', 'Wait', '$s $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); - console.log('schedulerAdd.controller $scope: ', $scope) $scope.$watchGroup(["schedulerName", "schedulerStartDt", From 5f75fa9b467575cb49186d3552aae6a8058f7a1d Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Fri, 19 Feb 2016 12:04:09 -0500 Subject: [PATCH 32/57] #696 remove logging pt. 2 --- awx/ui/client/src/management-jobs/card/card.controller.js | 1 - awx/ui/client/src/scheduler/schedulerEdit.controller.js | 1 - 2 files changed, 2 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 5d32ae3e32..b74926f652 100644 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ b/awx/ui/client/src/management-jobs/card/card.controller.js @@ -24,7 +24,6 @@ export default Rest.setUrl(defaultUrl); Rest.get() .success(function(data){ - console.log(data) $scope.mgmtCards = data.results; Wait('stop'); }) diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index d5754c4acb..05e9d7eef3 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -6,7 +6,6 @@ export default ['$compile', '$state', '$stateParams', 'EditSchedule', 'Wait', '$ $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); - console.log($scope) $scope.$watchGroup(["schedulerName", "schedulerStartDt", From 5c049d37a65c9d60b9db56dd34449b9471b1a8ae Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Mon, 22 Feb 2016 10:00:50 -0500 Subject: [PATCH 33/57] Lookup modal styling to match given design --- awx/ui/client/legacy-styles/ansible-ui.less | 5 - .../legacy-styles/jquery-ui-overrides.less | 62 ++++++++++++- awx/ui/client/legacy-styles/lists.less | 42 +++++++++ awx/ui/client/src/helpers/Lookup.js | 23 +++-- awx/ui/client/src/shared/Modal.js | 2 +- .../src/shared/branding/colors.default.less | 1 + awx/ui/client/src/shared/branding/colors.less | 1 + .../list-generator/list-generator.factory.js | 92 +++++++++++-------- 8 files changed, 169 insertions(+), 59 deletions(-) diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index ec58ca474f..028529fffa 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -697,11 +697,6 @@ legend { margin-bottom: 7px; } - .pagination > li > a { - padding: 3px 6px; - font-size: 10px; - } - .modal-body { .pagination { margin-top: 15px; diff --git a/awx/ui/client/legacy-styles/jquery-ui-overrides.less b/awx/ui/client/legacy-styles/jquery-ui-overrides.less index c21dadb3c6..c40440bf8a 100644 --- a/awx/ui/client/legacy-styles/jquery-ui-overrides.less +++ b/awx/ui/client/legacy-styles/jquery-ui-overrides.less @@ -14,12 +14,13 @@ table.ui-datepicker-calendar { } /* Modal dialog */ - .ui-dialog-title { - font-size: 22px; - color: @blue-link; + font-size: 15px; + color: @default-interface-txt; font-weight: bold; line-height: normal; + font-family: 'Open Sans', helvetica; + text-transform: uppercase; } .ui-dialog { .close { @@ -33,12 +34,14 @@ table.ui-datepicker-calendar { .ui-widget-header { border-radius: 0; border: none; - border-bottom: 1px solid #A9A9A9; - height: 55px; } .ui-dialog-titlebar { padding-bottom: 0; padding-top: 12px; + + button.close i { + font-size: 24px; + } } .ui-dialog-titlebar .ui-state-default { background-image: none; @@ -58,9 +61,58 @@ table.ui-datepicker-calendar { background-position: -80px -224px; color: @black; } + + button.btn.btn-primary, + button.btn.btn-default { + padding: 5px 15px; + font-size: 12px; + line-height: 1.5; + transition: background-color 0.2s; + font-size: 12px; + } + + button.btn.btn-primary { + text-transform: uppercase; + background-color: @default-succ; + border-color: @default-succ; + + &:hover { + background-color: @default-succ-hov; + border-color: @default-succ-hov; + } + + &:disabled { + background-color: @default-succ-disabled; + border-color: @default-succ-disabled; + } + + &:first-of-type { + margin-right: 24px; + } + } + + button.btn.btn-default { + text-transform: uppercase; + border-color: @default-border; + color: @default-interface-txt; + } + + .ui-dialog-buttonpane.ui-widget-content { + border: none; + margin: 0; + padding-top: 0; + padding-right: 8px; + } + + .input-group-btn.dropdown, .List-pagination, .List-tableHeader { + font-family: 'Open Sans', sans-serif; + } + } .ui-dialog-buttonset { + text-transform: uppercase; + button.btn.btn-default.ui-state-hover, button.btn.btn-default.ui-state-active, button.btn.btn-default.ui-state-focus { diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 020d306dfc..9db2792d2b 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -101,6 +101,8 @@ table, tbody { margin-left: 15px; } + +/* -- Pagination -- */ .List-pagination { margin-top: 20px; font-size: 12px; @@ -108,6 +110,23 @@ table, tbody { text-transform: uppercase; height: 22px; display: flex; + + .pagination>li>a, + .pagination>li>span { + border: 1px solid @grey-border; + padding: 3px 6px; + font-size: 10px; + } + + .pagination li { + a#next-page { + border-radius: 0 4px 4px 0; + } + + a#previous-page { + border-radius: 4px 0 0 4px; + } + } } .List-paginationPagerHolder { @@ -244,6 +263,7 @@ table, tbody { padding-left: 15px!important; height: 34px!important; padding-right: 45px!important; + font-family: 'Open Sans', sans-serif; } .List-searchInput:placeholder-shown { @@ -348,3 +368,25 @@ table, tbody { display: block; font-size: 13px; } + +#lookup-modal-dialog { + + .List-searchWidget { + width: 66.6666% + } + + .List-tableHeaderRow { + .List-tableHeader:first-of-type { + width: 3%; + } + } + + .List-tableHeader, + .List-tableHeader:last-of-type { + text-align:left; + } + + .List-tableCell { + color: @default-interface-txt; + } +} diff --git a/awx/ui/client/src/helpers/Lookup.js b/awx/ui/client/src/helpers/Lookup.js index 0b92d43d75..d23614de3c 100644 --- a/awx/ui/client/src/helpers/Lookup.js +++ b/awx/ui/client/src/helpers/Lookup.js @@ -132,7 +132,6 @@ export default master[field] = scope[field]; master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; - GenerateList.inject(list, { mode: 'lookup', id: 'lookup-modal-dialog', @@ -145,22 +144,22 @@ export default hdr = (params.hdr) ? params.hdr : 'Select ' + name; // Show pop-up - buttons = [{ + buttons = [ + { + label: "Save", + onClick: function() { + scope.selectAction(); + }, + //icon: "fa-check", + "class": "btn btn-primary", + "id": "lookup-save-button" + },{ label: "Cancel", - icon: "fa-times", "class": "btn btn-default", "id": "lookup-cancel-button", onClick: function() { $('#lookup-modal-dialog').dialog('close'); } - },{ - label: "Select", - onClick: function() { - scope.selectAction(); - }, - icon: "fa-check", - "class": "btn btn-primary", - "id": "lookup-save-button" }]; if (scope.removeModalReady) { @@ -175,7 +174,7 @@ export default scope: scope, buttons: buttons, width: 600, - height: (instructions) ? 625 : 500, + height: (instructions) ? 625 : 450, minWidth: 500, title: hdr, id: 'lookup-modal-dialog', diff --git a/awx/ui/client/src/shared/Modal.js b/awx/ui/client/src/shared/Modal.js index 2c17b1e14c..37ae34f97a 100644 --- a/awx/ui/client/src/shared/Modal.js +++ b/awx/ui/client/src/shared/Modal.js @@ -106,7 +106,7 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper']) resizable: resizable, create: function () { // Fix the close button - $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).text('x'); + $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).html(''); setTimeout(function() { // Make buttons bootstrapy diff --git a/awx/ui/client/src/shared/branding/colors.default.less b/awx/ui/client/src/shared/branding/colors.default.less index fbde1449ac..dd53943e88 100644 --- a/awx/ui/client/src/shared/branding/colors.default.less +++ b/awx/ui/client/src/shared/branding/colors.default.less @@ -12,6 +12,7 @@ @default-err-hov: #FF1105; @default-succ: #3CB878; @default-succ-hov: #60D66F; +@default-succ-disabled: lighten(@default-succ, 30); @default-link: #1678C4; @default-link-hov: #4498DA; @default-button-hov: #F2F2F2; diff --git a/awx/ui/client/src/shared/branding/colors.less b/awx/ui/client/src/shared/branding/colors.less index 4d9d801584..839b79a174 100644 --- a/awx/ui/client/src/shared/branding/colors.less +++ b/awx/ui/client/src/shared/branding/colors.less @@ -5,6 +5,7 @@ @blue-dark: #2a6496; /* link hover */ @grey: #A9A9A9; @grey-txt: #707070; +@grey-border: #DDDDDD; @info: #d9edf7; /* alert info background color */ @info-border: #bce8f1; /* alert info border color */ @info-color: #3a87ad; 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 304478ab3c..3d974db190 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 @@ -299,44 +299,47 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate list = this.list, base, size, action, fld, cnt, field_action, fAction, itm; - if(options.title !== false){ - html += "
"; - html += "
"; + if (options.mode !== 'lookup') { + if(options.title !== false){ + html += "
"; + html += "
"; - if (list.listTitle) { + if (list.listTitle) { - html += "
" + list.listTitle + "
"; - // We want to show the list title badge by default and only hide it when the list config specifically passes a false flag - list.listTitleBadge = (typeof list.listTitleBadge === 'boolean' && list.listTitleBadge == false) ? false : true; - if(list.listTitleBadge) { - html += "{{(" + list.iterator + "_total_rows | number:0)}}"; - } + html += "
" + list.listTitle + "
"; + // We want to show the list title badge by default and only hide it when the list config specifically passes a false flag + list.listTitleBadge = (typeof list.listTitleBadge === 'boolean' && list.listTitleBadge == false) ? false : true; + if(list.listTitleBadge) { + html += "{{(" + list.iterator + "_total_rows | number:0)}}"; + } + } + + html += "
"; + if(list.toolbarAuxAction) { + html += "
"; + html += list.toolbarAuxAction; + html += "
"; + } + html += "
"; + html += "
\n"; + + for (action in list.actions) { + list.actions[action] = _.defaults(list.actions[action], { dataPlacement: "top" }); + } + + html += "
\n"; + html += "
"; + html += "
"; + } } - html += "
"; - if(list.toolbarAuxAction) { - html += "
"; - html += list.toolbarAuxAction; - html += "
"; - } - html += "
"; - html += "
\n"; - - for (action in list.actions) { - list.actions[action] = _.defaults(list.actions[action], { dataPlacement: "top" }); - } - - html += "
\n"; - html += "
"; - html += "
"; - } if (options.mode === 'edit' && list.editInstructions) { html += "
\n"; - html += "\n"; + html += "\n"; html += "Hint: " + list.editInstructions + "\n"; html += "
\n"; } @@ -356,7 +359,6 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate html += "
"; html += (list.emptyListText) ? list.emptyListText : "PLEASE ADD ITEMS TO THIS LIST"; html += "
"; - if (options.showSearch=== undefined || options.showSearch === true) { // Only show the search bar if we are loading results or if we have at least 1 base result html += "
0)\">\n"; @@ -459,6 +461,20 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate innerTable += ''; } + // Change layout if a lookup list, place radio buttons before labels + if (options.mode === 'lookup') { + if(options.input_type==="radio"){ //added by JT so that lookup forms can be either radio inputs or check box inputs + innerTable += ""; + } + else { // its assumed that options.input_type = checkbox + innerTable += ""; + } + } + cnt = 2; base = (list.base) ? list.base : list.name; base = base.replace(/^\//, ''); @@ -475,7 +491,7 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate } } - if (options.mode === 'select' || options.mode === 'lookup') { + if (options.mode === 'select') { if(options.input_type==="radio"){ //added by JT so that lookup forms can be either radio inputs or check box inputs innerTable += ""; + } for (fld in list.fields) { if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) && !(options.mode === 'lookup' && list.fields[fld].excludeModal === true)) { @@ -643,9 +662,10 @@ export default ['$location', '$compile', '$rootScope', 'SearchWidget', 'Paginate html += "\n"; } } - if (options.mode === 'select' || options.mode === 'lookup') { - html += "Select"; - } else if (options.mode === 'edit' && list.fieldActions) { + if (options.mode === 'select') { + html += "Select"; + } + else if (options.mode === 'edit' && list.fieldActions) { html += ""; From 718a9c2a972c8afcdcfb2fb64f7042e1e22ffb5d Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Mon, 22 Feb 2016 10:21:34 -0500 Subject: [PATCH 34/57] Fix up some merge requirements conflicts --- requirements/requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index eadd73588e..c2ff9995ef 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -123,12 +123,10 @@ requests==2.5.1 requestsexceptions==1.1.1 shade==1.4.0 simplejson==3.8.1 -git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize six==1.9.0 statsd==3.2.1 stevedore==1.10.0 suds==0.4 -South==1.0.2 unicodecsv==0.14.1 warlock==1.2.0 wheel==0.24.0 From 6fa10deb8ac99a4dc7aa553433c12011459ee207 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 22 Feb 2016 11:29:20 -0500 Subject: [PATCH 35/57] #999 move adhoc form to inventoryManage state, #951 fix styling & undefined fields of adhoc form --- awx/ui/client/legacy-styles/forms.less | 9 +++++++++ awx/ui/client/legacy-styles/main-layout.less | 4 ++++ awx/ui/client/src/adhoc/adhoc.controller.js | 8 ++++++-- awx/ui/client/src/adhoc/adhoc.form.js | 11 +++++++---- awx/ui/client/src/adhoc/adhoc.partial.html | 2 +- awx/ui/client/src/adhoc/adhoc.route.js | 4 ++-- awx/ui/client/src/controllers/Inventories.js | 3 ++- awx/ui/client/src/partials/inventory-manage.html | 1 + 8 files changed, 32 insertions(+), 10 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 0556f7e1cd..5f1ae9f449 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -363,6 +363,15 @@ input[type='radio']:checked:before { display: flex; justify-content: flex-end; } +.Form-button{ + margin-left: 4px; +} + +.Form-buttonDefault { + background-color: #FFFFFF; + color: #848992; + border-color: #E8E8E8; +} .Form-saveButton{ background-color: @submit-button-bg; diff --git a/awx/ui/client/legacy-styles/main-layout.less b/awx/ui/client/legacy-styles/main-layout.less index 803d554eaf..5b7f8f1c01 100644 --- a/awx/ui/client/legacy-styles/main-layout.less +++ b/awx/ui/client/legacy-styles/main-layout.less @@ -91,6 +91,10 @@ body { margin-top: 20px; } +.btn{ + text-transform: uppercase; +} + @media (max-width: 1075px) { #main-menu-container { diff --git a/awx/ui/client/src/adhoc/adhoc.controller.js b/awx/ui/client/src/adhoc/adhoc.controller.js index 24a289ebe9..17ca17783a 100644 --- a/awx/ui/client/src/adhoc/adhoc.controller.js +++ b/awx/ui/client/src/adhoc/adhoc.controller.js @@ -10,7 +10,7 @@ * @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran. */ function adhocController($q, $scope, $rootScope, $location, $stateParams, - CheckPasswords, PromptForPasswords, CreateLaunchDialog, adhocForm, + $state, CheckPasswords, PromptForPasswords, CreateLaunchDialog, adhocForm, GenerateForm, Rest, ProcessErrors, ClearScope, GetBasePath, GetChoices, KindChange, LookUpInit, CredentialList, Empty, Wait) { @@ -162,6 +162,10 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams, privateFn.initializeForm(id, urls, hostPattern); + $scope.formCancel = function(){ + $state.go('inventoryManage'); + } + // remove all data input into the form and reset the form back to defaults $scope.formReset = function () { GenerateForm.reset(); @@ -291,7 +295,7 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams, } export default ['$q', '$scope', '$rootScope', '$location', '$stateParams', - 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'adhocForm', + '$state', 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'adhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'ClearScope', 'GetBasePath', 'GetChoices', 'KindChange', 'LookUpInit', 'CredentialList', 'Empty', 'Wait', adhocController]; diff --git a/awx/ui/client/src/adhoc/adhoc.form.js b/awx/ui/client/src/adhoc/adhoc.form.js index d200d37b47..2632a0e7bb 100644 --- a/awx/ui/client/src/adhoc/adhoc.form.js +++ b/awx/ui/client/src/adhoc/adhoc.form.js @@ -12,7 +12,7 @@ export default function() { return { - editTitle: 'Execute Command', + addTitle: 'Execute Command', name: 'adhoc', well: true, forceListeners: true, @@ -125,13 +125,16 @@ export default function() { buttons: { launch: { - label: 'Launch', + label: 'Save', ngClick: 'launchJob()', - ngDisabled: true + ngDisabled: true, + 'class': 'Form-buttonDefault Form-button' }, reset: { ngClick: 'formReset()', - ngDisabled: true + ngDisabled: true, + label: 'Reset', + 'class': 'Form-buttonDefault Form-button' } }, diff --git a/awx/ui/client/src/adhoc/adhoc.partial.html b/awx/ui/client/src/adhoc/adhoc.partial.html index 7420af3ea9..eb25ec5b56 100644 --- a/awx/ui/client/src/adhoc/adhoc.partial.html +++ b/awx/ui/client/src/adhoc/adhoc.partial.html @@ -1,4 +1,4 @@ -
+
diff --git a/awx/ui/client/src/adhoc/adhoc.route.js b/awx/ui/client/src/adhoc/adhoc.route.js index f5fa7e9639..5e7b47a850 100644 --- a/awx/ui/client/src/adhoc/adhoc.route.js +++ b/awx/ui/client/src/adhoc/adhoc.route.js @@ -7,8 +7,8 @@ import {templateUrl} from '../shared/template-url/template-url.factory'; export default { - route: '/inventories/:inventory_id/adhoc', - name: 'inventoryAdhoc', + route: '/adhoc', + name: 'inventoryManage.adhoc', templateUrl: templateUrl('adhoc/adhoc'), controller: 'adhocController', resolve: { diff --git a/awx/ui/client/src/controllers/Inventories.js b/awx/ui/client/src/controllers/Inventories.js index a506d78ead..9b2774bd98 100644 --- a/awx/ui/client/src/controllers/Inventories.js +++ b/awx/ui/client/src/controllers/Inventories.js @@ -859,7 +859,8 @@ export function InventoriesManage ($log, $scope, $rootScope, $location, } } $rootScope.hostPatterns = host_patterns; - $location.path('/inventories/' + $scope.inventory.id + '/adhoc'); + $state.go('inventoryManage.adhoc'); + //$location.path('/inventories/' + $scope.inventory.id + '/adhoc'); }; $scope.refreshHostsOnGroupRefresh = false; diff --git a/awx/ui/client/src/partials/inventory-manage.html b/awx/ui/client/src/partials/inventory-manage.html index 452df8bbb8..ecae801c20 100644 --- a/awx/ui/client/src/partials/inventory-manage.html +++ b/awx/ui/client/src/partials/inventory-manage.html @@ -1,4 +1,5 @@
+
From d70615efbd71219a02254b901cf930f27705da1e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 19 Feb 2016 14:46:19 -0500 Subject: [PATCH 36/57] bring back meta choice options lost in upgrade * status and launch_type OPTIONS choices were lost in the django + drf upgrade. This brings them back. --- awx/api/metadata.py | 21 +++++++++++++++++++ awx/api/serializers.py | 15 +++++++++++++ .../functional/api/test_unified_jobs_view.py | 17 +++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 awx/main/tests/functional/api/test_unified_jobs_view.py diff --git a/awx/api/metadata.py b/awx/api/metadata.py index 713f9f83a2..a11df8d1ce 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -4,6 +4,7 @@ # Django from django.core.exceptions import PermissionDenied from django.http import Http404 +from django.utils.encoding import force_text # Django REST Framework from rest_framework import exceptions @@ -17,8 +18,28 @@ from awx.main.models import InventorySource class Metadata(metadata.SimpleMetadata): + # DRF 3.3 doesn't render choices for read-only fields + # + # We want to render choices for read-only fields + # + # Note: This works in conjuction with logic in serializers.py that sets + # field property editable=True before calling DRF build_standard_field() + # Note: Consider expanding this rendering for more than just choices fields + def _render_read_only_choices(self, field, field_info): + if field_info.get('read_only') and hasattr(field, 'choices'): + field_info['choices'] = [ + { + 'value': choice_value, + 'display_name': force_text(choice_name, strings_only=True) + } + for choice_value, choice_name in field.choices.items() + ] + return field_info + def get_field_info(self, field): field_info = super(Metadata, self).get_field_info(field) + if hasattr(field, 'choices') and field.choices: + field_info = self._render_read_only_choices(field, field_info) # Indicate if a field has a default value. # FIXME: Still isn't showing all default values? diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 491e76ddb7..e01828b276 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -331,7 +331,22 @@ class BaseSerializer(serializers.ModelSerializer): return obj.active def build_standard_field(self, field_name, model_field): + + # DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits + # when a Model's editable field is set to False. The short circuit skips choice rendering. + # + # This logic is to force rendering choice's on an uneditable field. + # Note: Consider expanding this rendering for more than just choices fields + # Note: This logic works in conjuction with + if hasattr(model_field, 'choices') and model_field.choices: + was_editable = model_field.editable + model_field.editable = True + field_class, field_kwargs = super(BaseSerializer, self).build_standard_field(field_name, model_field) + if hasattr(model_field, 'choices') and model_field.choices: + model_field.editable = was_editable + if was_editable is False: + field_kwargs['read_only'] = True # Update help text for common fields. opts = self.Meta.model._meta.concrete_model._meta diff --git a/awx/main/tests/functional/api/test_unified_jobs_view.py b/awx/main/tests/functional/api/test_unified_jobs_view.py new file mode 100644 index 0000000000..ab9487bc38 --- /dev/null +++ b/awx/main/tests/functional/api/test_unified_jobs_view.py @@ -0,0 +1,17 @@ +import pytest + +from awx.main.models import UnifiedJob +from django.core.urlresolvers import reverse + +@pytest.mark.django_db +def test_options_fields_choices(instance, options, user): + + url = reverse('api:unified_job_list') + response = options(url, None, user('admin', True)) + + assert 'launch_type' in response.data['actions']['GET'] + assert 'choice' == response.data['actions']['GET']['launch_type']['type'] + assert UnifiedJob.LAUNCH_TYPE_CHOICES == response.data['actions']['GET']['launch_type']['choices'] + assert 'choice' == response.data['actions']['GET']['status']['type'] + assert UnifiedJob.STATUS_CHOICES == response.data['actions']['GET']['status']['choices'] + From 1915347aff71f8ab198e65236c307d5e335473b4 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 23 Feb 2016 10:32:39 -0500 Subject: [PATCH 37/57] fix req --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index c2ff9995ef..ecbe792db9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -50,7 +50,6 @@ idna==2.0 importlib==1.0.3 ip-associations-python-novaclient-ext==0.1 ipaddress==1.0.16 -git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy iso8601==0.1.11 isodate==0.5.1 jsonpatch==1.12 From 244d1ac73dcb1a5fa3e297b93435c2919fd9526e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 23 Feb 2016 11:47:26 -0500 Subject: [PATCH 38/57] keystone dep fix --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index ecbe792db9..edfc35fa42 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -38,7 +38,6 @@ git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=d git+https://github.com/umutbozkurt/django-rest-framework-mongoengine.git@5dfa1df79f81765d36c0de31dc1c2f390e42d428#egg=django-rest-framework-mongoengine git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy -git+https://github.com/chrismeyersfsu/python-keystoneclient.git@1.3.0#egg=python-keystoneclient git+https://github.com/chrismeyersfsu/shade.git@tower_0.5.0#egg=shade git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize greenlet==0.4.9 From 9f44888c00d29bd1d1a53eb09ab90b61f33c5e05 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 23 Feb 2016 12:15:39 -0500 Subject: [PATCH 39/57] Update existing settings migration with minor field change. --- awx/main/migrations/0002_v300_changes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/main/migrations/0002_v300_changes.py b/awx/main/migrations/0002_v300_changes.py index b4b87e24c9..9d222762b1 100644 --- a/awx/main/migrations/0002_v300_changes.py +++ b/awx/main/migrations/0002_v300_changes.py @@ -26,7 +26,7 @@ class Migration(migrations.Migration): ('key', models.CharField(unique=True, max_length=255)), ('description', models.TextField()), ('category', models.CharField(max_length=128)), - ('value', models.TextField()), + ('value', models.TextField(blank=True)), ('value_type', models.CharField(max_length=12, choices=[(b'string', 'String'), (b'int', 'Integer'), (b'float', 'Decimal'), (b'json', 'JSON'), (b'bool', 'Boolean'), (b'password', 'Password'), (b'list', 'List')])), ('user', models.ForeignKey(related_name='settings', default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)), ], From 3013bcbebbeda0bf9ef356f0de554de488166550 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 23 Feb 2016 12:27:20 -0500 Subject: [PATCH 40/57] remove duplicate shade --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index edfc35fa42..edf8f7ad74 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -38,7 +38,6 @@ git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=d git+https://github.com/umutbozkurt/django-rest-framework-mongoengine.git@5dfa1df79f81765d36c0de31dc1c2f390e42d428#egg=django-rest-framework-mongoengine git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy -git+https://github.com/chrismeyersfsu/shade.git@tower_0.5.0#egg=shade git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize greenlet==0.4.9 dogpile.cache==0.5.7 From 3535cf3b08ecb4b51bd7c6c6fe3ebdc15191def2 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 23 Feb 2016 12:35:54 -0500 Subject: [PATCH 41/57] add error to requirements.txt for jenkins testing --- requirements/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index edf8f7ad74..d6ab613b33 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -38,6 +38,7 @@ git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=d git+https://github.com/umutbozkurt/django-rest-framework-mongoengine.git@5dfa1df79f81765d36c0de31dc1c2f390e42d428#egg=django-rest-framework-mongoengine git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy +git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize greenlet==0.4.9 dogpile.cache==0.5.7 From 61fd645d255c48b05d89d19e513a245b4cd16036 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 23 Feb 2016 12:49:28 -0500 Subject: [PATCH 42/57] Revert "add error to requirements.txt for jenkins testing" This reverts commit b1543763a4c195c857ec82bda058e06a0becf816. --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index d6ab613b33..edf8f7ad74 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -38,7 +38,6 @@ git+https://github.com/chrismeyersfsu/django-qsstats-magic.git@tower_0.7.2#egg=d git+https://github.com/umutbozkurt/django-rest-framework-mongoengine.git@5dfa1df79f81765d36c0de31dc1c2f390e42d428#egg=django-rest-framework-mongoengine git+https://github.com/chrismeyersfsu/gevent-socketio.git@tower_0.3.6#egg=gevent-socketio git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy -git+https://github.com/chrismeyersfsu/python-ipy.git@fix-127_localhost#egg=IPy git+https://github.com/chrismeyersfsu/sitecustomize.git#egg=sitecustomize greenlet==0.4.9 dogpile.cache==0.5.7 From 90d1a3d0208bbaad20f46e4c1f11c668bbed3874 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 23 Feb 2016 14:04:30 -0500 Subject: [PATCH 43/57] Remove write-only fields from GET response for job template launch, update raw data form to show any fields/passwords needed to launch the job. --- awx/api/serializers.py | 19 ++++++------------- awx/api/views.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index cb22723098..f79fb2ebe3 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1955,7 +1955,7 @@ class JobLaunchSerializer(BaseSerializer): variables_needed_to_start = serializers.ReadOnlyField() credential_needed_to_start = serializers.SerializerMethodField() survey_enabled = serializers.SerializerMethodField() - extra_vars = VerbatimField(required=False) + extra_vars = VerbatimField(required=False, write_only=True) class Meta: model = JobTemplate @@ -1963,18 +1963,11 @@ class JobLaunchSerializer(BaseSerializer): 'ask_variables_on_launch', 'survey_enabled', 'variables_needed_to_start', 'credential', 'credential_needed_to_start',) read_only_fields = ('ask_variables_on_launch',) - write_only_fields = ('credential', 'extra_vars',) - - def to_representation(self, obj): - res = super(JobLaunchSerializer, self).to_representation(obj) - view = self.context.get('view', None) - if obj and hasattr(view, '_raw_data_form_marker'): - if obj.passwords_needed_to_start: - password_keys = dict([(p, u'') for p in obj.passwords_needed_to_start]) - res.update(password_keys) - if self.get_credential_needed_to_start(obj) is True: - res.update(dict(credential='')) - return res + extra_kwargs = { + 'credential': { + 'write_only': True, + }, + } def get_credential_needed_to_start(self, obj): return not (obj and obj.credential and obj.credential.active) diff --git a/awx/api/views.py b/awx/api/views.py index 28697ee58a..1a6e306419 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1876,6 +1876,21 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView): is_job_start = True always_allow_superuser = False + def update_raw_data(self, data): + obj = self.get_object() + extra_vars = data.get('extra_vars') or {} + if obj: + for p in obj.passwords_needed_to_start: + data[p] = u'' + if obj.credential and obj.credential.active: + data.pop('credential', None) + else: + data['credential'] = None + for v in obj.variables_needed_to_start: + extra_vars.setdefault(v, u'') + data['extra_vars'] = extra_vars + return data + def post(self, request, *args, **kwargs): obj = self.get_object() if not request.user.can_access(self.model, 'start', obj): From 382368f8db5f2ab04a3b700838338a0085563d4e Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 24 Feb 2016 08:20:22 -0500 Subject: [PATCH 44/57] Made standard out details responsive. Removed the /stdout on the end of some of the standard out routes. --- .../adhoc/standard-out-adhoc.partial.html | 2 +- .../adhoc/standard-out-adhoc.route.js | 2 +- .../standard-out-inventory-sync.partial.html | 10 +++++----- .../standard-out-inventory-sync.route.js | 2 +- .../standard-out-management-jobs.partial.html | 4 ++-- .../standard-out-management-jobs.route.js | 2 +- .../standard-out-scm-update.partial.html | 4 ++-- .../scm-update/standard-out-scm-update.route.js | 2 +- .../src/standard-out/standard-out.block.less | 15 +++++++++++++++ .../src/standard-out/standard-out.controller.js | 2 -- 10 files changed, 29 insertions(+), 16 deletions(-) diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html index 9d6d6ad4ac..3b8b51fa97 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.partial.html @@ -17,7 +17,7 @@
STATUS
- {{ job.status }} + {{ job.status }}
diff --git a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js index 180baa17fa..d9d121b443 100644 --- a/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js +++ b/awx/ui/client/src/standard-out/adhoc/standard-out-adhoc.route.js @@ -8,7 +8,7 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'adHocJobStdout', - route: '/ad_hoc_commands/:id/stdout', + route: '/ad_hoc_commands/:id', templateUrl: templateUrl('standard-out/adhoc/standard-out-adhoc'), controller: 'JobStdoutController', ncyBreadcrumb: { diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index 63835c3c80..cba201786e 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -21,13 +21,13 @@
STATUS
- {{ job.status }} + {{ job.status }}
LICENSE ERROR
-
+
{{ job.license_error }}
@@ -55,7 +55,7 @@
LAUNCH TYPE
-
+
{{ job.launch_type }}
@@ -94,14 +94,14 @@
OVERWRITE
-
+
{{ job.overwrite }}
OVERWRITE VARS
-
+
{{ job.overwrite_vars }}
diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js index 85afb5e01f..a357f39afc 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.route.js @@ -10,7 +10,7 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'inventorySyncStdout', - route: '/inventory_sync/:id/stdout', + route: '/inventory_sync/:id', templateUrl: templateUrl('standard-out/inventory-sync/standard-out-inventory-sync'), controller: 'JobStdoutController', ncyBreadcrumb: { diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html index f268e9569a..36a29fef55 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.partial.html @@ -17,7 +17,7 @@
STATUS
- {{ job.status }} + {{ job.status }}
@@ -44,7 +44,7 @@
LAUNCH TYPE
-
+
{{ job.launch_type }}
diff --git a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js index c119ea215b..6eaa1aa184 100644 --- a/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js +++ b/awx/ui/client/src/standard-out/management-jobs/standard-out-management-jobs.route.js @@ -8,7 +8,7 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'managementJobStdout', - route: '/management_jobs/:id/stdout', + route: '/management_jobs/:id', templateUrl: templateUrl('standard-out/management-jobs/standard-out-management-jobs'), controller: 'JobStdoutController', ncyBreadcrumb: { diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html index 883d382c20..ec1378cad7 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.partial.html @@ -21,7 +21,7 @@
STATUS
- {{ job.status }} + {{ job.status }}
@@ -48,7 +48,7 @@
LAUNCH TYPE
-
+
{{ job.launch_type }}
diff --git a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js index 5f9a089231..294518cd7b 100644 --- a/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js +++ b/awx/ui/client/src/standard-out/scm-update/standard-out-scm-update.route.js @@ -10,7 +10,7 @@ import {templateUrl} from '../../shared/template-url/template-url.factory'; export default { name: 'scmUpdateStdout', - route: '/scm_update/:id/stdout', + route: '/scm_update/:id', templateUrl: templateUrl('standard-out/scm-update/standard-out-scm-update'), controller: 'JobStdoutController', ncyBreadcrumb: { 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 7fcd0aa9b6..654439b6ed 100644 --- a/awx/ui/client/src/standard-out/standard-out.block.less +++ b/awx/ui/client/src/standard-out/standard-out.block.less @@ -58,3 +58,18 @@ .StandardOut-statusText { margin-left: 6px; } + +.StandardOut--capitalize { + text-transform: capitalize; +} + +@standardout-breakpoint: 900px; + +@media screen and (max-width: @standardout-breakpoint) { + .StandardOut { + flex-direction: column; + } + .StandardOut-rightPanel { + margin-left: 0px; + } +} diff --git a/awx/ui/client/src/standard-out/standard-out.controller.js b/awx/ui/client/src/standard-out/standard-out.controller.js index f95eaab1fb..e0ac1362e4 100644 --- a/awx/ui/client/src/standard-out/standard-out.controller.js +++ b/awx/ui/client/src/standard-out/standard-out.controller.js @@ -158,8 +158,6 @@ export function JobStdoutController ($location, $log, $rootScope, $scope, $compi } }; - $(".StandardOut").height($("body").height() - 60); - Rest.setUrl(GetBasePath('base') + jobType + '/' + job_id + '/'); Rest.get() .success(function(data) { From 62d0e8f6a5fc994afe123344faa94face5711b3e Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 24 Feb 2016 08:52:16 -0500 Subject: [PATCH 45/57] Update Dockerfile Add dateutils python package for `private/license_writer.py` to succeed --- tools/docker-compose/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index d7acaa5375..ed38a6eecd 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get update RUN apt-get install -y openssh-server ansible mg vim tmux git mercurial subversion python-dev python-psycopg2 make postgresql-client libpq-dev nodejs python-psutil libxml2-dev libxslt-dev lib32z1-dev libsasl2-dev libldap2-dev libffi-dev libzmq-dev proot python-pip libxmlsec1-dev swig redis-server && rm -rf /var/lib/apt/lists/* RUN pip install flake8 RUN pip install pytest pytest-pythonpath pytest-django pytest-cov +RUN pip install dateutils # for private/license_writer.py RUN /usr/bin/ssh-keygen -q -t rsa -N "" -f /root/.ssh/id_rsa RUN mkdir -p /etc/tower RUN mkdir -p /data/db From c2d2c57aad366cd73309eb5b31269526277172eb Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 24 Feb 2016 08:58:41 -0500 Subject: [PATCH 46/57] add newer version of proot Use proot version that is used in production (5.1.0). Solves the ansible-playbook 2.0 symlink issue we see in our container when launching jobs. --- tools/docker-compose/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/docker-compose/Dockerfile b/tools/docker-compose/Dockerfile index ed38a6eecd..16fed136fe 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -5,7 +5,7 @@ ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 RUN apt-get update && apt-get install -y software-properties-common python-software-properties curl -RUN add-apt-repository -y ppa:chris-lea/redis-server; add-apt-repository -y ppa:chris-lea/zeromq; add-apt-repository -y ppa:chris-lea/node.js; add-apt-repository -y ppa:ansible/ansible +RUN add-apt-repository -y ppa:chris-lea/redis-server; add-apt-repository -y ppa:chris-lea/zeromq; add-apt-repository -y ppa:chris-lea/node.js; add-apt-repository -y ppa:ansible/ansible; add-apt-repository -y ppa:jal233/proot; RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 && apt-key adv --fetch-keys http://www.postgresql.org/media/keys/ACCC4CF8.asc RUN echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list && echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" | tee /etc/apt/sources.list.d/postgres-9.4.list From 1de32b987fedbdf1611d85f532716a7803743894 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 24 Feb 2016 13:23:04 -0500 Subject: [PATCH 47/57] Added missing query parameters to /home/hosts and /home/groups --- awx/ui/client/src/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 43ad6627fb..7dd67c1f4e 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -234,7 +234,7 @@ var tower = angular.module('Tower', [ }). state('dashboardGroups', { - url: '/home/groups', + url: '/home/groups?id&name&has_active_failures&status&source&has_external_source&inventory_source__id', templateUrl: urlPrefix + 'partials/subhome.html', controller: HomeGroups, ncyBreadcrumb: { @@ -249,7 +249,7 @@ var tower = angular.module('Tower', [ }). state('dashboardHosts', { - url: '/home/hosts?has_active_failures', + url: '/home/hosts?has_active_failures&name&id', templateUrl: urlPrefix + 'partials/subhome.html', controller: HomeHosts, data: { From e8b890ac458482b7004ac01d9788f1ecfe234fd6 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 24 Feb 2016 13:28:46 -0500 Subject: [PATCH 48/57] Fixed this /home/groups route. Having the extra slash before the query params was resulting in a redirect to the dashboard. --- .../inventory-sync/standard-out-inventory-sync.partial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index 63835c3c80..bcabcbc40a 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -11,7 +11,7 @@
NAME
From c65d795b3bd135f11907e407ba1b1c296982adb6 Mon Sep 17 00:00:00 2001 From: Michael Abashian Date: Wed, 24 Feb 2016 13:37:27 -0500 Subject: [PATCH 49/57] Fixed this other /home/groups href by removing the extra slash --- .../inventory-sync/standard-out-inventory-sync.partial.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html index bcabcbc40a..236a07edb1 100644 --- a/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html +++ b/awx/ui/client/src/standard-out/inventory-sync/standard-out-inventory-sync.partial.html @@ -72,7 +72,7 @@
GROUP
From a2595dcf124a3435cba2b32f76a2bf924604e6f7 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Wed, 24 Feb 2016 18:38:04 -0500 Subject: [PATCH 50/57] API fixes for 500 errors. --- awx/api/serializers.py | 4 ++++ awx/api/views.py | 8 ++++---- awx/main/views.py | 5 ++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 83719f1dba..223803bcbf 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -478,6 +478,10 @@ class BaseSerializer(serializers.ModelSerializer): return attrs +class EmptySerializer(serializers.Serializer): + pass + + class BaseFactSerializer(DocumentSerializer): __metaclass__ = BaseSerializerMetaclass diff --git a/awx/api/views.py b/awx/api/views.py index 1a6e306419..8dc0aff15b 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -1940,7 +1940,7 @@ class JobTemplateSurveySpec(GenericAPIView): model = JobTemplate parent_model = JobTemplate - # FIXME: Add serializer class to define fields in OPTIONS request! + serializer_class = EmptySerializer def get(self, request, *args, **kwargs): obj = self.get_object() @@ -2019,8 +2019,8 @@ class JobTemplateActivityStreamList(SubListAPIView): class JobTemplateCallback(GenericAPIView): model = JobTemplate - # FIXME: Add serializer class to define fields in OPTIONS request! permission_classes = (JobTemplateCallbackPermission,) + serializer_class = EmptySerializer @csrf_exempt @transaction.non_atomic_requests @@ -2202,7 +2202,7 @@ class SystemJobTemplateDetail(RetrieveAPIView): class SystemJobTemplateLaunch(GenericAPIView): model = SystemJobTemplate - # FIXME: Add serializer class to define fields in OPTIONS request! + serializer_class = EmptySerializer def get(self, request, *args, **kwargs): return Response({}) @@ -2273,7 +2273,7 @@ class JobActivityStreamList(SubListAPIView): class JobStart(GenericAPIView): model = Job - # FIXME: Add serializer class to define fields in OPTIONS request! + serializer_class = EmptySerializer is_job_start = True def get(self, request, *args, **kwargs): diff --git a/awx/main/views.py b/awx/main/views.py index 5720405093..a1036a96e6 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -34,7 +34,10 @@ def handle_error(request, status=404, **kwargs): status_code = status default_detail = kwargs['content'] api_error_view = ApiErrorView.as_view(view_name=kwargs['name'], exception_class=APIException) - return api_error_view(request) + response = api_error_view(request) + if hasattr(response, 'render'): + response.render() + return response else: kwargs['content'] = format_html('{}', kwargs.get('content', '')) return render(request, 'error.html', kwargs, status=status) From 3d858b68cd68c699c4e8b1e5c28a0129781e6146 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Wed, 24 Feb 2016 10:41:40 -0500 Subject: [PATCH 51/57] Moved LESS code around per comments. Modularized. Removed old lookup helper and references. Modularized Lookup, put in own folder, removed old Lookup.js --- awx/ui/client/legacy-styles/ansible-ui.less | 17 ++ awx/ui/client/legacy-styles/lists.less | 40 --- awx/ui/client/src/app.js | 1 + awx/ui/client/src/helpers.js | 2 - awx/ui/client/src/helpers/Lookup.js | 306 -------------------- awx/ui/client/src/lookup/lookup.block.less | 22 ++ awx/ui/client/src/lookup/lookup.factory.js | 305 +++++++++++++++++++ awx/ui/client/src/lookup/main.js | 12 + awx/ui/client/src/partials/portal.html | 2 +- 9 files changed, 358 insertions(+), 349 deletions(-) delete mode 100644 awx/ui/client/src/helpers/Lookup.js create mode 100644 awx/ui/client/src/lookup/lookup.block.less create mode 100644 awx/ui/client/src/lookup/lookup.factory.js create mode 100644 awx/ui/client/src/lookup/main.js diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 028529fffa..06d924b141 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -697,6 +697,23 @@ legend { margin-bottom: 7px; } + .pagination>li>a, + .pagination>li>span { + border: 1px solid @grey-border; + padding: 3px 6px; + font-size: 10px; + } + + .pagination li { + a#next-page { + border-radius: 0 4px 4px 0; + } + + a#previous-page { + border-radius: 4px 0 0 4px; + } + } + .modal-body { .pagination { margin-top: 15px; diff --git a/awx/ui/client/legacy-styles/lists.less b/awx/ui/client/legacy-styles/lists.less index 9db2792d2b..2dcac015c8 100644 --- a/awx/ui/client/legacy-styles/lists.less +++ b/awx/ui/client/legacy-styles/lists.less @@ -101,7 +101,6 @@ table, tbody { margin-left: 15px; } - /* -- Pagination -- */ .List-pagination { margin-top: 20px; @@ -110,23 +109,6 @@ table, tbody { text-transform: uppercase; height: 22px; display: flex; - - .pagination>li>a, - .pagination>li>span { - border: 1px solid @grey-border; - padding: 3px 6px; - font-size: 10px; - } - - .pagination li { - a#next-page { - border-radius: 0 4px 4px 0; - } - - a#previous-page { - border-radius: 4px 0 0 4px; - } - } } .List-paginationPagerHolder { @@ -368,25 +350,3 @@ table, tbody { display: block; font-size: 13px; } - -#lookup-modal-dialog { - - .List-searchWidget { - width: 66.6666% - } - - .List-tableHeaderRow { - .List-tableHeader:first-of-type { - width: 3%; - } - } - - .List-tableHeader, - .List-tableHeader:last-of-type { - text-align:left; - } - - .List-tableCell { - color: @default-interface-txt; - } -} diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js index 43ad6627fb..2c27bf8c20 100644 --- a/awx/ui/client/src/app.js +++ b/awx/ui/client/src/app.js @@ -45,6 +45,7 @@ import adhoc from './adhoc/main'; import login from './login/main'; import activityStream from './activity-stream/main'; import standardOut from './standard-out/main'; +import lookUpHelper from './lookup/main'; import {JobTemplatesList, JobTemplatesAdd, JobTemplatesEdit} from './controllers/JobTemplates'; import {LicenseController} from './controllers/License'; import {ScheduleEditController} from './controllers/Schedules'; diff --git a/awx/ui/client/src/helpers.js b/awx/ui/client/src/helpers.js index 4a277970da..4b9c4ab548 100644 --- a/awx/ui/client/src/helpers.js +++ b/awx/ui/client/src/helpers.js @@ -22,7 +22,6 @@ import Jobs from "./helpers/Jobs"; import License from "./helpers/License"; import LoadConfig from "./helpers/LoadConfig"; import LogViewer from "./helpers/LogViewer"; -import Lookup from "./helpers/Lookup"; import PaginationHelpers from "./helpers/PaginationHelpers"; import Parse from "./helpers/Parse"; import ProjectPath from "./helpers/ProjectPath"; @@ -60,7 +59,6 @@ export License, LoadConfig, LogViewer, - Lookup, PaginationHelpers, Parse, ProjectPath, diff --git a/awx/ui/client/src/helpers/Lookup.js b/awx/ui/client/src/helpers/Lookup.js deleted file mode 100644 index d23614de3c..0000000000 --- a/awx/ui/client/src/helpers/Lookup.js +++ /dev/null @@ -1,306 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name helpers.function:Lookup - * @description - * LookUpInit( { - * scope: , - * form:
, - * current_item: , - * list: , - * field: , - * hdr: - * postAction: optional function to run after selection made, - * callback: optional label to $emit() on parent scope - * input_type: optional string that specifies whether lookup should have checkboxes or radio buttons. defaults to 'checkbox' --- added by JT 8/21/14 - * }) - */ - -import listGenerator from '../shared/list-generator/main'; - -export default - angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', 'PaginationHelpers', listGenerator.name, 'ApiLoader', 'ModalDialog']) - - .factory('LookUpInit', ['Alert', 'Rest', 'ProcessErrors', 'generateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog', - function (Alert, Rest, ProcessErrors, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) { - return function (params) { - - var parent_scope = params.scope, - form = params.form, - list = params.list, - field = params.field, - instructions = params.instructions, - postAction = params.postAction, - callback = params.callback, - autopopulateLookup, - input_type = (params.input_type) ? params.input_type: "checkbox", - defaultUrl, name, watchUrl; - - if (params.autopopulateLookup !== undefined) { - autopopulateLookup = params.autopopulateLookup; - } else { - autopopulateLookup = true; - } - - if (params.url) { - // pass in a url value to override the default - defaultUrl = params.url; - } else { - defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name); - } - - if ($('#htmlTemplate #lookup-modal-dialog').length > 0) { - $('#htmlTemplate #lookup-modal-dialog').empty(); - } - else { - $('#htmlTemplate').append("
"); - } - - name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); - - watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&'; - watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value'; - - $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl); - $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field); - - var sourceModel = form.fields[field].sourceModel, - sourceField = form.fields[field].sourceField; - - // this logic makes sure that the form is being added, and that the lookup to be autopopulated is required - // we also look to see if the lookupinit autopopulateLookup parameter passed in by the controller is false - function fieldIsAutopopulatable() { - if (autopopulateLookup === false) { - return false; - } if (parent_scope.mode === "add") { - if (parent_scope[sourceModel + "_field"].awRequiredWhen && - parent_scope[sourceModel + "_field"].awRequiredWhen.variable && - parent_scope[parent_scope[sourceModel + "_field"].awRequiredWhen.variable]) { - return true; - } else if (parent_scope[sourceModel + "_field"].addRequired === true) { - return true; - } else { - return false; - } - } else { - return false; - } - } - - if (fieldIsAutopopulatable()) { - // Auto populate the field if there is only one result - Rest.setUrl(defaultUrl); - Rest.get() - .success(function (data) { - if (data.count === 1) { - parent_scope[field] = data.results[0].id; - if (parent_scope[form.name + '_form'] && form.fields[field] && sourceModel) { - parent_scope[sourceModel + '_' + sourceField] = - data.results[0][sourceField]; - if (parent_scope[form.name + '_form'][sourceModel + '_' + sourceField]) { - parent_scope[form.name + '_form'][sourceModel + '_' + sourceField] - .$setValidity('awlookup', true); - } - } - if (parent_scope[form.name + '_form']) { - parent_scope[form.name + '_form'].$setDirty(); - } - } - }) - .error(function (data, status) { - ProcessErrors(parent_scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to launch adhoc command. POST returned status: ' + - status }); - }); - } - - - parent_scope['lookUp' + name] = function () { - - var master = {}, - scope = parent_scope.$new(), - name, hdr, buttons; - - // Generating the search list potentially kills the values held in scope for the field. - // We'll keep a copy in master{} that we can revert back to on cancel; - master[field] = scope[field]; - master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; - GenerateList.inject(list, { - mode: 'lookup', - id: 'lookup-modal-dialog', - scope: scope, - instructions: instructions, - input_type: input_type - }); - - name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); - hdr = (params.hdr) ? params.hdr : 'Select ' + name; - - // Show pop-up - buttons = [ - { - label: "Save", - onClick: function() { - scope.selectAction(); - }, - //icon: "fa-check", - "class": "btn btn-primary", - "id": "lookup-save-button" - },{ - label: "Cancel", - "class": "btn btn-default", - "id": "lookup-cancel-button", - onClick: function() { - $('#lookup-modal-dialog').dialog('close'); - } - }]; - - if (scope.removeModalReady) { - scope.removeModalReady(); - } - scope.removeModalReady = scope.$on('ModalReady', function() { - $('#lookup-save-button').attr('disabled','disabled'); - $('#lookup-modal-dialog').dialog('open'); - }); - - CreateDialog({ - scope: scope, - buttons: buttons, - width: 600, - height: (instructions) ? 625 : 450, - minWidth: 500, - title: hdr, - id: 'lookup-modal-dialog', - onClose: function() { - setTimeout( function() { - scope.$apply( function() { - if (Empty(scope[field])) { - scope[field] = master[field]; - scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; - } - }); - }, 300); - }, - callback: 'ModalReady' - }); - - SearchInit({ - scope: scope, - set: list.name, - list: list, - url: defaultUrl - }); - - PaginateInit({ - scope: scope, - list: list, - url: defaultUrl, - mode: 'lookup' - }); - - if (scope.lookupPostRefreshRemove) { - scope.lookupPostRefreshRemove(); - } - scope.lookupPostRefreshRemove = scope.$on('PostRefresh', function () { - var fld, i; - for (fld in list.fields) { - if (list.fields[fld].type && list.fields[fld].type === 'date') { - //convert dates to our standard format - for (i = 0; i < scope[list.name].length; i++) { - scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld])); - } - } - } - - // List generator creates the list, resetting it and losing the previously selected value. - // If the selected value is in the current set, find it and mark selected. - if (!Empty(parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField])) { - scope[list.name].forEach(function(elem) { - if (elem[form.fields[field].sourceField] === - parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { - scope[field] = elem.id; - } - }); - - } - - if (!Empty(scope[field])) { - scope['toggle_' + list.iterator](scope[field]); - } - - }); - - scope.search(list.iterator); - - scope.selectAction = function () { - var i, found = false; - for (i = 0; i < scope[list.name].length; i++) { - if (scope[list.name][i].checked === '1' || scope[list.name][i].checked===1 || scope[list.name][i].checked === true) { - found = true; - parent_scope[field] = scope[list.name][i].id; - if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) { - parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = - scope[list.name][i][form.fields[field].sourceField]; - if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { - parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField] - .$setValidity('awlookup', true); - } - } - if (parent_scope[form.name + '_form']) { - parent_scope[form.name + '_form'].$setDirty(); - } - } - } - if (found) { - // Selection made - $('#lookup-modal-dialog').dialog('close'); - if (postAction) { - postAction(); - } - if (callback) { - parent_scope.$emit(callback); - } - } - }; - - - scope['toggle_' + list.iterator] = function (id) { - var count = 0; - scope[list.name].forEach( function(row, i) { - if (row.id === id) { - if (row.checked) { - scope[list.name][i].success_class = 'success'; - } - else { - row.checked = true; - scope[list.name][i].success_class = ''; - } - } else { - scope[list.name][i].checked = 0; - scope[list.name][i].success_class = ''; - } - }); - // Check if any rows are checked - scope[list.name].forEach(function(row) { - if (row.checked) { - count++; - } - }); - if (count === 0) { - $('#lookup-save-button').attr('disabled','disabled'); - } - else { - $('#lookup-save-button').removeAttr('disabled'); - } - }; - }; - }; - }]); diff --git a/awx/ui/client/src/lookup/lookup.block.less b/awx/ui/client/src/lookup/lookup.block.less new file mode 100644 index 0000000000..9f2dea25a9 --- /dev/null +++ b/awx/ui/client/src/lookup/lookup.block.less @@ -0,0 +1,22 @@ +@import "../shared/branding/colors.default.less"; + +#LookupModal-dialog { + .List-searchWidget { + width: 66.6666% + } + + .List-tableHeaderRow { + .List-tableHeader:first-of-type { + width: 3%; + } + } + + .List-tableHeader, + .List-tableHeader:last-of-type { + text-align:left; + } + + .List-tableCell { + color: @default-interface-txt; + } +} diff --git a/awx/ui/client/src/lookup/lookup.factory.js b/awx/ui/client/src/lookup/lookup.factory.js new file mode 100644 index 0000000000..468acbc4e6 --- /dev/null +++ b/awx/ui/client/src/lookup/lookup.factory.js @@ -0,0 +1,305 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ + + /** +* @ngdoc function +* @name helpers.function:Lookup +* @description +* LookUpInit( { +* scope: , +* form: , +* current_item: , +* list: , +* field: , +* hdr: +* postAction: optional function to run after selection made, +* callback: optional label to $emit() on parent scope +* input_type: optional string that specifies whether lookup should have checkboxes or radio buttons. defaults to 'checkbox' --- added by JT 8/21/14 +* }) +*/ + +export default ['Alert', 'Rest', 'ProcessErrors', 'generateList', + 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', + 'Empty', 'CreateDialog', + function(Alert, Rest, ProcessErrors, GenerateList, + SearchInit, PaginateInit, GetBasePath, FormatDate, + Empty, CreateDialog) { + return function(params) { + + var parent_scope = params.scope, + form = params.form, + list = params.list, + field = params.field, + instructions = params.instructions, + postAction = params.postAction, + callback = params.callback, + autopopulateLookup, + input_type = (params.input_type) ? params.input_type : "checkbox", + defaultUrl, name, watchUrl; + + if (params.autopopulateLookup !== undefined) { + autopopulateLookup = params.autopopulateLookup; + } else { + autopopulateLookup = true; + } + + if (params.url) { + // pass in a url value to override the default + defaultUrl = params.url; + } else { + defaultUrl = (list.name === 'inventories') ? GetBasePath('inventory') : GetBasePath(list.name); + } + + if ($('#htmlTemplate #LookupModal-dialog').length > 0) { + $('#htmlTemplate #LookupModal-dialog').empty(); + } else { + $('#htmlTemplate').append("
"); + } + + name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); + + watchUrl = (/\/$/.test(defaultUrl)) ? defaultUrl + '?' : defaultUrl + '&'; + watchUrl += form.fields[field].sourceField + '__' + 'iexact=:value'; + + $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-url', watchUrl); + $('input[name="' + form.fields[field].sourceModel + '_' + form.fields[field].sourceField + '"]').attr('data-source', field); + + var sourceModel = form.fields[field].sourceModel, + sourceField = form.fields[field].sourceField; + + // this logic makes sure that the form is being added, and that the lookup to be autopopulated is required + // we also look to see if the lookupinit autopopulateLookup parameter passed in by the controller is false + function fieldIsAutopopulatable() { + if (autopopulateLookup === false) { + return false; + } + if (parent_scope.mode === "add") { + if (parent_scope[sourceModel + "_field"].awRequiredWhen && + parent_scope[sourceModel + "_field"].awRequiredWhen.variable && + parent_scope[parent_scope[sourceModel + "_field"].awRequiredWhen.variable]) { + return true; + } else if (parent_scope[sourceModel + "_field"].addRequired === true) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + if (fieldIsAutopopulatable()) { + // Auto populate the field if there is only one result + Rest.setUrl(defaultUrl); + Rest.get() + .success(function(data) { + if (data.count === 1) { + parent_scope[field] = data.results[0].id; + if (parent_scope[form.name + '_form'] && form.fields[field] && sourceModel) { + parent_scope[sourceModel + '_' + sourceField] = + data.results[0][sourceField]; + if (parent_scope[form.name + '_form'][sourceModel + '_' + sourceField]) { + parent_scope[form.name + '_form'][sourceModel + '_' + sourceField] + .$setValidity('awlookup', true); + } + } + if (parent_scope[form.name + '_form']) { + parent_scope[form.name + '_form'].$setDirty(); + } + } + }) + .error(function(data, status) { + ProcessErrors(parent_scope, data, status, form, { + hdr: 'Error!', + msg: 'Failed to launch adhoc command. POST returned status: ' + + status + }); + }); + } + + + parent_scope['lookUp' + name] = function() { + + var master = {}, + scope = parent_scope.$new(), + name, hdr, buttons; + + // Generating the search list potentially kills the values held in scope for the field. + // We'll keep a copy in master{} that we can revert back to on cancel; + master[field] = scope[field]; + master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; + GenerateList.inject(list, { + mode: 'lookup', + id: 'LookupModal-dialog', + scope: scope, + instructions: instructions, + input_type: input_type + }); + + name = list.iterator.charAt(0).toUpperCase() + list.iterator.substring(1); + hdr = (params.hdr) ? params.hdr : 'Select ' + name; + + // Show pop-up + buttons = [{ + label: "Save", + onClick: function() { + scope.selectAction(); + }, + //icon: "fa-check", + "class": "btn btn-primary", + "id": "lookup-save-button" + }, { + label: "Cancel", + "class": "btn btn-default", + "id": "lookup-cancel-button", + onClick: function() { + $('#LookupModal-dialog').dialog('close'); + } + }]; + + if (scope.removeModalReady) { + scope.removeModalReady(); + } + scope.removeModalReady = scope.$on('ModalReady', function() { + $('#lookup-save-button').attr('disabled', 'disabled'); + $('#LookupModal-dialog').dialog('open'); + }); + + CreateDialog({ + scope: scope, + buttons: buttons, + width: 600, + height: (instructions) ? 625 : 450, + minWidth: 500, + title: hdr, + id: 'LookupModal-dialog', + onClose: function() { + setTimeout(function() { + scope.$apply(function() { + if (Empty(scope[field])) { + scope[field] = master[field]; + scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + master[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]; + } + }); + }, 300); + }, + callback: 'ModalReady' + }); + + SearchInit({ + scope: scope, + set: list.name, + list: list, + url: defaultUrl + }); + + PaginateInit({ + scope: scope, + list: list, + url: defaultUrl, + mode: 'lookup' + }); + + if (scope.lookupPostRefreshRemove) { + scope.lookupPostRefreshRemove(); + } + scope.lookupPostRefreshRemove = scope.$on('PostRefresh', function() { + var fld, i; + for (fld in list.fields) { + if (list.fields[fld].type && list.fields[fld].type === 'date') { + //convert dates to our standard format + for (i = 0; i < scope[list.name].length; i++) { + scope[list.name][i][fld] = FormatDate(new Date(scope[list.name][i][fld])); + } + } + } + + // List generator creates the list, resetting it and losing the previously selected value. + // If the selected value is in the current set, find it and mark selected. + if (!Empty(parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField])) { + scope[list.name].forEach(function(elem) { + if (elem[form.fields[field].sourceField] === + parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { + scope[field] = elem.id; + } + }); + + } + + if (!Empty(scope[field])) { + scope['toggle_' + list.iterator](scope[field]); + } + + }); + + scope.search(list.iterator); + + scope.selectAction = function() { + var i, found = false; + for (i = 0; i < scope[list.name].length; i++) { + if (scope[list.name][i].checked === '1' || scope[list.name][i].checked === 1 || scope[list.name][i].checked === true) { + found = true; + parent_scope[field] = scope[list.name][i].id; + if (parent_scope[form.name + '_form'] && form.fields[field] && form.fields[field].sourceModel) { + parent_scope[form.fields[field].sourceModel + '_' + form.fields[field].sourceField] = + scope[list.name][i][form.fields[field].sourceField]; + if (parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField]) { + parent_scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField] + .$setValidity('awlookup', true); + } + } + if (parent_scope[form.name + '_form']) { + parent_scope[form.name + '_form'].$setDirty(); + } + } + } + if (found) { + // Selection made + $('#LookupModal-dialog').dialog('close'); + if (postAction) { + postAction(); + } + if (callback) { + parent_scope.$emit(callback); + } + } + }; + + + scope['toggle_' + list.iterator] = function(id) { + var count = 0; + scope[list.name].forEach(function(row, i) { + if (row.id === id) { + if (row.checked) { + scope[list.name][i].success_class = 'success'; + } else { + row.checked = true; + scope[list.name][i].success_class = ''; + } + } else { + scope[list.name][i].checked = 0; + scope[list.name][i].success_class = ''; + } + }); + // Check if any rows are checked + scope[list.name].forEach(function(row) { + if (row.checked) { + count++; + } + }); + if (count === 0) { + $('#lookup-save-button').attr('disabled', 'disabled'); + } else { + $('#lookup-save-button').removeAttr('disabled'); + } + }; + }; + }; + + } +]; diff --git a/awx/ui/client/src/lookup/main.js b/awx/ui/client/src/lookup/main.js new file mode 100644 index 0000000000..f5ead33d5b --- /dev/null +++ b/awx/ui/client/src/lookup/main.js @@ -0,0 +1,12 @@ +/************************************************* + * Copyright (c) 2016 Ansible, Inc. + * + * All Rights Reserved + *************************************************/ +import LookUpInit from './lookup.factory'; +import listGenerator from '../shared/list-generator/main'; + +export default + angular.module('LookUpHelper', ['RestServices', 'Utilities', 'SearchHelper', + 'PaginationHelpers', listGenerator.name, 'ApiLoader', 'ModalDialog']) + .factory('LookUpInit', LookUpInit); diff --git a/awx/ui/client/src/partials/portal.html b/awx/ui/client/src/partials/portal.html index 077eed49e3..fe6039de54 100644 --- a/awx/ui/client/src/partials/portal.html +++ b/awx/ui/client/src/partials/portal.html @@ -32,4 +32,4 @@
- + From c69534de145856e89bde30ef272f7b5bcd807010 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Wed, 24 Feb 2016 10:11:09 -0500 Subject: [PATCH 52/57] Attached sub form work to source control form in projects.js, added title plus some styling Added some breathing room at the bottom of the sub form --- awx/ui/client/legacy-styles/forms.less | 22 +++++++++ awx/ui/client/src/forms/Credentials.js | 54 +++++++++++++++------- awx/ui/client/src/forms/Projects.js | 17 +++++-- awx/ui/client/src/shared/form-generator.js | 27 +++++++++++ 4 files changed, 99 insertions(+), 21 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 0556f7e1cd..f26f0c03d1 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -141,6 +141,28 @@ padding-right: 50px; } +.Form-subForm { + width: 100%; + border-left: 5px solid @default-border; + margin-left: -20px; + padding-left: 15px; + margin-bottom: 15px; + + .Form-formGroup { + float: left; + } +} + +.Form-subForm--title { + font-weight: bold; + text-transform: uppercase; + color: @default-interface-txt; + font-size: small; + width: 100%; + float: left; + margin-bottom: 10px; +} + .Form-textAreaLabel{ width:100%; } diff --git a/awx/ui/client/src/forms/Credentials.js b/awx/ui/client/src/forms/Credentials.js index 9921b3a27c..221ab12b22 100644 --- a/awx/ui/client/src/forms/Credentials.js +++ b/awx/ui/client/src/forms/Credentials.js @@ -18,6 +18,9 @@ export default editTitle: '{{ name }}', //Legend in edit mode name: 'credential', forceListeners: true, + subFormTitles: { + credentialSubForm: 'Type Details', + }, actions: { @@ -103,7 +106,8 @@ export default '\n', dataTitle: 'Type', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + hasSubForm: true, // helpCollapse: [{ // hdr: 'Select a Credential Type', // content: '
\n' + @@ -131,7 +135,8 @@ export default init: false }, autocomplete: false, - apiField: 'username' + apiField: 'username', + subForm: 'credentialSubForm', }, secret_key: { label: 'Secret Key', @@ -145,7 +150,8 @@ export default ask: false, clear: false, hasShowInputButton: true, - apiField: 'passwowrd' + apiField: 'passwowrd', + subForm: 'credentialSubForm' }, security_token: { label: 'STS Token', @@ -157,7 +163,8 @@ export default hasShowInputButton: true, dataTitle: 'STS Token', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + subForm: 'credentialSubForm' }, "host": { labelBind: 'hostLabel', @@ -172,7 +179,8 @@ export default awRequiredWhen: { variable: 'host_required', init: false - } + }, + subForm: 'credentialSubForm' }, "username": { labelBind: 'usernameLabel', @@ -183,7 +191,8 @@ export default variable: 'username_required', init: false }, - autocomplete: false + autocomplete: false, + subForm: "credentialSubForm" }, "email_address": { labelBind: 'usernameLabel', @@ -197,7 +206,8 @@ export default awPopOver: '

The email address assigned to the Google Compute Engine service account.

', dataTitle: 'Email', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + subForm: 'credentialSubForm' }, "subscription_id": { labelBind: "usernameLabel", @@ -213,8 +223,8 @@ export default awPopOver: '

Subscription ID is an Azure construct, which is mapped to a username.

', dataTitle: 'Subscription ID', dataPlacement: 'right', - dataContainer: "body" - + dataContainer: "body", + subForm: 'credentialSubForm' }, "api_key": { label: 'API Key', @@ -228,6 +238,7 @@ export default ask: false, hasShowInputButton: true, clear: false, + subForm: 'credentialSubForm' }, "password": { labelBind: 'passwordLabel', @@ -242,7 +253,8 @@ export default awRequiredWhen: { variable: "password_required", init: false - } + }, + subForm: "credentialSubForm" }, "ssh_password": { label: 'Password', // formally 'SSH Password' @@ -252,7 +264,8 @@ export default editRequired: false, ask: true, hasShowInputButton: true, - autocomplete: false + autocomplete: false, + subForm: 'credentialSubForm' }, "ssh_key_data": { labelBind: 'sshKeyDataLabel', @@ -273,7 +286,8 @@ export default awPopOverWatch: "key_description", dataTitle: 'Help', dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + subForm: "credentialSubForm" }, "ssh_key_unlock": { label: 'Private Key Passphrase', @@ -285,6 +299,7 @@ export default ask: true, hasShowInputButton: true, askShow: "kind.value == 'ssh'", // Only allow ask for machine credentials + subForm: 'credentialSubForm' }, "become_method": { label: "Privilege Escalation", @@ -297,7 +312,8 @@ export default "This is equivalent to specifying the --become-method=BECOME_METHOD parameter, where BECOME_METHOD could be "+ "sudo | su | pbrun | pfexec | runas
(defaults to sudo)

", dataPlacement: 'right', - dataContainer: "body" + dataContainer: "body", + subForm: 'credentialSubForm' }, "become_username": { label: 'Privilege Escalation Username', @@ -305,7 +321,8 @@ export default ngShow: "kind.value == 'ssh' && (become_method && become_method.value)", addRequired: false, editRequired: false, - autocomplete: false + autocomplete: false, + subForm: 'credentialSubForm' }, "become_password": { label: 'Privilege Escalation Password', @@ -315,7 +332,8 @@ export default editRequired: false, ask: true, hasShowInputButton: true, - autocomplete: false + autocomplete: false, + subForm: 'credentialSubForm' }, "project": { labelBind: 'projectLabel', @@ -331,7 +349,8 @@ export default awRequiredWhen: { variable: 'project_required', init: false - } + }, + subForm: 'credentialSubForm' }, "vault_password": { label: "Vault Password", @@ -341,7 +360,8 @@ export default editRequired: false, ask: true, hasShowInputButton: true, - autocomplete: false + autocomplete: false, + subForm: 'credentialSubForm' } }, diff --git a/awx/ui/client/src/forms/Projects.js b/awx/ui/client/src/forms/Projects.js index 3019bc9a69..dbf04c457f 100644 --- a/awx/ui/client/src/forms/Projects.js +++ b/awx/ui/client/src/forms/Projects.js @@ -19,6 +19,10 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) name: 'project', forceListeners: true, tabs: true, + subFormTitles: { + sourceSubForm: 'Source Details', + }, + fields: { name: { @@ -62,7 +66,8 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) ngOptions: 'type.label for type in scm_type_options track by type.value', ngChange: 'scmChange()', addRequired: true, - editRequired: true + editRequired: true, + hasSubForm: true }, missing_path_alert: { type: 'alertblock', @@ -112,6 +117,7 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) variable: "scmRequired", init: false }, + subForm: 'sourceSubForm', helpCollapse: [{ hdr: 'GIT URLs', content: '

Example URLs for GIT SCM include:

  • https://github.com/ansible/ansible.git
  • ' + @@ -135,14 +141,15 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) 'Do not put the username and key in the URL. ' + 'If using Bitbucket and SSH, do not supply your Bitbucket username.', show: "scm_type.value == 'hg'" - }] + }], }, scm_branch: { labelBind: "scmBranchLabel", type: 'text', ngShow: "scm_type && scm_type.value !== 'manual'", addRequired: false, - editRequired: false + editRequired: false, + subForm: 'sourceSubForm' }, credential: { label: 'SCM Credential', @@ -152,12 +159,14 @@ angular.module('ProjectFormDefinition', ['SchedulesListDefinition']) sourceField: 'name', ngClick: 'lookUpCredential()', addRequired: false, - editRequired: false + editRequired: false, + subForm: 'sourceSubForm' }, checkbox_group: { label: 'SCM Update Options', type: 'checkbox_group', ngShow: "scm_type && scm_type.value !== 'manual'", + subForm: 'sourceSubForm', fields: [{ name: 'scm_clean', label: 'Clean', diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js index ed9aa488ae..693a090f68 100644 --- a/awx/ui/client/src/shared/form-generator.js +++ b/awx/ui/client/src/shared/form-generator.js @@ -1476,6 +1476,9 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat } html += "
\n"; } else { + var inSubForm = false; + var currentSubForm = undefined; + var hasSubFormField; // original, single-column form section = ''; group = ''; @@ -1502,9 +1505,33 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat html += "\n"; section = field.section; } + + // To hide/show the subform when the value changes on parent + if (field.hasSubForm === true) { + hasSubFormField = fld; + } + + // Add a subform container + if(field.subForm && currentSubForm === undefined) { + currentSubForm = field.subForm; + var subFormTitle = this.form.subFormTitles[field.subForm]; + + html += '
'; + html += ''+ subFormTitle +''; + } + else if (!field.subForm && currentSubForm !== undefined) { + currentSubForm = undefined; + html += '
'; + } + html += this.buildField(fld, field, options, this.form); + } } + if (currentSubForm) { + currentSubForm = undefined; + html += '
'; + } if (section !== '') { html += "
\n
\n"; } From 28f693b7bdbbf44f06d86e9b7bb9f649ef412898 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 25 Feb 2016 09:43:20 -0500 Subject: [PATCH 53/57] #951 #999 add missing dependency injection to unit test, fix misc styles --- awx/ui/client/legacy-styles/forms.less | 3 ++- awx/ui/client/src/adhoc/adhoc.controller.js | 2 +- awx/ui/client/src/controllers/Inventories.js | 1 - awx/ui/client/tests/adhoc/adhoc.controller-test.js | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 5f1ae9f449..86f10ec1c2 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -363,8 +363,9 @@ input[type='radio']:checked:before { display: flex; justify-content: flex-end; } + .Form-button{ - margin-left: 4px; + margin-left: 4px; } .Form-buttonDefault { diff --git a/awx/ui/client/src/adhoc/adhoc.controller.js b/awx/ui/client/src/adhoc/adhoc.controller.js index 17ca17783a..cb9e0a648e 100644 --- a/awx/ui/client/src/adhoc/adhoc.controller.js +++ b/awx/ui/client/src/adhoc/adhoc.controller.js @@ -1,5 +1,5 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2016 Ansible, Inc. * * All Rights Reserved *************************************************/ diff --git a/awx/ui/client/src/controllers/Inventories.js b/awx/ui/client/src/controllers/Inventories.js index 9b2774bd98..916b812976 100644 --- a/awx/ui/client/src/controllers/Inventories.js +++ b/awx/ui/client/src/controllers/Inventories.js @@ -860,7 +860,6 @@ export function InventoriesManage ($log, $scope, $rootScope, $location, } $rootScope.hostPatterns = host_patterns; $state.go('inventoryManage.adhoc'); - //$location.path('/inventories/' + $scope.inventory.id + '/adhoc'); }; $scope.refreshHostsOnGroupRefresh = false; diff --git a/awx/ui/client/tests/adhoc/adhoc.controller-test.js b/awx/ui/client/tests/adhoc/adhoc.controller-test.js index c60af03f44..26583b71dd 100644 --- a/awx/ui/client/tests/adhoc/adhoc.controller-test.js +++ b/awx/ui/client/tests/adhoc/adhoc.controller-test.js @@ -55,6 +55,7 @@ describe("adhoc.controller", function() { $provide.value('Wait', waitCallback); $provide.value('$stateExtender', stateExtenderCallback); $provide.value('$stateParams', angular.noop); + $provide.value('$state', angular.noop); }])); beforeEach("put $q in scope", window.inject(['$q', function($q) { From 908bc92f943f4150fe6765b5fd257219f740a0f5 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 25 Feb 2016 09:58:35 -0500 Subject: [PATCH 54/57] more misc styles #951 #999 --- awx/ui/client/legacy-styles/forms.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index 86f10ec1c2..df21f8534c 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -369,9 +369,9 @@ input[type='radio']:checked:before { } .Form-buttonDefault { - background-color: #FFFFFF; - color: #848992; - border-color: #E8E8E8; + background-color: @default-bg; + color: @default-interface-txt; + border-color: @default-border; } .Form-saveButton{ From a99e21521b227892646116492a90f77e4965fbf9 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 25 Feb 2016 10:16:15 -0500 Subject: [PATCH 55/57] force jenkins rebuild --- awx/ui/client/src/management-jobs/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/management-jobs/main.js b/awx/ui/client/src/management-jobs/main.js index 35e6c10c76..e881baa0fb 100644 --- a/awx/ui/client/src/management-jobs/main.js +++ b/awx/ui/client/src/management-jobs/main.js @@ -1,5 +1,5 @@ /************************************************* - * Copyright (c) 2015 Ansible, Inc. + * Copyright (c) 2016 Ansible, Inc. * * All Rights Reserved *************************************************/ From f8fe6d9ae40346a2ecb2120a29ad467e15ba7151 Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Thu, 25 Feb 2016 12:12:28 -0500 Subject: [PATCH 56/57] Removed Alert dependency. --- awx/ui/client/src/lookup/lookup.factory.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/lookup/lookup.factory.js b/awx/ui/client/src/lookup/lookup.factory.js index 468acbc4e6..e52dfb4876 100644 --- a/awx/ui/client/src/lookup/lookup.factory.js +++ b/awx/ui/client/src/lookup/lookup.factory.js @@ -21,10 +21,10 @@ * }) */ -export default ['Alert', 'Rest', 'ProcessErrors', 'generateList', +export default ['Rest', 'ProcessErrors', 'generateList', 'SearchInit', 'PaginateInit', 'GetBasePath', 'FormatDate', 'Empty', 'CreateDialog', - function(Alert, Rest, ProcessErrors, GenerateList, + function(Rest, ProcessErrors, GenerateList, SearchInit, PaginateInit, GetBasePath, FormatDate, Empty, CreateDialog) { return function(params) { From 393db691ed07492a1d5652b994cdee522db7292a Mon Sep 17 00:00:00 2001 From: Ken Hoes Date: Thu, 25 Feb 2016 12:15:07 -0500 Subject: [PATCH 57/57] Cleaned up indentation. --- awx/ui/client/legacy-styles/forms.less | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/awx/ui/client/legacy-styles/forms.less b/awx/ui/client/legacy-styles/forms.less index f26f0c03d1..cadb7f14eb 100644 --- a/awx/ui/client/legacy-styles/forms.less +++ b/awx/ui/client/legacy-styles/forms.less @@ -142,15 +142,15 @@ } .Form-subForm { - width: 100%; - border-left: 5px solid @default-border; - margin-left: -20px; - padding-left: 15px; - margin-bottom: 15px; + width: 100%; + border-left: 5px solid @default-border; + margin-left: -20px; + padding-left: 15px; + margin-bottom: 15px; - .Form-formGroup { - float: left; - } + .Form-formGroup { + float: left; + } } .Form-subForm--title {