From 9fe9866fc219c5a0e3ca1f4f62da13f5faceed18 Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Tue, 6 Sep 2016 12:47:49 -0400 Subject: [PATCH 01/45] remove stray print statement --- awx/main/access.py | 1 - 1 file changed, 1 deletion(-) diff --git a/awx/main/access.py b/awx/main/access.py index 5fa3b76274..734dce5ecc 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -661,7 +661,6 @@ class CredentialAccess(BaseAccess): or (not organization_pk and obj.organization): return False - print(self.user in obj.admin_role) return self.user in obj.admin_role def can_delete(self, obj): From 646a9f1570e85e0cfa31141b5e0e544c4f711a37 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Wed, 7 Sep 2016 15:53:41 -0700 Subject: [PATCH 02/45] Update Pendo service for 3.0 changes to retreiving the config endpoint --- .../authenticationServices/pendo.service.js | 63 ++++--------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/awx/ui/client/src/login/authenticationServices/pendo.service.js b/awx/ui/client/src/login/authenticationServices/pendo.service.js index 5e034eaa87..dbfe407820 100644 --- a/awx/ui/client/src/login/authenticationServices/pendo.service.js +++ b/awx/ui/client/src/login/authenticationServices/pendo.service.js @@ -92,58 +92,21 @@ export default return deferred.promise; }, - getConfig: function () { - var config = ConfigService.get(), - deferred = $q.defer(); - if(_.isEmpty(config)){ - var url = GetBasePath('config'); - Rest.setUrl(url); - var promise = Rest.get(); - promise.then(function (response) { - config = response.data.license_info; - config.analytics_status = response.data.analytics_status; - config.version = response.data.version; - config.ansible_version = response.data.ansible_version; - if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){ - $pendolytics.bootstrap(); - deferred.resolve(config); - } - else { - deferred.reject('Pendo is turned off.'); - } - }); - promise.catch(function (response) { - ProcessErrors($rootScope, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory name. GET returned status: ' + - response.status }); - deferred.reject('Could not resolve pendo config.'); - }); - } - else if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){ - $pendolytics.bootstrap(); - deferred.resolve(config); - } - else { - deferred.reject('Pendo is turned off.'); - } - return deferred.promise; - }, - issuePendoIdentity: function () { - var that = this; - this.getConfig().then(function(config){ - var options = that.setPendoOptions(config); - that.setRole(options).then(function(options){ - $log.debug('Pendo status is '+ config.analytics_status + '. Object below:'); - $log.debug(options); - $pendolytics.identify(options); - }, function(reason){ - // reject function for setRole - $log.debug(reason); - }); + var config, + options, + c = ConfigService.get(), + config = c.license_info; + config.analytics_status = c.analytics_status; + config.version = c.version; + config.ansible_version = c.ansible_version; + options = this.setPendoOptions(config); + this.setRole(options).then(function(options){ + $log.debug('Pendo status is '+ config.analytics_status + '. Object below:'); + $log.debug(options); + $pendolytics.identify(options); }, function(reason){ - // reject function for getConfig + // reject function for setRole $log.debug(reason); }); } From 56d42100476955a2c42ab156c75844f84e390783 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 14 Sep 2016 16:06:13 -0400 Subject: [PATCH 03/45] Implement Performance improvements shown in #3492 * Make callback workers tunable * Disable callback worker recycling - doesn't appear to be needed anymore * Tweak pexpect behavior by limiting its read buffer size and search window * Use copy instead of deepcopy for job event callback emitter censor --- .../management/commands/run_callback_receiver.py | 13 +++---------- awx/main/tasks.py | 2 +- awx/plugins/callback/job_event_callback.py | 4 ++-- awx/settings/defaults.py | 3 +++ 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/awx/main/management/commands/run_callback_receiver.py b/awx/main/management/commands/run_callback_receiver.py index e6080fa419..c31b3dffe2 100644 --- a/awx/main/management/commands/run_callback_receiver.py +++ b/awx/main/management/commands/run_callback_receiver.py @@ -25,8 +25,6 @@ from awx.main.socket import Socket logger = logging.getLogger('awx.main.commands.run_callback_receiver') -WORKERS = 4 - class CallbackReceiver(object): def __init__(self): self.parent_mappings = {} @@ -54,7 +52,7 @@ class CallbackReceiver(object): if use_workers: connection.close() - for idx in range(WORKERS): + for idx in range(settings.JOB_EVENT_WORKERS): queue_actual = Queue(settings.JOB_EVENT_MAX_QUEUE_SIZE) w = Process(target=self.callback_worker, args=(queue_actual, idx,)) w.start() @@ -99,7 +97,7 @@ class CallbackReceiver(object): time.sleep(0.1) def write_queue_worker(self, preferred_queue, worker_queues, message): - queue_order = sorted(range(WORKERS), cmp=lambda x, y: -1 if x==preferred_queue else 0) + queue_order = sorted(range(settings.JOB_EVENT_WORKERS), cmp=lambda x, y: -1 if x==preferred_queue else 0) for queue_actual in queue_order: try: worker_actual = worker_queues[queue_actual] @@ -153,7 +151,7 @@ class CallbackReceiver(object): if message['event'] == 'playbook_on_stats': job_parent_events = {} - actual_queue = self.write_queue_worker(total_messages % WORKERS, worker_queues, message) + actual_queue = self.write_queue_worker(total_messages % settings.JOB_EVENT_WORKERS, worker_queues, message) # NOTE: It might be better to recycle the entire callback receiver process if one or more of the queues are too full # the drawback is that if we under extremely high load we may be legitimately taking a while to process messages if actual_queue is None: @@ -282,7 +280,6 @@ class CallbackReceiver(object): return None def callback_worker(self, queue_actual, idx): - messages_processed = 0 while True: try: message = queue_actual.get(block=True, timeout=1) @@ -292,10 +289,6 @@ class CallbackReceiver(object): logger.error("Exception on listen socket, restarting: " + str(e)) break self.process_job_event(message) - messages_processed += 1 - if messages_processed >= settings.JOB_EVENT_RECYCLE_THRESHOLD: - logger.info("Shutting down message receiver") - break class Command(NoArgsCommand): ''' diff --git a/awx/main/tasks.py b/awx/main/tasks.py index c99f043e1a..20edd955ed 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -575,7 +575,7 @@ class BaseTask(Task): instance = self.update_model(instance.pk, status='running', output_replacements=output_replacements) while child.isalive(): - result_id = child.expect(expect_list, timeout=pexpect_timeout) + result_id = child.expect(expect_list, timeout=pexpect_timeout, maxread=100, searchwindowsize=100) if result_id in expect_passwords: child.sendline(expect_passwords[result_id]) if logfile_pos != logfile.tell(): diff --git a/awx/plugins/callback/job_event_callback.py b/awx/plugins/callback/job_event_callback.py index a9c5b712ed..2049edc4b8 100644 --- a/awx/plugins/callback/job_event_callback.py +++ b/awx/plugins/callback/job_event_callback.py @@ -38,7 +38,7 @@ import os import pwd import urlparse import re -from copy import deepcopy +from copy import copy # Requests import requests @@ -228,7 +228,7 @@ class BaseCallbackModule(object): def _log_event(self, event, **event_data): if 'res' in event_data: - event_data['res'] = censor(deepcopy(event_data['res'])) + event_data['res'] = censor(copy(event_data['res'])) if self.callback_consumer_port: self._post_job_event_queue_msg(event, event_data) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 2998d15bb7..e2995d0dbd 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -438,6 +438,9 @@ AWX_TASK_ENV = {} # before it recycles JOB_EVENT_RECYCLE_THRESHOLD = 3000 +# Number of workers used to proecess job events in parallel +JOB_EVENT_WORKERS = 4 + # Maximum number of job events that can be waiting on a single worker queue before # it can be skipped as too busy JOB_EVENT_MAX_QUEUE_SIZE = 100 From fb2ab05045a47c70b7ce2d643c59743a79de04b1 Mon Sep 17 00:00:00 2001 From: Jim Ladd Date: Thu, 15 Sep 2016 16:58:50 -0400 Subject: [PATCH 04/45] Bump version for 3.0.3 --- awx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/__init__.py b/awx/__init__.py index bf3f75255c..fe3644cf53 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -5,7 +5,7 @@ import os import sys import warnings -__version__ = '3.0.2' +__version__ = '3.0.3' __all__ = ['__version__'] From c74880756f10fbcc69ca8431b631a45d9c6b7174 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 16 Sep 2016 14:13:53 -0700 Subject: [PATCH 05/45] Pendo should not issue identity when turned off Fixes an issue whereh the identity was issued to the Pendo agent despite an "analytic_status": "off" --- .../authenticationServices/pendo.service.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/login/authenticationServices/pendo.service.js b/awx/ui/client/src/login/authenticationServices/pendo.service.js index dbfe407820..3eaf06e0b0 100644 --- a/awx/ui/client/src/login/authenticationServices/pendo.service.js +++ b/awx/ui/client/src/login/authenticationServices/pendo.service.js @@ -100,15 +100,21 @@ export default config.analytics_status = c.analytics_status; config.version = c.version; config.ansible_version = c.ansible_version; - options = this.setPendoOptions(config); - this.setRole(options).then(function(options){ - $log.debug('Pendo status is '+ config.analytics_status + '. Object below:'); - $log.debug(options); - $pendolytics.identify(options); - }, function(reason){ - // reject function for setRole - $log.debug(reason); - }); + if(config.analytics_status === 'detailed' || config.analytics_status === 'anonymous'){ + $pendolytics.bootstrap(); + options = this.setPendoOptions(config); + this.setRole(options).then(function(options){ + $log.debug('Pendo status is '+ config.analytics_status + '. Object below:'); + $log.debug(options); + $pendolytics.identify(options); + }, function(reason){ + // reject function for setRole + $log.debug(reason); + }); + } + else { + $log.debug('Pendo is turned off.') + } } }; } From 654633f556ea4ad2f97f8258c917b4fbc276291a Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 16 Sep 2016 14:53:58 -0700 Subject: [PATCH 06/45] Fix for Pendo if trial key/value pair is missing from the config endpoint -> license_info --- .../client/src/login/authenticationServices/pendo.service.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awx/ui/client/src/login/authenticationServices/pendo.service.js b/awx/ui/client/src/login/authenticationServices/pendo.service.js index 3eaf06e0b0..10cdbd33d8 100644 --- a/awx/ui/client/src/login/authenticationServices/pendo.service.js +++ b/awx/ui/client/src/login/authenticationServices/pendo.service.js @@ -13,6 +13,7 @@ export default return { setPendoOptions: function (config) { var tower_version = config.version.split('-')[0], + trial = (config.trial) ? config.trial : false, options = { visitor: { id: null, @@ -24,7 +25,7 @@ export default planLevel: config.license_type, planPrice: config.instance_count, creationDate: config.license_date, - trial: config.trial, + trial: trial, tower_version: tower_version, ansible_version: config.ansible_version } From 1268c8e205486bb2647f9016a3cf1f926ca1d328 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 23 Sep 2016 08:22:26 -0400 Subject: [PATCH 07/45] update authentication class in postprocess and add unit test --- awx/main/tests/unit/test_settings.py | 11 +++++++++++ awx/settings/postprocess.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 awx/main/tests/unit/test_settings.py diff --git a/awx/main/tests/unit/test_settings.py b/awx/main/tests/unit/test_settings.py new file mode 100644 index 0000000000..2018771c63 --- /dev/null +++ b/awx/main/tests/unit/test_settings.py @@ -0,0 +1,11 @@ +from split_settings.tools import include + +def test_postprocess_auth_basic_enabled(): + locals().update({'__file__': __file__}) + + include('../../../settings/defaults.py', scope=locals()) + assert 'awx.api.authentication.LoggedBasicAuthentication' in locals()['REST_FRAMEWORK']['DEFAULT_AUTHENTICATION_CLASSES'] + + locals().update({'AUTH_BASIC_ENABLED': False}) + include('../../../settings/postprocess.py', scope=locals()) + assert 'awx.api.authentication.LoggedBasicAuthentication' not in locals()['REST_FRAMEWORK']['DEFAULT_AUTHENTICATION_CLASSES'] diff --git a/awx/settings/postprocess.py b/awx/settings/postprocess.py index 544758e04f..fd54fd5050 100644 --- a/awx/settings/postprocess.py +++ b/awx/settings/postprocess.py @@ -31,4 +31,4 @@ if not all([SOCIAL_AUTH_SAML_SP_ENTITY_ID, SOCIAL_AUTH_SAML_SP_PUBLIC_CERT, AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'awx.sso.backends.SAMLAuth'] if not AUTH_BASIC_ENABLED: - REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = [x for x in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] if x != 'rest_framework.authentication.BasicAuthentication'] + REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = [x for x in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] if x != 'awx.api.authentication.LoggedBasicAuthentication'] From 5fa1632d1e2cc0e065cbd6cd6ba7f31a1cd7b741 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 23 Sep 2016 08:41:13 -0400 Subject: [PATCH 08/45] flake8 drive-by --- awx/sso/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/sso/__init__.py b/awx/sso/__init__.py index 347aedfeee..6596e4bf78 100644 --- a/awx/sso/__init__.py +++ b/awx/sso/__init__.py @@ -8,7 +8,7 @@ import threading xmlsec_init_lock = threading.Lock() xmlsec_initialized = False -import dm.xmlsec.binding +import dm.xmlsec.binding # noqa original_xmlsec_initialize = dm.xmlsec.binding.initialize def xmlsec_initialize(*args, **kwargs): From fd18e2eebd678a0c2230593f28b4950474dadb5f Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 23 Sep 2016 09:12:36 -0400 Subject: [PATCH 09/45] add host variable dash fix as a migration --- .../0033_v303_v245_host_variable_fix.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 awx/main/migrations/0033_v303_v245_host_variable_fix.py diff --git a/awx/main/migrations/0033_v303_v245_host_variable_fix.py b/awx/main/migrations/0033_v303_v245_host_variable_fix.py new file mode 100644 index 0000000000..fad3545b65 --- /dev/null +++ b/awx/main/migrations/0033_v303_v245_host_variable_fix.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +from awx.main.migrations import _migration_utils as migration_utils + + +def update_dashed_host_variables(apps, schema_editor): + Host = apps.get_model('main', 'Host') + for host in Host.objects.filter(variables='---'): + host.variables = '' + host.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0032_v302_credential_permissions_update'), + ] + + operations = [ + migrations.RunPython(migration_utils.set_current_apps_for_migrations), + migrations.RunPython(update_dashed_host_variables), + ] From a4adda1ae719149e21bcd9e7690a88646509561d Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 23 Sep 2016 10:10:39 -0400 Subject: [PATCH 10/45] only allow superusers to start a job from a SystemJobTemplate --- awx/main/access.py | 4 +++- awx/main/tests/unit/test_access.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 734dce5ecc..72636e1776 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1122,8 +1122,10 @@ class SystemJobTemplateAccess(BaseAccess): model = SystemJobTemplate + @check_superuser def can_start(self, obj): - return self.can_read(obj) + '''Only a superuser can start a job from a SystemJobTemplate''' + return False class SystemJobAccess(BaseAccess): ''' diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index 000d91268c..0c2e6bb5be 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -8,8 +8,16 @@ from awx.main.access import ( BaseAccess, check_superuser, JobTemplateAccess, + SystemJobTemplateAccess, +) + +from awx.main.models import ( + Credential, + Inventory, + Project, + Role, + Organization, ) -from awx.main.models import Credential, Inventory, Project, Role, Organization @pytest.fixture @@ -110,3 +118,12 @@ def test_jt_can_add_bad_data(user_unit): access = JobTemplateAccess(user_unit) assert not access.can_add({'asdf': 'asdf'}) +def test_system_job_template_can_start(mocker): + user = mocker.MagicMock(spec=User, id=1, is_system_auditor=True, is_superuser=False) + assert user.is_system_auditor + access = SystemJobTemplateAccess(user) + assert not access.can_start(None) + + user.is_superuser = True + access = SystemJobTemplateAccess(user) + assert access.can_start(None) From 898cbbf26e927da26ec0058e711755a71dfead50 Mon Sep 17 00:00:00 2001 From: nitzmahone Date: Thu, 15 Sep 2016 19:33:08 -0700 Subject: [PATCH 11/45] update azure_rm inventory script to 2.0.0rc5-target --- awx/plugins/inventory/azure_rm.ini.example | 7 +- awx/plugins/inventory/azure_rm.py | 86 ++++++++++++---------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/awx/plugins/inventory/azure_rm.ini.example b/awx/plugins/inventory/azure_rm.ini.example index 6ea2688efa..816da16532 100644 --- a/awx/plugins/inventory/azure_rm.ini.example +++ b/awx/plugins/inventory/azure_rm.ini.example @@ -1,5 +1,5 @@ # -# Configuration file for azure_rm_invetory.py +# Configuration file for azure_rm.py # [azure] # Control which resource groups are included. By default all resources groups are included. @@ -9,11 +9,14 @@ # Control which tags are included. Set tags to a comma separated list of keys or key:value pairs #tags= +# Control which locations are included. Set locations to a comma separated list (e.g. eastus,eastus2,westus) +#locations= + # Include powerstate. If you don't need powerstate information, turning it off improves runtime performance. include_powerstate=yes # Control grouping with the following boolean flags. Valid values: yes, no, true, false, True, False, 0, 1. group_by_resource_group=yes group_by_location=yes -group_by_security_group=no +group_by_security_group=yes group_by_tag=yes diff --git a/awx/plugins/inventory/azure_rm.py b/awx/plugins/inventory/azure_rm.py index 0554ecf879..f3c9e7c28d 100755 --- a/awx/plugins/inventory/azure_rm.py +++ b/awx/plugins/inventory/azure_rm.py @@ -76,7 +76,7 @@ required. For a specific host, this script returns the following variables: "version": "latest" }, "location": "westus", - "mac_address": "00-0D-3A-31-2C-EC", + "mac_address": "00-00-5E-00-53-FE", "name": "object-name", "network_interface": "interface-name", "network_interface_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/networkInterfaces/object-name1", @@ -115,7 +115,7 @@ When run in --list mode, instances are grouped by the following categories: - tag key - tag key_value -Control groups using azure_rm_inventory.ini or set environment variables: +Control groups using azure_rm.ini or set environment variables: AZURE_GROUP_BY_RESOURCE_GROUP=yes AZURE_GROUP_BY_LOCATION=yes @@ -130,6 +130,10 @@ Select hosts for specific tag key by assigning a comma separated list of tag key AZURE_TAGS=key1,key2,key3 +Select hosts for specific locations: + +AZURE_LOCATIONS=eastus,westus,eastus2 + Or, select hosts for specific tag key:value pairs by assigning a comma separated list key:value pairs to: AZURE_TAGS=key1:value1,key2:value2 @@ -137,12 +141,14 @@ AZURE_TAGS=key1:value1,key2:value2 If you don't need the powerstate, you can improve performance by turning off powerstate fetching: AZURE_INCLUDE_POWERSTATE=no -azure_rm_inventory.ini ----------------------- -As mentioned above you can control execution using environment variables or an .ini file. A sample -azure_rm_inventory.ini is included. The name of the .ini file is the basename of the inventory script (in this case -'azure_rm_inventory') with a .ini extension. This provides you with the flexibility of copying and customizing this -script and having matching .ini files. Go forth and customize your Azure inventory! +azure_rm.ini +------------ +As mentioned above, you can control execution using environment variables or a .ini file. A sample +azure_rm.ini is included. The name of the .ini file is the basename of the inventory script (in this case +'azure_rm') with a .ini extension. It also assumes the .ini file is alongside the script. To specify +a different path for the .ini file, define the AZURE_INI_PATH environment variable: + + export AZURE_INI_PATH=/path/to/custom.ini Powerstate: ----------- @@ -152,13 +158,13 @@ up. If the value is anything other than 'running', the machine is down, and will Examples: --------- Execute /bin/uname on all instances in the galaxy-qa resource group - $ ansible -i azure_rm_inventory.py galaxy-qa -m shell -a "/bin/uname -a" + $ ansible -i azure_rm.py galaxy-qa -m shell -a "/bin/uname -a" Use the inventory script to print instance specific information - $ contrib/inventory/azure_rm_inventory.py --host my_instance_host_name --pretty + $ contrib/inventory/azure_rm.py --host my_instance_host_name --pretty Use with a playbook - $ ansible-playbook -i contrib/inventory/azure_rm_inventory.py my_playbook.yml --limit galaxy-qa + $ ansible-playbook -i contrib/inventory/azure_rm.py my_playbook.yml --limit galaxy-qa Insecure Platform Warning @@ -180,11 +186,13 @@ Version: 1.0.0 import argparse import ConfigParser -import json +import json import os import re import sys +from distutils.version import LooseVersion + from os.path import expanduser HAS_AZURE = True @@ -195,12 +203,9 @@ try: from azure.mgmt.compute import __version__ as azure_compute_version from azure.common import AzureMissingResourceHttpError, AzureHttpError from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials - from azure.mgmt.network.network_management_client import NetworkManagementClient,\ - NetworkManagementClientConfiguration - from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient,\ - ResourceManagementClientConfiguration - from azure.mgmt.compute.compute_management_client import ComputeManagementClient,\ - ComputeManagementClientConfiguration + from azure.mgmt.network.network_management_client import NetworkManagementClient + from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient + from azure.mgmt.compute.compute_management_client import ComputeManagementClient except ImportError as exc: HAS_AZURE_EXC = exc HAS_AZURE = False @@ -219,6 +224,7 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict( AZURE_CONFIG_SETTINGS = dict( resource_groups='AZURE_RESOURCE_GROUPS', tags='AZURE_TAGS', + locations='AZURE_LOCATIONS', include_powerstate='AZURE_INCLUDE_POWERSTATE', group_by_resource_group='AZURE_GROUP_BY_RESOURCE_GROUP', group_by_location='AZURE_GROUP_BY_LOCATION', @@ -226,7 +232,7 @@ AZURE_CONFIG_SETTINGS = dict( group_by_tag='AZURE_GROUP_BY_TAG' ) -AZURE_MIN_VERSION = "2016-03-30" +AZURE_MIN_VERSION = "0.30.0rc5" def azure_id_to_dict(id): @@ -362,8 +368,7 @@ class AzureRM(object): def network_client(self): self.log('Getting network client') if not self._network_client: - self._network_client = NetworkManagementClient( - NetworkManagementClientConfiguration(self.azure_credentials, self.subscription_id)) + self._network_client = NetworkManagementClient(self.azure_credentials, self.subscription_id) self._register('Microsoft.Network') return self._network_client @@ -371,16 +376,14 @@ class AzureRM(object): def rm_client(self): self.log('Getting resource manager client') if not self._resource_client: - self._resource_client = ResourceManagementClient( - ResourceManagementClientConfiguration(self.azure_credentials, self.subscription_id)) + self._resource_client = ResourceManagementClient(self.azure_credentials, self.subscription_id) return self._resource_client @property def compute_client(self): self.log('Getting compute client') if not self._compute_client: - self._compute_client = ComputeManagementClient( - ComputeManagementClientConfiguration(self.azure_credentials, self.subscription_id)) + self._compute_client = ComputeManagementClient(self.azure_credentials, self.subscription_id) self._register('Microsoft.Compute') return self._compute_client @@ -403,6 +406,7 @@ class AzureInventory(object): self.resource_groups = [] self.tags = None + self.locations = None self.replace_dash_in_groups = False self.group_by_resource_group = True self.group_by_location = True @@ -425,6 +429,9 @@ class AzureInventory(object): if self._args.tags: self.tags = self._args.tags.split(',') + if self._args.locations: + self.locations = self._args.locations.split(',') + if self._args.no_powerstate: self.include_powerstate = False @@ -462,6 +469,8 @@ class AzureInventory(object): help='Return inventory for comma separated list of resource group names') parser.add_argument('--tags', action='store', help='Return inventory for comma separated list of tag key:value pairs') + parser.add_argument('--locations', action='store', + help='Return inventory for comma separated list of locations') parser.add_argument('--no-powerstate', action='store_true', default=False, help='Do not include the power state of each virtual host') return parser.parse_args() @@ -487,7 +496,7 @@ class AzureInventory(object): except Exception as exc: sys.exit("Error: fetching virtual machines - {0}".format(str(exc))) - if self._args.host or self.tags > 0: + if self._args.host or self.tags or self.locations: selected_machines = self._selected_machines(virtual_machines) self._load_machines(selected_machines) else: @@ -524,7 +533,7 @@ class AzureInventory(object): resource_group=resource_group, mac_address=None, plan=(machine.plan.name if machine.plan else None), - virtual_machine_size=machine.hardware_profile.vm_size.value, + virtual_machine_size=machine.hardware_profile.vm_size, computer_name=machine.os_profile.computer_name, provisioning_state=machine.provisioning_state, ) @@ -576,7 +585,7 @@ class AzureInventory(object): host_vars['mac_address'] = network_interface.mac_address for ip_config in network_interface.ip_configurations: host_vars['private_ip'] = ip_config.private_ip_address - host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method.value + host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method if ip_config.public_ip_address: public_ip_reference = self._parse_ref_id(ip_config.public_ip_address.id) public_ip_address = self._network_client.public_ip_addresses.get( @@ -585,7 +594,7 @@ class AzureInventory(object): host_vars['ansible_host'] = public_ip_address.ip_address host_vars['public_ip'] = public_ip_address.ip_address host_vars['public_ip_name'] = public_ip_address.name - host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method.value + host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method host_vars['public_ip_id'] = public_ip_address.id if public_ip_address.dns_settings: host_vars['fqdn'] = public_ip_address.dns_settings.fqdn @@ -599,6 +608,8 @@ class AzureInventory(object): selected_machines.append(machine) if self.tags and self._tags_match(machine.tags, self.tags): selected_machines.append(machine) + if self.locations and machine.location in self.locations: + selected_machines.append(machine) return selected_machines def _get_security_groups(self, resource_group): @@ -676,17 +687,17 @@ class AzureInventory(object): file_settings = self._load_settings() if file_settings: for key in AZURE_CONFIG_SETTINGS: - if key in ('resource_groups', 'tags') and file_settings.get(key, None) is not None: + if key in ('resource_groups', 'tags', 'locations') and file_settings.get(key): values = file_settings.get(key).split(',') if len(values) > 0: setattr(self, key, values) - elif file_settings.get(key, None) is not None: + elif file_settings.get(key): val = self._to_boolean(file_settings[key]) setattr(self, key, val) else: env_settings = self._get_env_settings() for key in AZURE_CONFIG_SETTINGS: - if key in('resource_groups', 'tags') and env_settings.get(key, None) is not None: + if key in('resource_groups', 'tags', 'locations') and env_settings.get(key): values = env_settings.get(key).split(',') if len(values) > 0: setattr(self, key, values) @@ -719,7 +730,8 @@ class AzureInventory(object): def _load_settings(self): basename = os.path.splitext(os.path.basename(__file__))[0] - path = basename + '.ini' + default_path = os.path.join(os.path.dirname(__file__), (basename + '.ini')) + path = os.path.expanduser(os.path.expandvars(os.environ.get('AZURE_INI_PATH', default_path))) config = None settings = None try: @@ -774,11 +786,11 @@ class AzureInventory(object): def main(): if not HAS_AZURE: - sys.exit("The Azure python sdk is not installed (try 'pip install azure') - {0}".format(HAS_AZURE_EXC)) + sys.exit("The Azure python sdk is not installed (try 'pip install azure==2.0.0rc5') - {0}".format(HAS_AZURE_EXC)) - if azure_compute_version < AZURE_MIN_VERSION: - sys.exit("Expecting azure.mgmt.compute.__version__ to be >= {0}. Found version {1} " - "Do you have Azure >= 2.0.0rc2 installed?".format(AZURE_MIN_VERSION, azure_compute_version)) + if LooseVersion(azure_compute_version) != LooseVersion(AZURE_MIN_VERSION): + sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} " + "Do you have Azure == 2.0.0rc5 installed?".format(AZURE_MIN_VERSION, azure_compute_version)) AzureInventory() From 162f0b81d1847fd025296326322a961d7b17a5b0 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 28 Sep 2016 13:15:53 -0400 Subject: [PATCH 12/45] Remove unneeded maxread searchwindowsize was the important bit here --- 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 20edd955ed..c76107d601 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -575,7 +575,7 @@ class BaseTask(Task): instance = self.update_model(instance.pk, status='running', output_replacements=output_replacements) while child.isalive(): - result_id = child.expect(expect_list, timeout=pexpect_timeout, maxread=100, searchwindowsize=100) + result_id = child.expect(expect_list, timeout=pexpect_timeout, searchwindowsize=100) if result_id in expect_passwords: child.sendline(expect_passwords[result_id]) if logfile_pos != logfile.tell(): From 839c2877c840314af4812b6e51b1c28a19b77dc6 Mon Sep 17 00:00:00 2001 From: nitzmahone Date: Mon, 8 Aug 2016 10:45:01 -0700 Subject: [PATCH 13/45] update requirements versions - current Ansible stuff (2.1.1) requires azure SDK 2.0.0rc5 - requests 2.10.0 is the published minimum version for pywinrm, as it contains a bugfix for catastrophic SSL tunnel failure on large payloads that pywinrm hits frequently, 2.11.0 is best tested. --- requirements/requirements_ansible.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt index b35cb6fcbb..e62e3e5ead 100644 --- a/requirements/requirements_ansible.txt +++ b/requirements/requirements_ansible.txt @@ -1,7 +1,7 @@ anyjson==0.3.3 apache-libcloud==0.20.1 appdirs==1.4.0 -azure==2.0.0rc2 +azure==2.0.0rc5 Babel==2.2.0 boto==2.40.0 cliff==1.15.0 @@ -69,7 +69,7 @@ rackspace-auth-openstack==1.3 rackspace-novaclient==1.5 rax-default-network-flags-python-novaclient-ext==0.3.2 rax-scheduled-images-python-novaclient-ext==0.3.1 -requests==2.5.1 +requests==2.11.0 requestsexceptions==1.1.1 shade==1.4.0 simplejson==3.8.1 From f30c8c0bb8006e156965eeaa468ec6183862cc0b Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 29 Sep 2016 10:37:18 -0400 Subject: [PATCH 14/45] Update postgres yum/apt repo locations *Thanks postgres team (cherry picked from commit b398fcc5b97ed478273e3d124aaaaa3af2879965) --- 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 4e22788585..3acd687372 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y software-properties-common python-softw 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 curl -sL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - -RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" | tee /etc/apt/sources.list.d/postgres-9.4.list +RUN echo "deb http://download.postgresql.org/pub/repos/apt/dists/ trusty-pgdg main" | tee /etc/apt/sources.list.d/postgres-9.4.list RUN apt-get update && 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 libgss-dev libkrb5-dev && apt-get autoremove --purge -y && rm -rf /var/lib/apt/lists/* RUN pip install flake8 pytest pytest-pythonpath pytest-django pytest-cov pytest-mock dateutils django-debug-toolbar==1.4 pyflakes==1.0.0 virtualenv RUN /usr/bin/ssh-keygen -q -t rsa -N "" -f /root/.ssh/id_rsa From 4d22af0d3cc4d76154e05fb3a44fa7b348663c0d Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Thu, 29 Sep 2016 13:16:58 -0400 Subject: [PATCH 15/45] Fix up ubuntu apt repo paths --- 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 3acd687372..8ec83450b5 100644 --- a/tools/docker-compose/Dockerfile +++ b/tools/docker-compose/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y software-properties-common python-softw 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 curl -sL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - -RUN echo "deb http://download.postgresql.org/pub/repos/apt/dists/ trusty-pgdg main" | tee /etc/apt/sources.list.d/postgres-9.4.list +RUN echo "deb http://download.postgresql.org/pub/repos/apt/ trusty-pgdg main" | tee /etc/apt/sources.list.d/postgres-9.4.list RUN apt-get update && 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 libgss-dev libkrb5-dev && apt-get autoremove --purge -y && rm -rf /var/lib/apt/lists/* RUN pip install flake8 pytest pytest-pythonpath pytest-django pytest-cov pytest-mock dateutils django-debug-toolbar==1.4 pyflakes==1.0.0 virtualenv RUN /usr/bin/ssh-keygen -q -t rsa -N "" -f /root/.ssh/id_rsa From f82b10f018cfc2a1f168a74d1ef5f9b05acbd879 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 29 Sep 2016 13:35:47 -0400 Subject: [PATCH 16/45] Job relaunch permissions made consistent with JT prompts --- awx/main/access.py | 30 ++++++++-- .../tests/functional/test_rbac_job_start.py | 56 +++++++++++++++++-- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index 72636e1776..f3b8ef22e1 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1094,17 +1094,35 @@ class JobAccess(BaseAccess): if self.user.is_superuser: return True - # If a user can launch the job template then they can relaunch a job from that - # job template + inventory_access = obj.inventory and self.user in obj.inventory.use_role + credential_access = obj.credential and self.user in obj.credential.use_role + + # Check if JT execute access (and related prompts) is sufficient if obj.job_template is not None: - return self.user in obj.job_template.execute_role + prompts_access = True + job_fields = {} + for fd in obj.job_template._ask_for_vars_dict(): + job_fields[fd] = getattr(obj, fd) + accepted_fields, ignored_fields = obj.job_template._accept_or_ignore_job_kwargs(**job_fields) + for fd in ignored_fields: + if fd == 'extra_vars': + if ignored_fields[fd]: + prompts_access = False + elif job_fields[fd] != getattr(obj.job_template, fd): + # Job has field that is not promptable + prompts_access = False + if obj.credential != obj.job_template.credential and not credential_access: + prompts_access = False + if obj.inventory != obj.job_template.inventory and not inventory_access: + prompts_access = False + if prompts_access and self.user in obj.job_template.execute_role: + return True - inventory_access = self.user in obj.inventory.use_role - credential_access = self.user in obj.credential.use_role - org_access = self.user in obj.inventory.organization.admin_role + org_access = obj.inventory and self.user in obj.inventory.organization.admin_role project_access = obj.project is None or self.user in obj.project.admin_role + # job can be relaunched if user could make an equivalent JT return inventory_access and credential_access and (org_access or project_access) def can_cancel(self, obj): diff --git a/awx/main/tests/functional/test_rbac_job_start.py b/awx/main/tests/functional/test_rbac_job_start.py index 18060126e1..00358d1c38 100644 --- a/awx/main/tests/functional/test_rbac_job_start.py +++ b/awx/main/tests/functional/test_rbac_job_start.py @@ -2,11 +2,7 @@ import pytest from awx.main.models.inventory import Inventory from awx.main.models.credential import Credential -from awx.main.models.jobs import JobTemplate - -@pytest.fixture -def machine_credential(): - return Credential.objects.create(name='machine-cred', kind='ssh', username='test_user', password='pas4word') +from awx.main.models.jobs import JobTemplate, Job @pytest.mark.django_db @pytest.mark.job_permissions @@ -45,3 +41,53 @@ def test_inventory_use_access(inventory, user): inventory.use_role.members.add(common_user) assert common_user.can_access(Inventory, 'use', inventory) + +@pytest.mark.django_db +class TestJobRelaunchAccess: + @pytest.fixture + def jt_no_prompts(self, machine_credential, inventory): + return JobTemplate.objects.create(name='test-job_template', credential=machine_credential, inventory=inventory) + + @pytest.fixture + def jt_with_prompts(self, jt_no_prompts): + jt_no_prompts.update( + ask_tags_on_launch=True, ask_variables_on_launch=True, ask_skip_tags_on_launch=True, + ask_limit_on_launch=True, ask_job_type_on_launch=True, ask_inventory_on_launch=True, + ask_credential_on_launch=True) + return jt_no_prompts + + @pytest.fixture + def job_no_prompts(self, jt_no_prompts): + return jt_no_prompts.create_unified_job() + + @pytest.fixture + def job_with_prompts(self, jt_with_prompts, organization): + new_cred = Credential.objects.create(name='new-cred', kind='ssh', username='test_user', password='pas4word') + new_inv = Inventory.objects.create(name='new-inv', organization=organization) + return jt_with_prompts.create_unified_job(credential=new_cred, inventory=new_inv) + + def test_no_relaunch_without_prompted_fields_access(self, job_with_prompts, rando): + "Has JT execute_role but no use_role on inventory & credential - deny relaunch" + job_with_prompts.job_template.execute_role.members.add(rando) + assert not rando.can_access(Job, 'start', job_with_prompts) + + def test_can_relaunch_with_prompted_fields_access(self, job_with_prompts, rando): + "Has use_role on the prompted inventory & credential - allow relaunch" + job_with_prompts.job_template.execute_role.members.add(rando) + job_with_prompts.credential.use_role.members.add(rando) + job_with_prompts.inventory.use_role.members.add(rando) + assert rando.can_access(Job, 'start', job_with_prompts) + + def test_no_relaunch_after_limit_change(self, job_no_prompts, rando): + "State of the job contradicts the JT state - deny relaunch" + job_no_prompts.job_template.execute_role.members.add(rando) + job_no_prompts.limit = 'webservers' + job_no_prompts.save() + assert not rando.can_access(Job, 'start', job_no_prompts) + + def test_can_relaunch_if_limit_was_prompt(self, job_with_prompts, rando): + "Job state differs from JT, but only on prompted fields - allow relaunch" + job_with_prompts.job_template.execute_role.members.add(rando) + job_with_prompts.limit = 'webservers' + job_with_prompts.save() + assert not rando.can_access(Job, 'start', job_with_prompts) From 115dfac471169269689546d2f552faf88296082f Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 29 Sep 2016 16:42:41 -0400 Subject: [PATCH 17/45] do not introspect extra_vars, speed up tests, add base case test --- awx/main/access.py | 5 +--- .../tests/functional/test_rbac_job_start.py | 25 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/awx/main/access.py b/awx/main/access.py index f3b8ef22e1..d122d48da8 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1105,10 +1105,7 @@ class JobAccess(BaseAccess): job_fields[fd] = getattr(obj, fd) accepted_fields, ignored_fields = obj.job_template._accept_or_ignore_job_kwargs(**job_fields) for fd in ignored_fields: - if fd == 'extra_vars': - if ignored_fields[fd]: - prompts_access = False - elif job_fields[fd] != getattr(obj.job_template, fd): + if fd != 'extra_vars' and job_fields[fd] != getattr(obj.job_template, fd): # Job has field that is not promptable prompts_access = False if obj.credential != obj.job_template.credential and not credential_access: diff --git a/awx/main/tests/functional/test_rbac_job_start.py b/awx/main/tests/functional/test_rbac_job_start.py index 00358d1c38..c934973cf4 100644 --- a/awx/main/tests/functional/test_rbac_job_start.py +++ b/awx/main/tests/functional/test_rbac_job_start.py @@ -45,26 +45,25 @@ def test_inventory_use_access(inventory, user): @pytest.mark.django_db class TestJobRelaunchAccess: @pytest.fixture - def jt_no_prompts(self, machine_credential, inventory): - return JobTemplate.objects.create(name='test-job_template', credential=machine_credential, inventory=inventory) + def job_no_prompts(self, machine_credential, inventory): + jt = JobTemplate.objects.create(name='test-job_template', credential=machine_credential, inventory=inventory) + return jt.create_unified_job() @pytest.fixture - def jt_with_prompts(self, jt_no_prompts): - jt_no_prompts.update( + def job_with_prompts(self, machine_credential, inventory, organization): + jt = JobTemplate.objects.create( + name='test-job-template-prompts', credential=machine_credential, inventory=inventory, ask_tags_on_launch=True, ask_variables_on_launch=True, ask_skip_tags_on_launch=True, ask_limit_on_launch=True, ask_job_type_on_launch=True, ask_inventory_on_launch=True, ask_credential_on_launch=True) - return jt_no_prompts - - @pytest.fixture - def job_no_prompts(self, jt_no_prompts): - return jt_no_prompts.create_unified_job() - - @pytest.fixture - def job_with_prompts(self, jt_with_prompts, organization): new_cred = Credential.objects.create(name='new-cred', kind='ssh', username='test_user', password='pas4word') new_inv = Inventory.objects.create(name='new-inv', organization=organization) - return jt_with_prompts.create_unified_job(credential=new_cred, inventory=new_inv) + return jt.create_unified_job(credential=new_cred, inventory=new_inv) + + def test_normal_relaunch_via_job_template(self, job_no_prompts, rando): + "Has JT execute_role, job unchanged relative to JT" + job_no_prompts.job_template.execute_role.members.add(rando) + assert rando.can_access(Job, 'start', job_no_prompts) def test_no_relaunch_without_prompted_fields_access(self, job_with_prompts, rando): "Has JT execute_role but no use_role on inventory & credential - deny relaunch" From e36a4ed47e515bca959e28256d589e0bf4f77edc Mon Sep 17 00:00:00 2001 From: mabashian Date: Fri, 30 Sep 2016 10:14:08 -0400 Subject: [PATCH 18/45] Updated user's teams list empty text to read: "This user is not a member of any teams" --- awx/ui/client/src/forms/Users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/ui/client/src/forms/Users.js b/awx/ui/client/src/forms/Users.js index ddcda6e296..2ce2f69712 100644 --- a/awx/ui/client/src/forms/Users.js +++ b/awx/ui/client/src/forms/Users.js @@ -141,7 +141,7 @@ export default open: false, index: false, actions: {}, - + emptyListText: 'This user is not a member of any teams', fields: { name: { key: true, From 4ca8bc1eda6c5bc8059d8e29048c9a0f702b7f90 Mon Sep 17 00:00:00 2001 From: Graham Mainwaring Date: Fri, 30 Sep 2016 13:28:14 -0400 Subject: [PATCH 19/45] Merge pull request #3630 from ghjm/restore_apache_fix Use correct Apache service name when restoring on EL6 From b4c5e31a1dadf4ce8de4d30f2cc32ca7c6b07bea Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 3 Oct 2016 09:56:57 -0400 Subject: [PATCH 20/45] Stay on the projects/inv manage pages when syncs are launched from those pages. Otherwise, direct the user to the stdout pages for those individual updates. --- .../job-submission-factories/launchjob.factory.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js index 3968eaf484..6c3d861afa 100644 --- a/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js +++ b/awx/ui/client/src/job-submission/job-submission-factories/launchjob.factory.js @@ -130,12 +130,20 @@ export default goToJobDetails('managementJobStdout'); } else if(_.has(data, 'project_update')) { - if($state.current.name !== 'projects') { + // If we are on the projects list or any child state of that list + // then we want to stay on that page. Otherwise go to the stdout + // view. + if(!$state.includes('projects')) { goToJobDetails('scmUpdateStdout'); } } else if(_.has(data, 'inventory_update')) { - goToJobDetails('inventorySyncStdout'); + // If we are on the inventory manage page or any child state of that + // page then we want to stay on that page. Otherwise go to the stdout + // view. + if(!$state.includes('inventoryManage')) { + goToJobDetails('inventorySyncStdout'); + } } } if(scope.clearDialog) { From 16129b6e64b4a26068669979f42f7c3b1bff37af Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Mon, 3 Oct 2016 09:36:33 -0400 Subject: [PATCH 21/45] Add pycparser to SRC_ONLY_PKGS Fixes stuff due to https://github.com/pyca/cryptography/issues/3187 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2ff93989dd..68f6faffa1 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ CLIENT_TEST_DIR ?= build_test # Python packages to install only from source (not from binary wheels) # Comma separated list -SRC_ONLY_PKGS ?= cffi +SRC_ONLY_PKGS ?= cffi,pycparser # Determine appropriate shasum command UNAME_S := $(shell uname -s) From 6097bc2f176b4305aad81810b61c20b015a0489e Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 3 Oct 2016 11:34:27 -0400 Subject: [PATCH 22/45] Fixed bug where editing a host was showing oddly formatted YAML in the variables section --- .../src/inventories/manage/hosts/hosts-edit.controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js b/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js index 9098e53333..bc96df0eaf 100644 --- a/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js +++ b/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js @@ -5,8 +5,8 @@ *************************************************/ export default - ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'host', - function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host){ + ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'host', 'ParseVariableString', + function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host, ParseVariableString){ var generator = GenerateForm, form = HostForm; $scope.parseType = 'yaml'; @@ -31,12 +31,12 @@ var init = function(){ $scope.host = host; generator.inject(form, {mode: 'edit', related: false, id: 'Inventory-hostManage--panel', scope: $scope}); - $scope.variables = host.variables === '' ? '---' : host.variables; + $scope.variables = host.variables === '' ? '---' : ParseVariableString(host.variables); $scope.name = host.name; $scope.description = host.description; ParseTypeChange({ scope: $scope, - field_id: 'host_variables', + field_id: 'host_variables' }); }; init(); From 9fc30643cae55598c71e8c4aaace58fb19b09f45 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 3 Oct 2016 16:15:06 -0400 Subject: [PATCH 23/45] Prevent filtering on password fields. --- awx/api/filters.py | 7 +++++-- awx/main/tests/unit/api/test_filters.py | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/awx/api/filters.py b/awx/api/filters.py index 55155224c4..08a26735d2 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -14,7 +14,7 @@ from django.contrib.contenttypes.models import ContentType from django.utils.encoding import force_text # Django REST Framework -from rest_framework.exceptions import ParseError +from rest_framework.exceptions import ParseError, PermissionDenied from rest_framework.filters import BaseFilterBackend # Ansible Tower @@ -97,7 +97,10 @@ class FieldLookupBackend(BaseFilterBackend): new_parts.append(name) - if name == 'pk': + + if name in getattr(model, 'PASSWORD_FIELDS', ()): + raise PermissionDenied('Filtering on password fields is not allowed.') + elif name == 'pk': field = model._meta.pk else: field = model._meta.get_field_by_name(name)[0] diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py index 8f045db877..55ef257567 100644 --- a/awx/main/tests/unit/api/test_filters.py +++ b/awx/main/tests/unit/api/test_filters.py @@ -1,7 +1,8 @@ import pytest +from rest_framework.exceptions import PermissionDenied from awx.api.filters import FieldLookupBackend -from awx.main.models import JobTemplate +from awx.main.models import Credential, JobTemplate @pytest.mark.parametrize(u"empty_value", [u'', '']) def test_empty_in(empty_value): @@ -15,3 +16,21 @@ def test_valid_in(valid_value): field_lookup = FieldLookupBackend() value, new_lookup = field_lookup.value_to_python(JobTemplate, 'project__in', valid_value) assert 'foo' in value + +@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in']) +@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS) +def test_filter_on_password_field(password_field, lookup_suffix): + field_lookup = FieldLookupBackend() + lookup = '__'.join(filter(None, [password_field, lookup_suffix])) + with pytest.raises(PermissionDenied) as excinfo: + field, new_lookup = field_lookup.get_field_from_lookup(Credential, lookup) + assert 'not allowed' in str(excinfo.value) + +@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in']) +@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS) +def test_filter_on_related_password_field(password_field, lookup_suffix): + field_lookup = FieldLookupBackend() + lookup = '__'.join(filter(None, ['credential', password_field, lookup_suffix])) + with pytest.raises(PermissionDenied) as excinfo: + field, new_lookup = field_lookup.get_field_from_lookup(JobTemplate, lookup) + assert 'not allowed' in str(excinfo.value) From dc9344c82240205d8ce9447e42c4948d1ab0ee44 Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Thu, 6 Oct 2016 11:53:58 -0400 Subject: [PATCH 24/45] Host event modal - wrap long strings. --- awx/ui/client/src/job-detail/host-event/host-event.block.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/awx/ui/client/src/job-detail/host-event/host-event.block.less b/awx/ui/client/src/job-detail/host-event/host-event.block.less index 6edfc450ec..b22d52d36b 100644 --- a/awx/ui/client/src/job-detail/host-event/host-event.block.less +++ b/awx/ui/client/src/job-detail/host-event/host-event.block.less @@ -125,6 +125,8 @@ .OnePlusTwo-left--detailsRow; } .HostEvent-field--content{ + word-wrap: break-word; + max-width: 13em; flex: 0 1 13em; } .HostEvent-details--left, .HostEvent-details--right{ @@ -138,6 +140,7 @@ flex: 0 1 25em; } .HostEvent-field--content{ + max-width: 15em; flex: 0 1 15em; align-self: flex-end; } From f35fcf86e73739b85a32a1ba18e68ac2b3036c98 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Thu, 6 Oct 2016 16:13:38 -0400 Subject: [PATCH 25/45] fixed search pagination url issue --- awx/ui/client/src/helpers/refresh.js | 22 ++++++++++++++++++- .../client/src/search/tagSearch.controller.js | 3 ++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/helpers/refresh.js b/awx/ui/client/src/helpers/refresh.js index 5755cd934e..57c806f64a 100644 --- a/awx/ui/client/src/helpers/refresh.js +++ b/awx/ui/client/src/helpers/refresh.js @@ -71,7 +71,27 @@ export default // if you're editing an object, make sure you're on the right // page to display the element you are editing - if (scope.addedItem) { + if (params.fromSearch) { + var url = params.url; + // for a search, we want to make sure to get the first page of + // results + if (url.indexOf("page=") > -1) { + // if the url includes a page, remove that part + var urlArr = url.split("page="); + var afterPageUrlArr = urlArr[1].split("&"); + + if (afterPageUrlArr.length > 1) { + // if there's stuff after the page part, + // put that back in + afterPageUrlArr.shift(); + url = urlArr[0] + + afterPageUrlArr.join("&"); + } else { + url = urlArr[0]; + } + } + getPage(url); + } else if (scope.addedItem) { id = scope.addedItem + ""; delete scope.addedItem; $rootScope.rowBeingEdited = id; diff --git a/awx/ui/client/src/search/tagSearch.controller.js b/awx/ui/client/src/search/tagSearch.controller.js index e50a25670e..0209329110 100644 --- a/awx/ui/client/src/search/tagSearch.controller.js +++ b/awx/ui/client/src/search/tagSearch.controller.js @@ -72,7 +72,8 @@ export default ['$scope', 'Refresh', 'tagSearchService', '$stateParams', scope: listScope, set: set, iterator: iterator, - url: url + url: url, + fromSearch: true }); listScope.$on('PostRefresh', function() { From 58817367697bb6cdd595affefaf217ab6b4c2e5b Mon Sep 17 00:00:00 2001 From: James Laska Date: Wed, 5 Oct 2016 10:48:47 -0400 Subject: [PATCH 26/45] Properly detect enabled foreman inventory Related #3632 --- awx/settings/defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index e2995d0dbd..603a979cda 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -679,7 +679,7 @@ OPENSTACK_INSTANCE_ID_VAR = 'openstack.id' # ----- Foreman ----- # --------------------- SATELLITE6_ENABLED_VAR = 'foreman.enabled' -SATELLITE6_ENABLED_VALUE = 'true' +SATELLITE6_ENABLED_VALUE = True SATELLITE6_GROUP_FILTER = r'^.+$' SATELLITE6_HOST_FILTER = r'^.+$' SATELLITE6_EXCLUDE_EMPTY_GROUPS = True From 77a56b3dfd1c766764660172498cdbc7ddd0d7c3 Mon Sep 17 00:00:00 2001 From: James Laska Date: Fri, 7 Oct 2016 09:50:18 -0400 Subject: [PATCH 27/45] Properly recognize foreman.enabled The ENABLED_VALUE must always be a string. Thanks to @cchurch for the suggestion. Resolves #3632 --- awx/settings/defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 603a979cda..771155a523 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -679,7 +679,7 @@ OPENSTACK_INSTANCE_ID_VAR = 'openstack.id' # ----- Foreman ----- # --------------------- SATELLITE6_ENABLED_VAR = 'foreman.enabled' -SATELLITE6_ENABLED_VALUE = True +SATELLITE6_ENABLED_VALUE = 'True' SATELLITE6_GROUP_FILTER = r'^.+$' SATELLITE6_HOST_FILTER = r'^.+$' SATELLITE6_EXCLUDE_EMPTY_GROUPS = True From dbe6f5200f8c4ff307d78c34e4701e673defb234 Mon Sep 17 00:00:00 2001 From: Jared Tabor Date: Fri, 7 Oct 2016 14:58:31 -0700 Subject: [PATCH 28/45] YAML parsing groups and hosts extra variables YAML parsing got lost in translation in the 3.0 scuffle --- .../manage/groups/groups-add.controller.js | 11 +++++------ .../manage/groups/groups-edit.controller.js | 11 +++++++---- .../inventories/manage/hosts/hosts-add.controller.js | 9 +++++---- .../inventories/manage/hosts/hosts-edit.controller.js | 9 +++++---- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js index d1272f508b..627e253de0 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-add.controller.js @@ -5,10 +5,8 @@ *************************************************/ export default - ['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'LookUpInit', - 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ParseTypeChange, GenerateForm, inventoryData, LookUpInit, - GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions){ + ['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'LookUpInit', 'GroupManageService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions','ToJSON', + function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ParseTypeChange, GenerateForm, inventoryData, LookUpInit, GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, ToJSON){ var generator = GenerateForm, form = GroupForm(); @@ -20,10 +18,11 @@ $state.go('^'); }; $scope.formSave = function(){ - var params, source; + var params, source, + json_data = ToJSON($scope.parseType, $scope.variables, true); // group fields var group = { - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, + variables: json_data, name: $scope.name, description: $scope.description, inventory: inventoryData.id diff --git a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js index 941789f39d..2b7deefad4 100644 --- a/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js +++ b/awx/ui/client/src/inventories/manage/groups/groups-edit.controller.js @@ -7,10 +7,10 @@ export default ['$state', '$stateParams', '$scope', 'GroupForm', 'CredentialList', 'inventoryScriptsListObject', 'ToggleNotification', 'ParseVariableString', 'ParseTypeChange', 'GenerateForm', 'LookUpInit', 'RelatedSearchInit', 'RelatedPaginateInit', 'NotificationsListInit', - 'GroupManageService','GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData', 'inventorySourceData', + 'GroupManageService','GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'groupData', 'inventorySourceData', 'ToJSON', function($state, $stateParams, $scope, GroupForm, CredentialList, InventoryScriptsList, ToggleNotification, ParseVariableString, ParseTypeChange, GenerateForm, LookUpInit, RelatedSearchInit, RelatedPaginateInit, NotificationsListInit, - GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData, inventorySourceData){ + GroupManageService, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, groupData, inventorySourceData, ToJSON){ var generator = GenerateForm, form = GroupForm(); @@ -22,15 +22,18 @@ $state.go('^'); }; $scope.formSave = function(){ - var params, source; + var params, source, + json_data = ToJSON($scope.parseType, $scope.variables, true); + // group fields var group = { - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, + variables: json_data, name: $scope.name, description: $scope.description, inventory: $scope.inventory, id: groupData.id }; + if ($scope.source){ // inventory_source fields params = { diff --git a/awx/ui/client/src/inventories/manage/hosts/hosts-add.controller.js b/awx/ui/client/src/inventories/manage/hosts/hosts-add.controller.js index d0dd6c3c86..1958a24353 100644 --- a/awx/ui/client/src/inventories/manage/hosts/hosts-add.controller.js +++ b/awx/ui/client/src/inventories/manage/hosts/hosts-add.controller.js @@ -5,8 +5,8 @@ *************************************************/ export default - ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', - function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService){ + ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'ToJSON', + function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, ToJSON){ var generator = GenerateForm, form = HostForm; $scope.parseType = 'yaml'; @@ -17,8 +17,9 @@ $scope.host.enabled = !$scope.host.enabled; }; $scope.formSave = function(){ - var params = { - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, + var json_data = ToJSON($scope.parseType, $scope.variables, true), + params = { + variables: json_data,// $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, name: $scope.name, description: $scope.description, enabled: $scope.host.enabled, diff --git a/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js b/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js index bc96df0eaf..7480349505 100644 --- a/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js +++ b/awx/ui/client/src/inventories/manage/hosts/hosts-edit.controller.js @@ -5,8 +5,8 @@ *************************************************/ export default - ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'host', 'ParseVariableString', - function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host, ParseVariableString){ + ['$state', '$stateParams', '$scope', 'HostForm', 'ParseTypeChange', 'GenerateForm', 'HostManageService', 'host', 'ParseVariableString', 'ToJSON', + function($state, $stateParams, $scope, HostForm, ParseTypeChange, GenerateForm, HostManageService, host, ParseVariableString, ToJSON){ var generator = GenerateForm, form = HostForm; $scope.parseType = 'yaml'; @@ -17,9 +17,10 @@ $scope.host.enabled = !$scope.host.enabled; }; $scope.formSave = function(){ - var host = { + var json_data = ToJSON($scope.parseType, $scope.variables, true), + host = { id: $scope.host.id, - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, + variables: json_data, name: $scope.name, description: $scope.description, enabled: $scope.host.enabled From 236d4df4605933df360b6d584d1761c1eff37b11 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 10 Oct 2016 12:31:08 -0400 Subject: [PATCH 29/45] fixed manual project select2 population --- awx/ui/client/src/controllers/Projects.js | 16 +++++++++++----- awx/ui/client/src/helpers/ProjectPath.js | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/awx/ui/client/src/controllers/Projects.js b/awx/ui/client/src/controllers/Projects.js index 60692d581e..fce2ba8bde 100644 --- a/awx/ui/client/src/controllers/Projects.js +++ b/awx/ui/client/src/controllers/Projects.js @@ -596,6 +596,16 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, }); }); + if ($scope.pathsReadyRemove) { + $scope.pathsReadyRemove(); + } + $scope.pathsReadyRemove = $scope.$on('pathsReady', function () { + CreateSelect2({ + element: '#local-path-select', + multiple: false + }); + }); + // After the project is loaded, retrieve each related set if ($scope.projectLoadedRemove) { $scope.projectLoadedRemove(); @@ -623,6 +633,7 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, $scope.project_local_paths = opts; $scope.local_path = $scope.project_local_paths[0]; $scope.base_dir = 'You do not have access to view this property'; + $scope.$emit('pathsReady'); } LookUpInit({ @@ -718,11 +729,6 @@ export function ProjectsEdit($scope, $rootScope, $compile, $location, $log, multiple: false }); - CreateSelect2({ - element: '#local-path-select', - multiple: false - }); - $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; // Initialize related search functions. Doing it here to make sure relatedSets object is populated. diff --git a/awx/ui/client/src/helpers/ProjectPath.js b/awx/ui/client/src/helpers/ProjectPath.js index 91761fce0e..5fdc619aea 100644 --- a/awx/ui/client/src/helpers/ProjectPath.js +++ b/awx/ui/client/src/helpers/ProjectPath.js @@ -78,6 +78,7 @@ export default // trigger display of alert block when scm_type == manual scope.showMissingPlaybooksAlert = true; } + scope.$emit('pathsReady'); }) .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', From 57c599df41384ab387a37b634ea9e9f8ff97fbe9 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 12 Oct 2016 16:07:25 -0400 Subject: [PATCH 30/45] Exclude test directory from setup bundle tarballs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also using the rsync strategy in the tar-build target. This doesn’t leave behind the empty setup/test directory. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 68f6faffa1..bc628758a1 100644 --- a/Makefile +++ b/Makefile @@ -663,10 +663,10 @@ release_build: # Build setup tarball tar-build/$(SETUP_TAR_FILE): @mkdir -p tar-build - @cp -a setup tar-build/$(SETUP_TAR_NAME) + @rsync -az --exclude /test setup/ tar-build/$(SETUP_TAR_NAME) @rsync -az docs/licenses tar-build/$(SETUP_TAR_NAME)/ @cd tar-build/$(SETUP_TAR_NAME) && sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%RELEASE%#$(RELEASE)#;' group_vars/all.in > group_vars/all - @cd tar-build && tar -czf $(SETUP_TAR_FILE) --exclude "*/all.in" --exclude "**/test/*" $(SETUP_TAR_NAME)/ + @cd tar-build && tar -czf $(SETUP_TAR_FILE) --exclude "*/all.in" $(SETUP_TAR_NAME)/ @ln -sf $(SETUP_TAR_FILE) tar-build/$(SETUP_TAR_LINK) tar-build/$(SETUP_TAR_CHECKSUM): @@ -703,7 +703,7 @@ setup-bundle-build: # TODO - Somehow share implementation with setup_tarball setup-bundle-build/$(OFFLINE_TAR_FILE): - cp -a setup setup-bundle-build/$(OFFLINE_TAR_NAME) + rsync -az --exclude /test setup/ setup-bundle-build/$(OFFLINE_TAR_NAME) rsync -az docs/licenses setup-bundle-build/$(OFFLINE_TAR_NAME)/ cd setup-bundle-build/$(OFFLINE_TAR_NAME) && sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%RELEASE%#$(RELEASE)#;' group_vars/all.in > group_vars/all $(PYTHON) $(DEPS_SCRIPT) -d $(DIST) -r $(DIST_MAJOR) -u $(AW_REPO_URL) -s setup-bundle-build/$(OFFLINE_TAR_NAME) -v -v -v From 280d265d4ede175770f2815c22d831c9366dd910 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Thu, 13 Oct 2016 12:29:02 -0400 Subject: [PATCH 31/45] filter internal User.admin_roles from the /roles API list view --- awx/main/models/rbac.py | 6 +++++- awx/main/tests/functional/test_rbac_api.py | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 5e040b85a1..be724069d7 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -389,7 +389,11 @@ class Role(models.Model): ) ''' % sql_params] ) - return qs + + # Do not show roles that are of content_type(User) + # these roles are for internal only user. + user_type = ContentType.objects.get_for_model(User) + return qs.exclude(content_type__pk=user_type.id) @staticmethod @check_singleton diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index 54dcc8deb5..0076c59c9e 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -51,7 +51,6 @@ def test_get_roles_list_user(organization, inventory, team, get, user): assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id in role_hash assert organization.admin_role.id in role_hash assert organization.member_role.id in role_hash - assert this_user.admin_role.id in role_hash assert custom_role.id in role_hash assert inventory.admin_role.id not in role_hash From d89c07a25b3ebd5e94c47c6b3dcf0aef2ec45237 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Mon, 17 Oct 2016 15:18:46 -0400 Subject: [PATCH 32/45] Remove support@ansible.com, clean up some old links. Cherry-picked from devel. --- setup.py | 6 +++--- tools/docker-compose/ansible_tower.egg-info/PKG-INFO | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index e6e25d4eec..ff268cd81d 100755 --- a/setup.py +++ b/setup.py @@ -79,13 +79,13 @@ setup( name='ansible-tower', version=__version__.split("-")[0], # FIXME: Should keep full version here? author='Ansible, Inc.', - author_email='support@ansible.com', + author_email='info@ansible.com', description='ansible-tower: API, UI and Task Engine for Ansible', - long_description='AWX provides a web-based user interface, REST API and ' + long_description='Ansible Tower provides a web-based user interface, REST API and ' 'task engine built on top of Ansible', license='Proprietary', keywords='ansible', - url='http://github.com/ansible/ansible-commander', + url='http://github.com/ansible/ansible-tower', packages=['awx'], include_package_data=True, zip_safe=False, diff --git a/tools/docker-compose/ansible_tower.egg-info/PKG-INFO b/tools/docker-compose/ansible_tower.egg-info/PKG-INFO index 61643c7c28..0d78373ace 100644 --- a/tools/docker-compose/ansible_tower.egg-info/PKG-INFO +++ b/tools/docker-compose/ansible_tower.egg-info/PKG-INFO @@ -2,11 +2,11 @@ Metadata-Version: 1.1 Name: ansible-tower Version: 3.0.0-0.devel Summary: ansible-tower: API, UI and Task Engine for Ansible -Home-page: http://github.com/ansible/ansible-commander +Home-page: http://github.com/ansible/ansible-tower Author: Ansible, Inc. -Author-email: support@ansible.com +Author-email: info@ansible.com License: Proprietary -Description: AWX provides a web-based user interface, REST API and task engine built on top of Ansible +Description: Ansible Tower provides a web-based user interface, REST API and task engine built on top of Ansible Keywords: ansible Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable From 6d07699b2954db9c6662586934f58c863e07653b Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Tue, 18 Oct 2016 18:02:30 -0400 Subject: [PATCH 33/45] bump shade version --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a6bda0f61c..5d8a8365db 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -115,7 +115,7 @@ redis==2.10.3 requests-oauthlib==0.5.0 requests==2.9.1 requestsexceptions==1.1.1 -shade==1.4.0 +shade==1.12.1 simplejson==3.8.1 six==1.9.0 slackclient==0.16 From b43945b0f27f1b1dca4454977e3bab0df9d3e41a Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 19 Oct 2016 12:33:53 -0400 Subject: [PATCH 34/45] Revert "bump shade version" This reverts commit d0a7313a4ccb1cfca3c7655b34b61c6cb6861909. --- requirements/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 5d8a8365db..a6bda0f61c 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -115,7 +115,7 @@ redis==2.10.3 requests-oauthlib==0.5.0 requests==2.9.1 requestsexceptions==1.1.1 -shade==1.12.1 +shade==1.4.0 simplejson==3.8.1 six==1.9.0 slackclient==0.16 From ec140fb7daa7ae90a3f99dd6876636e3436fd65d Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Wed, 19 Oct 2016 12:45:22 -0500 Subject: [PATCH 35/45] Hack copying of job_template.related.survey_spec into ui job copy flow, resolves #3737 --- .../copy/job-templates-copy.service.js | 89 +++++++++++-------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js index 53e86a78bb..a6f937cd58 100644 --- a/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js +++ b/awx/ui/client/src/job-templates/copy/job-templates-copy.service.js @@ -5,39 +5,56 @@ *************************************************/ export default - ['$rootScope', 'Rest', 'ProcessErrors', 'GetBasePath', 'moment', - function($rootScope, Rest, ProcessErrors, GetBasePath, moment){ - return { - get: function(id){ - var defaultUrl = GetBasePath('job_templates') + '?id=' + id; - Rest.setUrl(defaultUrl); - return Rest.get() - .success(function(res){ - return res; - }) - .error(function(res, status){ - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); - }, - set: function(data){ - var defaultUrl = GetBasePath('job_templates'); - Rest.setUrl(defaultUrl); - var name = this.buildName(data.results[0].name); - data.results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm - return Rest.post(data.results[0]) - .success(function(res){ - return res; - }) - .error(function(res, status){ - ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', - msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); - }); - }, - buildName: function(name){ - var result = name.split('@')[0]; - return result; - } - }; - } - ]; + ['$rootScope', 'Rest', 'ProcessErrors', 'GetBasePath', 'moment', + function($rootScope, Rest, ProcessErrors, GetBasePath, moment){ + return { + get: function(id){ + var defaultUrl = GetBasePath('job_templates') + '?id=' + id; + Rest.setUrl(defaultUrl); + return Rest.get() + .success(function(res){ + return res; + }) + .error(function(res, status){ + ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', + msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); + }); + }, + getSurvey: function(endpoint){ + Rest.setUrl(endpoint); + return Rest.get(); + }, + copySurvey: function(source, target){ + return this.getSurvey(source.related.survey_spec).success( (data) => { + Rest.setUrl(target.related.survey_spec); + return Rest.post(data); + }); + }, + set: function(data){ + var defaultUrl = GetBasePath('job_templates'); + var self = this; + Rest.setUrl(defaultUrl); + var name = this.buildName(data.results[0].name); + data.results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm + return Rest.post(data.results[0]) + .success(function(job_template_res){ + // also copy any associated survey_spec + if (data.results[0].related.survey_spec){ + return self.copySurvey(data.results[0], job_template_res).success( () => job_template_res); + } + else{ + return job_template_res; + } + }) + .error(function(res, status){ + ProcessErrors($rootScope, res, status, null, {hdr: 'Error!', + msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status}); + }); + }, + buildName: function(name){ + var result = name.split('@')[0]; + return result; + } + }; + } + ]; From 8a4d6b6f35b9fac703e4fc84d5e36c6682dc3371 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 19 Oct 2016 14:07:20 -0400 Subject: [PATCH 36/45] bump shade version --- requirements/requirements_ansible.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt index e62e3e5ead..067e57ae21 100644 --- a/requirements/requirements_ansible.txt +++ b/requirements/requirements_ansible.txt @@ -71,7 +71,7 @@ rax-default-network-flags-python-novaclient-ext==0.3.2 rax-scheduled-images-python-novaclient-ext==0.3.1 requests==2.11.0 requestsexceptions==1.1.1 -shade==1.4.0 +shade==1.12.1 simplejson==3.8.1 six==1.9.0 stevedore==1.10.0 From 49c90b8f2d86bc2ecad7328804dc13f3df94d256 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Wed, 19 Oct 2016 14:17:04 -0400 Subject: [PATCH 37/45] Revert "bump shade version" This reverts commit 0125d114fdf8be5f3a5fdab856c92d82a2988607. --- requirements/requirements_ansible.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt index 067e57ae21..e62e3e5ead 100644 --- a/requirements/requirements_ansible.txt +++ b/requirements/requirements_ansible.txt @@ -71,7 +71,7 @@ rax-default-network-flags-python-novaclient-ext==0.3.2 rax-scheduled-images-python-novaclient-ext==0.3.1 requests==2.11.0 requestsexceptions==1.1.1 -shade==1.12.1 +shade==1.4.0 simplejson==3.8.1 six==1.9.0 stevedore==1.10.0 From 5c69ed2e9f166e2d7b54bfe26813b1df3057b7a6 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Wed, 19 Oct 2016 16:15:09 -0400 Subject: [PATCH 38/45] More regions! --- awx/settings/defaults.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 771155a523..6711a5872b 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -532,6 +532,7 @@ INV_ENV_VARIABLE_BLACKLIST = ("HOME", "USER", "_", "TERM") # http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region EC2_REGION_NAMES = { 'us-east-1': 'US East (Northern Virginia)', + 'us-east-2': 'US East (Ohio)', 'us-west-2': 'US West (Oregon)', 'us-west-1': 'US West (Northern California)', 'eu-central-1': 'EU (Frankfurt)', @@ -540,6 +541,7 @@ EC2_REGION_NAMES = { 'ap-southeast-2': 'Asia Pacific (Sydney)', 'ap-northeast-1': 'Asia Pacific (Tokyo)', 'ap-northeast-2': 'Asia Pacific (Seoul)', + 'ap-south-1': 'Asia Pacific (Mumbai)', 'sa-east-1': 'South America (Sao Paulo)', 'us-gov-west-1': 'US West (GovCloud)', 'cn-north-1': 'China (Beijing)', From 94b20d4fc6d86144cfab0ee5d50e2a0ca324a240 Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Wed, 19 Oct 2016 16:16:05 -0400 Subject: [PATCH 39/45] Also bump boto for new regions, per ryansb. --- requirements/requirements.txt | 2 +- requirements/requirements_ansible.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a6bda0f61c..1cc458550f 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -5,7 +5,7 @@ appdirs==1.4.0 azure==2.0.0rc2 Babel==2.2.0 billiard==3.3.0.16 -boto==2.40.0 +boto==2.43.0 celery==3.1.10 cliff==1.15.0 cmd2==0.6.8 diff --git a/requirements/requirements_ansible.txt b/requirements/requirements_ansible.txt index e62e3e5ead..4d14cd380e 100644 --- a/requirements/requirements_ansible.txt +++ b/requirements/requirements_ansible.txt @@ -3,7 +3,7 @@ apache-libcloud==0.20.1 appdirs==1.4.0 azure==2.0.0rc5 Babel==2.2.0 -boto==2.40.0 +boto==2.43.0 cliff==1.15.0 cmd2==0.6.8 cryptography==1.3.2 From 40fdd3eba57ea4006d59d0e2ec6d91327d862b8d Mon Sep 17 00:00:00 2001 From: Bill Nottingham Date: Wed, 19 Oct 2016 16:49:49 -0400 Subject: [PATCH 40/45] Sync azure changes to Tower virtual environment --- requirements/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a6bda0f61c..91cc06ad94 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,7 +2,7 @@ git+https://github.com/chrismeyersfsu/ansiconv.git@tower_1.0.0#egg=ansiconv amqp==1.4.5 anyjson==0.3.3 appdirs==1.4.0 -azure==2.0.0rc2 +azure==2.0.0rc5 Babel==2.2.0 billiard==3.3.0.16 boto==2.40.0 @@ -113,7 +113,7 @@ rax-default-network-flags-python-novaclient-ext==0.3.2 rax-scheduled-images-python-novaclient-ext==0.3.1 redis==2.10.3 requests-oauthlib==0.5.0 -requests==2.9.1 +requests==2.11.0 requestsexceptions==1.1.1 shade==1.4.0 simplejson==3.8.1 From a3123011ad21f140cef07ea23315fe481aeb634c Mon Sep 17 00:00:00 2001 From: Leigh Johnson Date: Mon, 24 Oct 2016 13:49:37 -0400 Subject: [PATCH 41/45] Resolves 404 when assigning resources/users to organizations in card view. Sidesteps a bug in the Refresh() utility, where pagination calculations are not made against filtered results. Resolves #750 --- .../linkout/organizations-linkout.route.js | 30 ++++--------------- .../list/organizations-list.controller.js | 27 +++++++---------- .../list/organizations-list.route.js | 5 +--- 3 files changed, 18 insertions(+), 44 deletions(-) diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js index 13d51cc68f..6b028a8702 100644 --- a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js +++ b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js @@ -23,10 +23,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "USERS" }, resolve: { @@ -45,10 +42,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "TEAMS" }, resolve: { @@ -67,10 +61,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "INVENTORIES" }, resolve: { @@ -89,10 +80,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "PROJECTS" }, resolve: { @@ -111,10 +99,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "JOB TEMPLATES" }, resolve: { @@ -133,10 +118,7 @@ export default [ activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "organizations.edit"; - }, + parent: "organizations.edit", label: "ADMINS" }, resolve: { diff --git a/awx/ui/client/src/organizations/list/organizations-list.controller.js b/awx/ui/client/src/organizations/list/organizations-list.controller.js index 84510a60dc..878d23544f 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.controller.js +++ b/awx/ui/client/src/organizations/list/organizations-list.controller.js @@ -8,12 +8,12 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', '$log', '$compile', 'Rest', 'PaginateInit', 'SearchInit', 'OrganizationList', 'Alert', 'Prompt', 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait', - '$state', 'generateList', 'Refresh', '$filter', + '$state', 'generateList', '$filter', function($stateParams, $scope, $rootScope, $location, $log, $compile, Rest, PaginateInit, SearchInit, OrganizationList, Alert, Prompt, ClearScope, ProcessErrors, GetBasePath, Wait, - $state, generateList, Refresh, $filter) { + $state, generateList, $filter) { ClearScope(); @@ -70,19 +70,14 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', }; $scope.$on("ReloadOrgListView", function() { - var url = GetBasePath('organizations') + '?'; - if ($state.$current.self.name === "organizations" || - $state.$current.self.name === "organizations.add") { - $scope.activeCard = null; - } - if ($scope[list.iterator + 'SearchFilters']){ - url = url + _.reduce($scope[list.iterator+'SearchFilters'], (result, filter) => result + '&' + filter.url, ''); - } - Refresh({ - scope: $scope, - set: list.name, - iterator: list.iterator, - url: url + Rest.setUrl($scope.current_url); + Rest.get() + .success((data) => $scope.organizations = data.results) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { + hdr: 'Error!', + msg: 'Call to ' + defaultUrl + ' failed. DELETE returned status: ' + status + }); }); }); @@ -158,7 +153,7 @@ export default ['$stateParams', '$scope', '$rootScope', '$location', }); // grab the pagination elements, move, destroy list generator elements $('#organization-pagination').appendTo('#OrgCards'); - $('tag-search').appendTo('.OrgCards-search'); + $('#organizations tag-search').appendTo('.OrgCards-search'); $('#organizations-list').remove(); PaginateInit({ diff --git a/awx/ui/client/src/organizations/list/organizations-list.route.js b/awx/ui/client/src/organizations/list/organizations-list.route.js index c99604eecb..c965686317 100644 --- a/awx/ui/client/src/organizations/list/organizations-list.route.js +++ b/awx/ui/client/src/organizations/list/organizations-list.route.js @@ -17,10 +17,7 @@ export default { activityStreamTarget: 'organization' }, ncyBreadcrumb: { - parent: function($scope) { - $scope.$parent.$emit("ReloadOrgListView"); - return "setup"; - }, + parent: "setup", label: "ORGANIZATIONS" } }; From 9f15fc38738436d58d46b4b45a4c9236d91b8bee Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Tue, 25 Oct 2016 11:53:43 -0400 Subject: [PATCH 42/45] fix spelling of disassociated --- awx/api/templates/api/job_template_label_list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/api/templates/api/job_template_label_list.md b/awx/api/templates/api/job_template_label_list.md index 76c520eab5..9d503e9c65 100644 --- a/awx/api/templates/api/job_template_label_list.md +++ b/awx/api/templates/api/job_template_label_list.md @@ -2,7 +2,7 @@ Labels not associated with any other resources are deleted. A label can become disassociated with a resource as a result of 3 events. -1. A label is explicitly diassociated with a related job template +1. A label is explicitly disassociated with a related job template 2. A job is deleted with labels 3. A cleanup job deletes a job with labels From b11f5c301584a440fee109fbe17bc0447ded493e Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Tue, 25 Oct 2016 13:40:15 -0400 Subject: [PATCH 43/45] Revert "filter internal User.admin_roles from the /roles API list view" This reverts commit 2a55bfa5741e91c466570e7b4cf9b517d366c610. --- awx/main/models/rbac.py | 6 +----- awx/main/tests/functional/test_rbac_api.py | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index be724069d7..5e040b85a1 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -389,11 +389,7 @@ class Role(models.Model): ) ''' % sql_params] ) - - # Do not show roles that are of content_type(User) - # these roles are for internal only user. - user_type = ContentType.objects.get_for_model(User) - return qs.exclude(content_type__pk=user_type.id) + return qs @staticmethod @check_singleton diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py index 0076c59c9e..54dcc8deb5 100644 --- a/awx/main/tests/functional/test_rbac_api.py +++ b/awx/main/tests/functional/test_rbac_api.py @@ -51,6 +51,7 @@ def test_get_roles_list_user(organization, inventory, team, get, user): assert Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).id in role_hash assert organization.admin_role.id in role_hash assert organization.member_role.id in role_hash + assert this_user.admin_role.id in role_hash assert custom_role.id in role_hash assert inventory.admin_role.id not in role_hash From f129c4292968bff13cfb2169aa53ca604cf53af8 Mon Sep 17 00:00:00 2001 From: James Laska Date: Wed, 26 Oct 2016 15:57:46 -0400 Subject: [PATCH 44/45] Update rax.py inventory Resolves https://github.com/ansible/ansible/pull/18204 and continues support for disabling SSL verification. Fixes #3709 --- awx/plugins/inventory/rax.py | 50 ++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/awx/plugins/inventory/rax.py b/awx/plugins/inventory/rax.py index f29e0e8ba0..89ff425717 100755 --- a/awx/plugins/inventory/rax.py +++ b/awx/plugins/inventory/rax.py @@ -155,8 +155,6 @@ import ConfigParser from six import iteritems -from ansible.constants import get_config, mk_boolean - try: import json except ImportError: @@ -166,11 +164,12 @@ try: import pyrax from pyrax.utils import slugify except ImportError: - print('pyrax is required for this module') - sys.exit(1) + sys.exit('pyrax is required for this module') from time import time +from ansible.constants import get_config, mk_boolean + NON_CALLABLES = (basestring, bool, dict, int, list, type(None)) @@ -227,12 +226,21 @@ def _list_into_cache(regions): prefix = get_config(p, 'rax', 'meta_prefix', 'RAX_META_PREFIX', 'meta') - networks = get_config(p, 'rax', 'access_network', 'RAX_ACCESS_NETWORK', - 'public', islist=True) try: - ip_versions = map(int, get_config(p, 'rax', 'access_ip_version', - 'RAX_ACCESS_IP_VERSION', 4, - islist=True)) + # Ansible 2.3+ + networks = get_config(p, 'rax', 'access_network', + 'RAX_ACCESS_NETWORK', 'public', value_type='list') + except TypeError: + # Ansible 2.2.x and below + networks = get_config(p, 'rax', 'access_network', + 'RAX_ACCESS_NETWORK', 'public', islist=True) + try: + try: + ip_versions = map(int, get_config(p, 'rax', 'access_ip_version', + 'RAX_ACCESS_IP_VERSION', 4, value_type='list')) + except TypeError: + ip_versions = map(int, get_config(p, 'rax', 'access_ip_version', + 'RAX_ACCESS_IP_VERSION', 4, islist=True)) except: ip_versions = [4] else: @@ -406,10 +414,9 @@ def setup(): if os.path.isfile(default_creds_file): creds_file = default_creds_file elif not keyring_username: - sys.stderr.write('No value in environment variable %s and/or no ' - 'credentials file at %s\n' - % ('RAX_CREDS_FILE', default_creds_file)) - sys.exit(1) + sys.exit('No value in environment variable %s and/or no ' + 'credentials file at %s' + % ('RAX_CREDS_FILE', default_creds_file)) identity_type = pyrax.get_setting('identity_type') pyrax.set_setting('identity_type', identity_type or 'rackspace') @@ -422,23 +429,28 @@ def setup(): else: pyrax.set_credential_file(creds_file, region=region) except Exception as e: - sys.stderr.write("%s: %s\n" % (e, e.message)) - sys.exit(1) + sys.exit("%s: %s" % (e, e.message)) regions = [] if region: regions.append(region) else: - region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all', - islist=True) + try: + # Ansible 2.3+ + region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all', + value_type='list') + except TypeError: + # Ansible 2.2.x and below + region_list = get_config(p, 'rax', 'regions', 'RAX_REGION', 'all', + islist=True) + for region in region_list: region = region.strip().upper() if region == 'ALL': regions = pyrax.regions break elif region not in pyrax.regions: - sys.stderr.write('Unsupported region %s' % region) - sys.exit(1) + sys.exit('Unsupported region %s' % region) elif region not in regions: regions.append(region) From 7b68dc6d33969e162c2976dd0e118a04705b8335 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Fri, 28 Oct 2016 16:42:44 -0400 Subject: [PATCH 45/45] Merge pull request #3808 from jakemcdermott/noissue_fix_tower_welcome_msg interpret backslash escapes when displaying url in welcome message