mirror of
https://github.com/ansible/awx.git
synced 2026-03-17 17:07:33 -02:30
Merge branch 'release_3.2.0' into devel
This commit is contained in:
@@ -38,7 +38,7 @@ from rest_framework.utils.serializer_helpers import ReturnList
|
|||||||
from polymorphic.models import PolymorphicModel
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.constants import SCHEDULEABLE_PROVIDERS
|
from awx.main.constants import SCHEDULEABLE_PROVIDERS, ANSI_SGR_PATTERN
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.access import get_user_capabilities
|
from awx.main.access import get_user_capabilities
|
||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
@@ -343,6 +343,8 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
continue
|
continue
|
||||||
summary_fields[fk] = OrderedDict()
|
summary_fields[fk] = OrderedDict()
|
||||||
for field in related_fields:
|
for field in related_fields:
|
||||||
|
if field == 'credential_type_id' and fk == 'credential' and self.version < 2: # TODO: remove version check in 3.3
|
||||||
|
continue
|
||||||
|
|
||||||
fval = getattr(fkval, field, None)
|
fval = getattr(fkval, field, None)
|
||||||
|
|
||||||
@@ -2332,8 +2334,13 @@ class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
|||||||
if obj.vault_credential:
|
if obj.vault_credential:
|
||||||
res['vault_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.vault_credential.pk})
|
res['vault_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.vault_credential.pk})
|
||||||
if self.version > 1:
|
if self.version > 1:
|
||||||
view = 'api:%s_extra_credentials_list' % camelcase_to_underscore(obj.__class__.__name__)
|
if isinstance(obj, UnifiedJobTemplate):
|
||||||
res['extra_credentials'] = self.reverse(view, kwargs={'pk': obj.pk})
|
res['extra_credentials'] = self.reverse(
|
||||||
|
'api:job_template_extra_credentials_list',
|
||||||
|
kwargs={'pk': obj.pk}
|
||||||
|
)
|
||||||
|
elif isinstance(obj, UnifiedJob):
|
||||||
|
res['extra_credentials'] = self.reverse('api:job_extra_credentials_list', kwargs={'pk': obj.pk})
|
||||||
else:
|
else:
|
||||||
cloud_cred = obj.cloud_credential
|
cloud_cred = obj.cloud_credential
|
||||||
if cloud_cred:
|
if cloud_cred:
|
||||||
@@ -3120,6 +3127,14 @@ class JobEventSerializer(BaseSerializer):
|
|||||||
max_bytes = settings.EVENT_STDOUT_MAX_BYTES_DISPLAY
|
max_bytes = settings.EVENT_STDOUT_MAX_BYTES_DISPLAY
|
||||||
if max_bytes > 0 and 'stdout' in ret and len(ret['stdout']) >= max_bytes:
|
if max_bytes > 0 and 'stdout' in ret and len(ret['stdout']) >= max_bytes:
|
||||||
ret['stdout'] = ret['stdout'][:(max_bytes - 1)] + u'\u2026'
|
ret['stdout'] = ret['stdout'][:(max_bytes - 1)] + u'\u2026'
|
||||||
|
set_count = 0
|
||||||
|
reset_count = 0
|
||||||
|
for m in ANSI_SGR_PATTERN.finditer(ret['stdout']):
|
||||||
|
if m.string[m.start():m.end()] == u'\u001b[0m':
|
||||||
|
reset_count += 1
|
||||||
|
else:
|
||||||
|
set_count += 1
|
||||||
|
ret['stdout'] += u'\u001b[0m' * (set_count - reset_count)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@@ -3151,6 +3166,14 @@ class AdHocCommandEventSerializer(BaseSerializer):
|
|||||||
max_bytes = settings.EVENT_STDOUT_MAX_BYTES_DISPLAY
|
max_bytes = settings.EVENT_STDOUT_MAX_BYTES_DISPLAY
|
||||||
if max_bytes > 0 and 'stdout' in ret and len(ret['stdout']) >= max_bytes:
|
if max_bytes > 0 and 'stdout' in ret and len(ret['stdout']) >= max_bytes:
|
||||||
ret['stdout'] = ret['stdout'][:(max_bytes - 1)] + u'\u2026'
|
ret['stdout'] = ret['stdout'][:(max_bytes - 1)] + u'\u2026'
|
||||||
|
set_count = 0
|
||||||
|
reset_count = 0
|
||||||
|
for m in ANSI_SGR_PATTERN.finditer(ret['stdout']):
|
||||||
|
if m.string[m.start():m.end()] == u'\u001b[0m':
|
||||||
|
reset_count += 1
|
||||||
|
else:
|
||||||
|
set_count += 1
|
||||||
|
ret['stdout'] += u'\u001b[0m' * (set_count - reset_count)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2383,24 +2383,24 @@ class InventoryScriptView(RetrieveAPIView):
|
|||||||
host = get_object_or_404(obj.hosts, name=hostname, **hosts_q)
|
host = get_object_or_404(obj.hosts, name=hostname, **hosts_q)
|
||||||
data = host.variables_dict
|
data = host.variables_dict
|
||||||
else:
|
else:
|
||||||
data = OrderedDict()
|
data = dict()
|
||||||
if obj.variables_dict:
|
if obj.variables_dict:
|
||||||
all_group = data.setdefault('all', OrderedDict())
|
all_group = data.setdefault('all', dict())
|
||||||
all_group['vars'] = obj.variables_dict
|
all_group['vars'] = obj.variables_dict
|
||||||
if obj.kind == 'smart':
|
if obj.kind == 'smart':
|
||||||
if len(obj.hosts.all()) == 0:
|
if len(obj.hosts.all()) == 0:
|
||||||
return Response({})
|
return Response({})
|
||||||
else:
|
else:
|
||||||
all_group = data.setdefault('all', OrderedDict())
|
all_group = data.setdefault('all', dict())
|
||||||
smart_hosts_qs = obj.hosts.all().order_by('name')
|
smart_hosts_qs = obj.hosts.all()
|
||||||
smart_hosts = list(smart_hosts_qs.values_list('name', flat=True))
|
smart_hosts = list(smart_hosts_qs.values_list('name', flat=True))
|
||||||
all_group['hosts'] = smart_hosts
|
all_group['hosts'] = smart_hosts
|
||||||
else:
|
else:
|
||||||
# Add hosts without a group to the all group.
|
# Add hosts without a group to the all group.
|
||||||
groupless_hosts_qs = obj.hosts.filter(groups__isnull=True, **hosts_q).order_by('name')
|
groupless_hosts_qs = obj.hosts.filter(groups__isnull=True, **hosts_q)
|
||||||
groupless_hosts = list(groupless_hosts_qs.values_list('name', flat=True))
|
groupless_hosts = list(groupless_hosts_qs.values_list('name', flat=True))
|
||||||
if groupless_hosts:
|
if groupless_hosts:
|
||||||
all_group = data.setdefault('all', OrderedDict())
|
all_group = data.setdefault('all', dict())
|
||||||
all_group['hosts'] = groupless_hosts
|
all_group['hosts'] = groupless_hosts
|
||||||
|
|
||||||
# Build in-memory mapping of groups and their hosts.
|
# Build in-memory mapping of groups and their hosts.
|
||||||
@@ -2408,7 +2408,6 @@ class InventoryScriptView(RetrieveAPIView):
|
|||||||
if 'enabled' in hosts_q:
|
if 'enabled' in hosts_q:
|
||||||
group_hosts_kw['host__enabled'] = hosts_q['enabled']
|
group_hosts_kw['host__enabled'] = hosts_q['enabled']
|
||||||
group_hosts_qs = Group.hosts.through.objects.filter(**group_hosts_kw)
|
group_hosts_qs = Group.hosts.through.objects.filter(**group_hosts_kw)
|
||||||
group_hosts_qs = group_hosts_qs.order_by('host__name')
|
|
||||||
group_hosts_qs = group_hosts_qs.values_list('group_id', 'host_id', 'host__name')
|
group_hosts_qs = group_hosts_qs.values_list('group_id', 'host_id', 'host__name')
|
||||||
group_hosts_map = {}
|
group_hosts_map = {}
|
||||||
for group_id, host_id, host_name in group_hosts_qs:
|
for group_id, host_id, host_name in group_hosts_qs:
|
||||||
@@ -2420,7 +2419,6 @@ class InventoryScriptView(RetrieveAPIView):
|
|||||||
from_group__inventory_id=obj.id,
|
from_group__inventory_id=obj.id,
|
||||||
to_group__inventory_id=obj.id,
|
to_group__inventory_id=obj.id,
|
||||||
)
|
)
|
||||||
group_parents_qs = group_parents_qs.order_by('from_group__name')
|
|
||||||
group_parents_qs = group_parents_qs.values_list('from_group_id', 'from_group__name', 'to_group_id')
|
group_parents_qs = group_parents_qs.values_list('from_group_id', 'from_group__name', 'to_group_id')
|
||||||
group_children_map = {}
|
group_children_map = {}
|
||||||
for from_group_id, from_group_name, to_group_id in group_parents_qs:
|
for from_group_id, from_group_name, to_group_id in group_parents_qs:
|
||||||
@@ -2429,15 +2427,15 @@ class InventoryScriptView(RetrieveAPIView):
|
|||||||
|
|
||||||
# Now use in-memory maps to build up group info.
|
# Now use in-memory maps to build up group info.
|
||||||
for group in obj.groups.all():
|
for group in obj.groups.all():
|
||||||
group_info = OrderedDict()
|
group_info = dict()
|
||||||
group_info['hosts'] = group_hosts_map.get(group.id, [])
|
group_info['hosts'] = group_hosts_map.get(group.id, [])
|
||||||
group_info['children'] = group_children_map.get(group.id, [])
|
group_info['children'] = group_children_map.get(group.id, [])
|
||||||
group_info['vars'] = group.variables_dict
|
group_info['vars'] = group.variables_dict
|
||||||
data[group.name] = group_info
|
data[group.name] = group_info
|
||||||
|
|
||||||
if hostvars:
|
if hostvars:
|
||||||
data.setdefault('_meta', OrderedDict())
|
data.setdefault('_meta', dict())
|
||||||
data['_meta'].setdefault('hostvars', OrderedDict())
|
data['_meta'].setdefault('hostvars', dict())
|
||||||
for host in obj.hosts.filter(**hosts_q):
|
for host in obj.hosts.filter(**hosts_q):
|
||||||
data['_meta']['hostvars'][host.name] = host.variables_dict
|
data['_meta']['hostvars'][host.name] = host.variables_dict
|
||||||
|
|
||||||
@@ -2669,6 +2667,12 @@ class InventoryUpdateList(ListAPIView):
|
|||||||
model = InventoryUpdate
|
model = InventoryUpdate
|
||||||
serializer_class = InventoryUpdateListSerializer
|
serializer_class = InventoryUpdateListSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super(InventoryUpdateList, self).get_queryset()
|
||||||
|
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
||||||
|
qs = qs.defer('result_stdout_text')
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
|
|
||||||
# Tower
|
# Tower
|
||||||
from awx.main.utils.common import get_licenser
|
from awx.main.utils.common import get_licenser, memoize
|
||||||
|
|
||||||
__all__ = ['LicenseForbids', 'get_license', 'get_licensed_features',
|
__all__ = ['LicenseForbids', 'get_license', 'get_licensed_features',
|
||||||
'feature_enabled', 'feature_exists']
|
'feature_enabled', 'feature_exists']
|
||||||
@@ -40,6 +40,7 @@ def get_licensed_features():
|
|||||||
return features
|
return features
|
||||||
|
|
||||||
|
|
||||||
|
@memoize(cache_name='ephemeral')
|
||||||
def feature_enabled(name):
|
def feature_enabled(name):
|
||||||
"""Return True if the requested feature is enabled, False otherwise."""
|
"""Return True if the requested feature is enabled, False otherwise."""
|
||||||
validated_license_data = _get_validated_license_data()
|
validated_license_data = _get_validated_license_data()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from awx.main.models import * # noqa
|
|||||||
from awx.main.models.unified_jobs import ACTIVE_STATES
|
from awx.main.models.unified_jobs import ACTIVE_STATES
|
||||||
from awx.main.models.mixins import ResourceMixin
|
from awx.main.models.mixins import ResourceMixin
|
||||||
|
|
||||||
from awx.conf.license import LicenseForbids
|
from awx.conf.license import LicenseForbids, feature_enabled
|
||||||
|
|
||||||
__all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors',
|
__all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors',
|
||||||
'user_accessible_objects', 'consumer_access',
|
'user_accessible_objects', 'consumer_access',
|
||||||
@@ -140,7 +140,14 @@ def get_user_capabilities(user, instance, **kwargs):
|
|||||||
convenient for the user interface to consume and hide or show various
|
convenient for the user interface to consume and hide or show various
|
||||||
actions in the interface.
|
actions in the interface.
|
||||||
'''
|
'''
|
||||||
access_class = access_registry[instance.__class__]
|
cls = instance.__class__
|
||||||
|
# When `.defer()` is used w/ the Django ORM, the result is a subclass of
|
||||||
|
# the original that represents e.g.,
|
||||||
|
# awx.main.models.ad_hoc_commands.AdHocCommand_Deferred_result_stdout_text
|
||||||
|
# We want to do the access registry lookup keyed on the base class name.
|
||||||
|
if getattr(cls, '_deferred', False):
|
||||||
|
cls = instance.__class__.__bases__[0]
|
||||||
|
access_class = access_registry[cls]
|
||||||
return access_class(user).get_user_capabilities(instance, **kwargs)
|
return access_class(user).get_user_capabilities(instance, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -323,6 +330,10 @@ class BaseAccess(object):
|
|||||||
if validation_errors:
|
if validation_errors:
|
||||||
user_capabilities[display_method] = False
|
user_capabilities[display_method] = False
|
||||||
continue
|
continue
|
||||||
|
elif isinstance(obj, (WorkflowJobTemplate, WorkflowJob)):
|
||||||
|
if not feature_enabled('workflows'):
|
||||||
|
user_capabilities[display_method] = (display_method == 'delete')
|
||||||
|
continue
|
||||||
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
|
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
|
||||||
user_capabilities[display_method] = self.user.is_superuser
|
user_capabilities[display_method] = self.user.is_superuser
|
||||||
continue
|
continue
|
||||||
@@ -482,8 +493,10 @@ class UserAccess(BaseAccess):
|
|||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if data is not None and ('is_superuser' in data or 'is_system_auditor' in data):
|
if data is not None and ('is_superuser' in data or 'is_system_auditor' in data):
|
||||||
if (to_python_boolean(data.get('is_superuser', 'false'), allow_none=True) or
|
if to_python_boolean(data.get('is_superuser', 'false'), allow_none=True) and \
|
||||||
to_python_boolean(data.get('is_system_auditor', 'false'), allow_none=True)) and not self.user.is_superuser:
|
not self.user.is_superuser:
|
||||||
|
return False
|
||||||
|
if to_python_boolean(data.get('is_system_auditor', 'false'), allow_none=True) and not (self.user.is_superuser or self.user == obj):
|
||||||
return False
|
return False
|
||||||
# A user can be changed if they are themselves, or by org admins or
|
# A user can be changed if they are themselves, or by org admins or
|
||||||
# superusers. Change permission implies changing only certain fields
|
# superusers. Change permission implies changing only certain fields
|
||||||
@@ -2068,6 +2081,8 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
# 'job_template__project',
|
# 'job_template__project',
|
||||||
# 'job_template__credential',
|
# 'job_template__credential',
|
||||||
#)
|
#)
|
||||||
|
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
||||||
|
qs = qs.defer('result_stdout_text')
|
||||||
return qs.all()
|
return qs.all()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
CLOUD_PROVIDERS = ('azure', 'azure_rm', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'satellite6', 'cloudforms')
|
CLOUD_PROVIDERS = ('azure', 'azure_rm', 'ec2', 'gce', 'rax', 'vmware', 'openstack', 'satellite6', 'cloudforms')
|
||||||
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',)
|
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',)
|
||||||
PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))]
|
PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), ('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))]
|
||||||
|
ANSI_SGR_PATTERN = re.compile(r'\x1b\[[0-9;]*m')
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ class Command(BaseCommand):
|
|||||||
Deprovision a Tower cluster node
|
Deprovision a Tower cluster node
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
help = (
|
||||||
|
'Remove instance from the database. '
|
||||||
|
'Specify `--hostname` to use this command.'
|
||||||
|
)
|
||||||
|
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
make_option('--hostname', dest='hostname', type='string',
|
make_option('--hostname', dest='hostname', type='string',
|
||||||
help='Hostname used during provisioning'),
|
help='Hostname used during provisioning'),
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ class Command(BaseCommand):
|
|||||||
Regsiter this instance with the database for HA tracking.
|
Regsiter this instance with the database for HA tracking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
help = (
|
||||||
|
'Add instance to the database. '
|
||||||
|
'Specify `--hostname` to use this command.'
|
||||||
|
)
|
||||||
|
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
make_option('--hostname', dest='hostname', type='string',
|
make_option('--hostname', dest='hostname', type='string',
|
||||||
help='Hostname used during provisioning'),
|
help='Hostname used during provisioning'),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def _create_fact_scan_project(ContentType, Project, org):
|
|||||||
ct = ContentType.objects.get_for_model(Project)
|
ct = ContentType.objects.get_for_model(Project)
|
||||||
name = "Tower Fact Scan - {}".format(org.name if org else "No Organization")
|
name = "Tower Fact Scan - {}".format(org.name if org else "No Organization")
|
||||||
proj = Project(name=name,
|
proj = Project(name=name,
|
||||||
scm_url='https://github.com/ansible/tower-fact-modules',
|
scm_url='https://github.com/ansible/awx-facts-playbooks',
|
||||||
scm_type='git',
|
scm_type='git',
|
||||||
scm_update_on_launch=True,
|
scm_update_on_launch=True,
|
||||||
scm_update_cache_timeout=86400,
|
scm_update_cache_timeout=86400,
|
||||||
|
|||||||
@@ -145,3 +145,17 @@ activity_stream_registrar.connect(WorkflowJob)
|
|||||||
|
|
||||||
# prevent API filtering on certain Django-supplied sensitive fields
|
# prevent API filtering on certain Django-supplied sensitive fields
|
||||||
prevent_search(User._meta.get_field('password'))
|
prevent_search(User._meta.get_field('password'))
|
||||||
|
|
||||||
|
|
||||||
|
# Always, always, always defer result_stdout_text for polymorphic UnifiedJob rows
|
||||||
|
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
||||||
|
def defer_stdout(f):
|
||||||
|
def _wrapped(*args, **kwargs):
|
||||||
|
objs = f(*args, **kwargs)
|
||||||
|
objs.query.deferred_loading[0].add('result_stdout_text')
|
||||||
|
return objs
|
||||||
|
return _wrapped
|
||||||
|
|
||||||
|
|
||||||
|
for cls in UnifiedJob.__subclasses__():
|
||||||
|
cls.base_objects.filter = defer_stdout(cls.base_objects.filter)
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ class TaskManager():
|
|||||||
# Already processed dependencies for this job
|
# Already processed dependencies for this job
|
||||||
if job.dependent_jobs.all():
|
if job.dependent_jobs.all():
|
||||||
return False
|
return False
|
||||||
latest_inventory_update = InventoryUpdate.objects.filter(inventory_source=inventory_source).order_by("created")
|
latest_inventory_update = InventoryUpdate.objects.filter(inventory_source=inventory_source).order_by("-created")
|
||||||
if not latest_inventory_update.exists():
|
if not latest_inventory_update.exists():
|
||||||
return True
|
return True
|
||||||
latest_inventory_update = latest_inventory_update.first()
|
latest_inventory_update = latest_inventory_update.first()
|
||||||
@@ -323,7 +323,7 @@ class TaskManager():
|
|||||||
now = tz_now()
|
now = tz_now()
|
||||||
if job.dependent_jobs.all():
|
if job.dependent_jobs.all():
|
||||||
return False
|
return False
|
||||||
latest_project_update = ProjectUpdate.objects.filter(project=job.project).order_by("created")
|
latest_project_update = ProjectUpdate.objects.filter(project=job.project, job_type='check').order_by("-created")
|
||||||
if not latest_project_update.exists():
|
if not latest_project_update.exists():
|
||||||
return True
|
return True
|
||||||
latest_project_update = latest_project_update.first()
|
latest_project_update = latest_project_update.first()
|
||||||
@@ -421,26 +421,40 @@ class TaskManager():
|
|||||||
if not found_acceptable_queue:
|
if not found_acceptable_queue:
|
||||||
logger.debug("%s couldn't be scheduled on graph, waiting for next cycle", task.log_format)
|
logger.debug("%s couldn't be scheduled on graph, waiting for next cycle", task.log_format)
|
||||||
|
|
||||||
def fail_jobs_if_not_in_celery(self, node_jobs, active_tasks, celery_task_start_time):
|
def fail_jobs_if_not_in_celery(self, node_jobs, active_tasks, celery_task_start_time,
|
||||||
|
isolated=False):
|
||||||
for task in node_jobs:
|
for task in node_jobs:
|
||||||
if (task.celery_task_id not in active_tasks and not hasattr(settings, 'IGNORE_CELERY_INSPECTOR')):
|
if (task.celery_task_id not in active_tasks and not hasattr(settings, 'IGNORE_CELERY_INSPECTOR')):
|
||||||
if isinstance(task, WorkflowJob):
|
if isinstance(task, WorkflowJob):
|
||||||
continue
|
continue
|
||||||
if task.modified > celery_task_start_time:
|
if task.modified > celery_task_start_time:
|
||||||
continue
|
continue
|
||||||
task.status = 'failed'
|
new_status = 'failed'
|
||||||
task.job_explanation += ' '.join((
|
if isolated:
|
||||||
'Task was marked as running in Tower but was not present in',
|
new_status = 'error'
|
||||||
'Celery, so it has been marked as failed.',
|
task.status = new_status
|
||||||
))
|
if isolated:
|
||||||
|
# TODO: cancel and reap artifacts of lost jobs from heartbeat
|
||||||
|
task.job_explanation += ' '.join((
|
||||||
|
'Task was marked as running in Tower but its ',
|
||||||
|
'controller management daemon was not present in',
|
||||||
|
'Celery, so it has been marked as failed.',
|
||||||
|
'Task may still be running, but contactability is unknown.'
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
task.job_explanation += ' '.join((
|
||||||
|
'Task was marked as running in Tower but was not present in',
|
||||||
|
'Celery, so it has been marked as failed.',
|
||||||
|
))
|
||||||
try:
|
try:
|
||||||
task.save(update_fields=['status', 'job_explanation'])
|
task.save(update_fields=['status', 'job_explanation'])
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
logger.error("Task {} DB error in marking failed. Job possibly deleted.".format(task.log_format))
|
logger.error("Task {} DB error in marking failed. Job possibly deleted.".format(task.log_format))
|
||||||
continue
|
continue
|
||||||
awx_tasks._send_notification_templates(task, 'failed')
|
awx_tasks._send_notification_templates(task, 'failed')
|
||||||
task.websocket_emit_status('failed')
|
task.websocket_emit_status(new_status)
|
||||||
logger.error("Task {} has no record in celery. Marking as failed".format(task.log_format))
|
logger.error("{}Task {} has no record in celery. Marking as failed".format(
|
||||||
|
'Isolated ' if isolated else '', task.log_format))
|
||||||
|
|
||||||
def cleanup_inconsistent_celery_tasks(self):
|
def cleanup_inconsistent_celery_tasks(self):
|
||||||
'''
|
'''
|
||||||
@@ -471,26 +485,36 @@ class TaskManager():
|
|||||||
self.fail_jobs_if_not_in_celery(waiting_tasks, all_celery_task_ids, celery_task_start_time)
|
self.fail_jobs_if_not_in_celery(waiting_tasks, all_celery_task_ids, celery_task_start_time)
|
||||||
|
|
||||||
for node, node_jobs in running_tasks.iteritems():
|
for node, node_jobs in running_tasks.iteritems():
|
||||||
|
isolated = False
|
||||||
if node in active_queues:
|
if node in active_queues:
|
||||||
active_tasks = active_queues[node]
|
active_tasks = active_queues[node]
|
||||||
else:
|
else:
|
||||||
'''
|
'''
|
||||||
Node task list not found in celery. If tower thinks the node is down
|
Node task list not found in celery. We may branch into cases:
|
||||||
then fail all the jobs on the node.
|
- instance is unknown to tower, system is improperly configured
|
||||||
|
- instance is reported as down, then fail all jobs on the node
|
||||||
|
- instance is an isolated node, then check running tasks
|
||||||
|
among all allowed controller nodes for management process
|
||||||
'''
|
'''
|
||||||
try:
|
instance = Instance.objects.filter(hostname=node).first()
|
||||||
instance = Instance.objects.get(hostname=node)
|
|
||||||
if instance.capacity == 0:
|
|
||||||
active_tasks = []
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
except Instance.DoesNotExist:
|
|
||||||
logger.error("Execution node Instance {} not found in database. "
|
|
||||||
"The node is currently executing jobs {}".format(node,
|
|
||||||
[j.log_format for j in node_jobs]))
|
|
||||||
active_tasks = []
|
|
||||||
|
|
||||||
self.fail_jobs_if_not_in_celery(node_jobs, active_tasks, celery_task_start_time)
|
if instance is None:
|
||||||
|
logger.error("Execution node Instance {} not found in database. "
|
||||||
|
"The node is currently executing jobs {}".format(
|
||||||
|
node, [j.log_format for j in node_jobs]))
|
||||||
|
active_tasks = []
|
||||||
|
elif instance.capacity == 0:
|
||||||
|
active_tasks = []
|
||||||
|
elif instance.rampart_groups.filter(controller__isnull=False).exists():
|
||||||
|
active_tasks = all_celery_task_ids
|
||||||
|
isolated = True
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.fail_jobs_if_not_in_celery(
|
||||||
|
node_jobs, active_tasks, celery_task_start_time,
|
||||||
|
isolated=isolated
|
||||||
|
)
|
||||||
|
|
||||||
def calculate_capacity_consumed(self, tasks):
|
def calculate_capacity_consumed(self, tasks):
|
||||||
self.graph = InstanceGroup.objects.capacity_values(tasks=tasks, graph=self.graph)
|
self.graph = InstanceGroup.objects.capacity_values(tasks=tasks, graph=self.graph)
|
||||||
|
|||||||
@@ -388,6 +388,9 @@ def activity_stream_create(sender, instance, created, **kwargs):
|
|||||||
# Skip recording any inventory source directly associated with a group.
|
# Skip recording any inventory source directly associated with a group.
|
||||||
if isinstance(instance, InventorySource) and instance.deprecated_group:
|
if isinstance(instance, InventorySource) and instance.deprecated_group:
|
||||||
return
|
return
|
||||||
|
_type = type(instance)
|
||||||
|
if getattr(_type, '_deferred', False):
|
||||||
|
return
|
||||||
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
||||||
changes = model_to_dict(instance, model_serializer_mapping)
|
changes = model_to_dict(instance, model_serializer_mapping)
|
||||||
# Special case where Job survey password variables need to be hidden
|
# Special case where Job survey password variables need to be hidden
|
||||||
@@ -421,6 +424,9 @@ def activity_stream_update(sender, instance, **kwargs):
|
|||||||
changes = model_instance_diff(old, new, model_serializer_mapping)
|
changes = model_instance_diff(old, new, model_serializer_mapping)
|
||||||
if changes is None:
|
if changes is None:
|
||||||
return
|
return
|
||||||
|
_type = type(instance)
|
||||||
|
if getattr(_type, '_deferred', False):
|
||||||
|
return
|
||||||
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
||||||
activity_entry = ActivityStream(
|
activity_entry = ActivityStream(
|
||||||
operation='update',
|
operation='update',
|
||||||
@@ -445,6 +451,9 @@ def activity_stream_delete(sender, instance, **kwargs):
|
|||||||
# explicitly called with flag on in Inventory.schedule_deletion.
|
# explicitly called with flag on in Inventory.schedule_deletion.
|
||||||
if isinstance(instance, Inventory) and not kwargs.get('inventory_delete_flag', False):
|
if isinstance(instance, Inventory) and not kwargs.get('inventory_delete_flag', False):
|
||||||
return
|
return
|
||||||
|
_type = type(instance)
|
||||||
|
if getattr(_type, '_deferred', False):
|
||||||
|
return
|
||||||
changes = model_to_dict(instance)
|
changes = model_to_dict(instance)
|
||||||
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
object1 = camelcase_to_underscore(instance.__class__.__name__)
|
||||||
activity_entry = ActivityStream(
|
activity_entry = ActivityStream(
|
||||||
@@ -466,6 +475,9 @@ def activity_stream_associate(sender, instance, **kwargs):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
obj1 = instance
|
obj1 = instance
|
||||||
|
_type = type(instance)
|
||||||
|
if getattr(_type, '_deferred', False):
|
||||||
|
return
|
||||||
object1=camelcase_to_underscore(obj1.__class__.__name__)
|
object1=camelcase_to_underscore(obj1.__class__.__name__)
|
||||||
obj_rel = sender.__module__ + "." + sender.__name__
|
obj_rel = sender.__module__ + "." + sender.__name__
|
||||||
|
|
||||||
@@ -476,6 +488,9 @@ def activity_stream_associate(sender, instance, **kwargs):
|
|||||||
if not obj2_actual.exists():
|
if not obj2_actual.exists():
|
||||||
continue
|
continue
|
||||||
obj2_actual = obj2_actual[0]
|
obj2_actual = obj2_actual[0]
|
||||||
|
_type = type(obj2_actual)
|
||||||
|
if getattr(_type, '_deferred', False):
|
||||||
|
return
|
||||||
if isinstance(obj2_actual, Role) and obj2_actual.content_object is not None:
|
if isinstance(obj2_actual, Role) and obj2_actual.content_object is not None:
|
||||||
obj2_actual = obj2_actual.content_object
|
obj2_actual = obj2_actual.content_object
|
||||||
object2 = camelcase_to_underscore(obj2_actual.__class__.__name__)
|
object2 = camelcase_to_underscore(obj2_actual.__class__.__name__)
|
||||||
|
|||||||
@@ -320,7 +320,11 @@ def awx_periodic_scheduler(self):
|
|||||||
def _send_notification_templates(instance, status_str):
|
def _send_notification_templates(instance, status_str):
|
||||||
if status_str not in ['succeeded', 'failed']:
|
if status_str not in ['succeeded', 'failed']:
|
||||||
raise ValueError(_("status_str must be either succeeded or failed"))
|
raise ValueError(_("status_str must be either succeeded or failed"))
|
||||||
notification_templates = instance.get_notification_templates()
|
try:
|
||||||
|
notification_templates = instance.get_notification_templates()
|
||||||
|
except:
|
||||||
|
logger.warn("No notification template defined for emitting notification")
|
||||||
|
notification_templates = None
|
||||||
if notification_templates:
|
if notification_templates:
|
||||||
if status_str == 'succeeded':
|
if status_str == 'succeeded':
|
||||||
notification_template_type = 'success'
|
notification_template_type = 'success'
|
||||||
@@ -482,6 +486,7 @@ class BaseTask(LogErrorsTask):
|
|||||||
model = None
|
model = None
|
||||||
abstract = True
|
abstract = True
|
||||||
cleanup_paths = []
|
cleanup_paths = []
|
||||||
|
proot_show_paths = []
|
||||||
|
|
||||||
def update_model(self, pk, _attempt=0, **updates):
|
def update_model(self, pk, _attempt=0, **updates):
|
||||||
"""Reload the model instance from the database and update the
|
"""Reload the model instance from the database and update the
|
||||||
@@ -793,6 +798,7 @@ class BaseTask(LogErrorsTask):
|
|||||||
# May have to serialize the value
|
# May have to serialize the value
|
||||||
kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs)
|
kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs)
|
||||||
kwargs['passwords'] = self.build_passwords(instance, **kwargs)
|
kwargs['passwords'] = self.build_passwords(instance, **kwargs)
|
||||||
|
kwargs['proot_show_paths'] = self.proot_show_paths
|
||||||
args = self.build_args(instance, **kwargs)
|
args = self.build_args(instance, **kwargs)
|
||||||
safe_args = self.build_safe_args(instance, **kwargs)
|
safe_args = self.build_safe_args(instance, **kwargs)
|
||||||
output_replacements = self.build_output_replacements(instance, **kwargs)
|
output_replacements = self.build_output_replacements(instance, **kwargs)
|
||||||
@@ -1068,6 +1074,7 @@ class RunJob(BaseTask):
|
|||||||
env['VMWARE_USER'] = cloud_cred.username
|
env['VMWARE_USER'] = cloud_cred.username
|
||||||
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
|
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
|
||||||
env['VMWARE_HOST'] = cloud_cred.host
|
env['VMWARE_HOST'] = cloud_cred.host
|
||||||
|
env['VMWARE_VALIDATE_CERTS'] = str(settings.VMWARE_VALIDATE_CERTS)
|
||||||
elif cloud_cred and cloud_cred.kind == 'openstack':
|
elif cloud_cred and cloud_cred.kind == 'openstack':
|
||||||
env['OS_CLIENT_CONFIG_FILE'] = cred_files.get(cloud_cred, '')
|
env['OS_CLIENT_CONFIG_FILE'] = cred_files.get(cloud_cred, '')
|
||||||
|
|
||||||
@@ -1285,6 +1292,10 @@ class RunProjectUpdate(BaseTask):
|
|||||||
name = 'awx.main.tasks.run_project_update'
|
name = 'awx.main.tasks.run_project_update'
|
||||||
model = ProjectUpdate
|
model = ProjectUpdate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def proot_show_paths(self):
|
||||||
|
return [settings.PROJECTS_ROOT]
|
||||||
|
|
||||||
def build_private_data(self, project_update, **kwargs):
|
def build_private_data(self, project_update, **kwargs):
|
||||||
'''
|
'''
|
||||||
Return SSH private key data needed for this project update.
|
Return SSH private key data needed for this project update.
|
||||||
@@ -1298,7 +1309,7 @@ class RunProjectUpdate(BaseTask):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
handle, self.revision_path = tempfile.mkstemp(dir=settings.AWX_PROOT_BASE_PATH)
|
handle, self.revision_path = tempfile.mkstemp(dir=settings.PROJECTS_ROOT)
|
||||||
self.cleanup_paths.append(self.revision_path)
|
self.cleanup_paths.append(self.revision_path)
|
||||||
private_data = {'credentials': {}}
|
private_data = {'credentials': {}}
|
||||||
if project_update.credential:
|
if project_update.credential:
|
||||||
@@ -1591,6 +1602,12 @@ class RunProjectUpdate(BaseTask):
|
|||||||
if status == 'successful' and instance.launch_type != 'sync':
|
if status == 'successful' and instance.launch_type != 'sync':
|
||||||
self._update_dependent_inventories(instance, dependent_inventory_sources)
|
self._update_dependent_inventories(instance, dependent_inventory_sources)
|
||||||
|
|
||||||
|
def should_use_proot(self, instance, **kwargs):
|
||||||
|
'''
|
||||||
|
Return whether this task should use proot.
|
||||||
|
'''
|
||||||
|
return getattr(settings, 'AWX_PROOT_ENABLED', False)
|
||||||
|
|
||||||
|
|
||||||
class RunInventoryUpdate(BaseTask):
|
class RunInventoryUpdate(BaseTask):
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ def test_system_auditor_is_system_auditor(system_auditor):
|
|||||||
assert system_auditor.is_system_auditor
|
assert system_auditor.is_system_auditor
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_system_auditor_can_modify_self(system_auditor):
|
||||||
|
access = UserAccess(system_auditor)
|
||||||
|
assert access.can_change(obj=system_auditor, data=dict(is_system_auditor='true'))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_queryset(user):
|
def test_user_queryset(user):
|
||||||
u = user('pete', False)
|
u = user('pete', False)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class TestCleanupInconsistentCeleryTasks():
|
|||||||
@mock.patch.object(TaskManager, 'get_active_tasks', return_value=([], {}))
|
@mock.patch.object(TaskManager, 'get_active_tasks', return_value=([], {}))
|
||||||
@mock.patch.object(TaskManager, 'get_running_tasks', return_value=({'host1': [Job(id=2), Job(id=3),]}, []))
|
@mock.patch.object(TaskManager, 'get_running_tasks', return_value=({'host1': [Job(id=2), Job(id=3),]}, []))
|
||||||
@mock.patch.object(InstanceGroup.objects, 'prefetch_related', return_value=[])
|
@mock.patch.object(InstanceGroup.objects, 'prefetch_related', return_value=[])
|
||||||
@mock.patch.object(Instance.objects, 'get', side_effect=Instance.DoesNotExist)
|
@mock.patch.object(Instance.objects, 'filter', return_value=mock.MagicMock(first=lambda: None))
|
||||||
@mock.patch('awx.main.scheduler.logger')
|
@mock.patch('awx.main.scheduler.logger')
|
||||||
def test_instance_does_not_exist(self, logger_mock, *args):
|
def test_instance_does_not_exist(self, logger_mock, *args):
|
||||||
logger_mock.error = mock.MagicMock(side_effect=RuntimeError("mocked"))
|
logger_mock.error = mock.MagicMock(side_effect=RuntimeError("mocked"))
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ class TestJobExecution:
|
|||||||
EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----'
|
EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----'
|
||||||
|
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
|
if not os.path.exists(settings.PROJECTS_ROOT):
|
||||||
|
os.mkdir(settings.PROJECTS_ROOT)
|
||||||
self.project_path = tempfile.mkdtemp(prefix='awx_project_')
|
self.project_path = tempfile.mkdtemp(prefix='awx_project_')
|
||||||
with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f:
|
with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f:
|
||||||
f.write('---')
|
f.write('---')
|
||||||
@@ -281,6 +283,15 @@ class TestGenericRun(TestJobExecution):
|
|||||||
args, cwd, env, stdout = call_args
|
args, cwd, env, stdout = call_args
|
||||||
assert args[0] == 'bwrap'
|
assert args[0] == 'bwrap'
|
||||||
|
|
||||||
|
def test_bwrap_virtualenvs_are_readonly(self):
|
||||||
|
self.task.run(self.pk)
|
||||||
|
|
||||||
|
assert self.run_pexpect.call_count == 1
|
||||||
|
call_args, _ = self.run_pexpect.call_args_list[0]
|
||||||
|
args, cwd, env, stdout = call_args
|
||||||
|
assert '--ro-bind %s %s' % (settings.ANSIBLE_VENV_PATH, settings.ANSIBLE_VENV_PATH) in ' '.join(args) # noqa
|
||||||
|
assert '--ro-bind %s %s' % (settings.AWX_VENV_PATH, settings.AWX_VENV_PATH) in ' '.join(args) # noqa
|
||||||
|
|
||||||
def test_awx_task_env(self):
|
def test_awx_task_env(self):
|
||||||
patch = mock.patch('awx.main.tasks.settings.AWX_TASK_ENV', {'FOO': 'BAR'})
|
patch = mock.patch('awx.main.tasks.settings.AWX_TASK_ENV', {'FOO': 'BAR'})
|
||||||
patch.start()
|
patch.start()
|
||||||
@@ -1096,6 +1107,27 @@ class TestProjectUpdateCredentials(TestJobExecution):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def test_bwrap_exposes_projects_root(self):
|
||||||
|
ssh = CredentialType.defaults['ssh']()
|
||||||
|
self.instance.scm_type = 'git'
|
||||||
|
self.instance.credential = Credential(
|
||||||
|
pk=1,
|
||||||
|
credential_type=ssh,
|
||||||
|
)
|
||||||
|
self.task.run(self.pk)
|
||||||
|
|
||||||
|
assert self.run_pexpect.call_count == 1
|
||||||
|
call_args, call_kwargs = self.run_pexpect.call_args_list[0]
|
||||||
|
args, cwd, env, stdout = call_args
|
||||||
|
|
||||||
|
assert ' '.join(args).startswith('bwrap')
|
||||||
|
' '.join([
|
||||||
|
'--bind',
|
||||||
|
settings.PROJECTS_ROOT,
|
||||||
|
settings.PROJECTS_ROOT,
|
||||||
|
]) in ' '.join(args)
|
||||||
|
assert '"scm_revision_output": "/projects/tmp' in ' '.join(args)
|
||||||
|
|
||||||
def test_username_and_password_auth(self, scm_type):
|
def test_username_and_password_auth(self, scm_type):
|
||||||
ssh = CredentialType.defaults['ssh']()
|
ssh = CredentialType.defaults['ssh']()
|
||||||
self.instance.scm_type = scm_type
|
self.instance.scm_type = scm_type
|
||||||
|
|||||||
@@ -108,13 +108,14 @@ class RequireDebugTrueOrTest(logging.Filter):
|
|||||||
return settings.DEBUG or 'test' in sys.argv
|
return settings.DEBUG or 'test' in sys.argv
|
||||||
|
|
||||||
|
|
||||||
def memoize(ttl=60, cache_key=None):
|
def memoize(ttl=60, cache_key=None, cache_name='default'):
|
||||||
'''
|
'''
|
||||||
Decorator to wrap a function and cache its result.
|
Decorator to wrap a function and cache its result.
|
||||||
'''
|
'''
|
||||||
from django.core.cache import cache
|
from django.core.cache import caches
|
||||||
|
|
||||||
def _memoizer(f, *args, **kwargs):
|
def _memoizer(f, *args, **kwargs):
|
||||||
|
cache = caches[cache_name]
|
||||||
key = cache_key or slugify('%s %r %r' % (f.__name__, args, kwargs))
|
key = cache_key or slugify('%s %r %r' % (f.__name__, args, kwargs))
|
||||||
value = cache.get(key)
|
value = cache.get(key)
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -696,8 +697,13 @@ def wrap_args_with_proot(args, cwd, **kwargs):
|
|||||||
show_paths = [cwd, kwargs['private_data_dir']]
|
show_paths = [cwd, kwargs['private_data_dir']]
|
||||||
else:
|
else:
|
||||||
show_paths = [cwd]
|
show_paths = [cwd]
|
||||||
show_paths.extend([settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH])
|
for venv in (
|
||||||
|
settings.ANSIBLE_VENV_PATH,
|
||||||
|
settings.AWX_VENV_PATH
|
||||||
|
):
|
||||||
|
new_args.extend(['--ro-bind', venv, venv])
|
||||||
show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
|
show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
|
||||||
|
show_paths.extend(kwargs.get('proot_show_paths', []))
|
||||||
for path in sorted(set(show_paths)):
|
for path in sorted(set(show_paths)):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ Command line arguments:
|
|||||||
- tenant
|
- tenant
|
||||||
- ad_user
|
- ad_user
|
||||||
- password
|
- password
|
||||||
|
- cloud_environment
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
- AZURE_PROFILE
|
- AZURE_PROFILE
|
||||||
@@ -58,6 +59,7 @@ Environment variables:
|
|||||||
- AZURE_TENANT
|
- AZURE_TENANT
|
||||||
- AZURE_AD_USER
|
- AZURE_AD_USER
|
||||||
- AZURE_PASSWORD
|
- AZURE_PASSWORD
|
||||||
|
- AZURE_CLOUD_ENVIRONMENT
|
||||||
|
|
||||||
Run for Specific Host
|
Run for Specific Host
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -190,22 +192,27 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import inspect
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
|
|
||||||
from os.path import expanduser
|
from os.path import expanduser
|
||||||
|
import ansible.module_utils.six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
HAS_AZURE = True
|
HAS_AZURE = True
|
||||||
HAS_AZURE_EXC = None
|
HAS_AZURE_EXC = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from msrestazure.azure_exceptions import CloudError
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
from msrestazure import azure_cloud
|
||||||
from azure.mgmt.compute import __version__ as azure_compute_version
|
from azure.mgmt.compute import __version__ as azure_compute_version
|
||||||
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
||||||
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
||||||
from azure.mgmt.network.network_management_client import NetworkManagementClient
|
from azure.mgmt.network import NetworkManagementClient
|
||||||
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient
|
from azure.mgmt.resource.resources import ResourceManagementClient
|
||||||
from azure.mgmt.compute.compute_management_client import ComputeManagementClient
|
from azure.mgmt.compute import ComputeManagementClient
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
HAS_AZURE_EXC = exc
|
HAS_AZURE_EXC = exc
|
||||||
HAS_AZURE = False
|
HAS_AZURE = False
|
||||||
@@ -218,7 +225,8 @@ AZURE_CREDENTIAL_ENV_MAPPING = dict(
|
|||||||
secret='AZURE_SECRET',
|
secret='AZURE_SECRET',
|
||||||
tenant='AZURE_TENANT',
|
tenant='AZURE_TENANT',
|
||||||
ad_user='AZURE_AD_USER',
|
ad_user='AZURE_AD_USER',
|
||||||
password='AZURE_PASSWORD'
|
password='AZURE_PASSWORD',
|
||||||
|
cloud_environment='AZURE_CLOUD_ENVIRONMENT',
|
||||||
)
|
)
|
||||||
|
|
||||||
AZURE_CONFIG_SETTINGS = dict(
|
AZURE_CONFIG_SETTINGS = dict(
|
||||||
@@ -232,7 +240,7 @@ AZURE_CONFIG_SETTINGS = dict(
|
|||||||
group_by_tag='AZURE_GROUP_BY_TAG'
|
group_by_tag='AZURE_GROUP_BY_TAG'
|
||||||
)
|
)
|
||||||
|
|
||||||
AZURE_MIN_VERSION = "0.30.0rc5"
|
AZURE_MIN_VERSION = "2.0.0"
|
||||||
|
|
||||||
|
|
||||||
def azure_id_to_dict(id):
|
def azure_id_to_dict(id):
|
||||||
@@ -249,6 +257,7 @@ class AzureRM(object):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self._args = args
|
self._args = args
|
||||||
|
self._cloud_environment = None
|
||||||
self._compute_client = None
|
self._compute_client = None
|
||||||
self._resource_client = None
|
self._resource_client = None
|
||||||
self._network_client = None
|
self._network_client = None
|
||||||
@@ -262,6 +271,26 @@ class AzureRM(object):
|
|||||||
self.fail("Failed to get credentials. Either pass as parameters, set environment variables, "
|
self.fail("Failed to get credentials. Either pass as parameters, set environment variables, "
|
||||||
"or define a profile in ~/.azure/credentials.")
|
"or define a profile in ~/.azure/credentials.")
|
||||||
|
|
||||||
|
# if cloud_environment specified, look up/build Cloud object
|
||||||
|
raw_cloud_env = self.credentials.get('cloud_environment')
|
||||||
|
if not raw_cloud_env:
|
||||||
|
self._cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD # SDK default
|
||||||
|
else:
|
||||||
|
# try to look up "well-known" values via the name attribute on azure_cloud members
|
||||||
|
all_clouds = [x[1] for x in inspect.getmembers(azure_cloud) if isinstance(x[1], azure_cloud.Cloud)]
|
||||||
|
matched_clouds = [x for x in all_clouds if x.name == raw_cloud_env]
|
||||||
|
if len(matched_clouds) == 1:
|
||||||
|
self._cloud_environment = matched_clouds[0]
|
||||||
|
elif len(matched_clouds) > 1:
|
||||||
|
self.fail("Azure SDK failure: more than one cloud matched for cloud_environment name '{0}'".format(raw_cloud_env))
|
||||||
|
else:
|
||||||
|
if not urlparse.urlparse(raw_cloud_env).scheme:
|
||||||
|
self.fail("cloud_environment must be an endpoint discovery URL or one of {0}".format([x.name for x in all_clouds]))
|
||||||
|
try:
|
||||||
|
self._cloud_environment = azure_cloud.get_cloud_from_metadata_endpoint(raw_cloud_env)
|
||||||
|
except Exception as e:
|
||||||
|
self.fail("cloud_environment {0} could not be resolved: {1}".format(raw_cloud_env, e.message))
|
||||||
|
|
||||||
if self.credentials.get('subscription_id', None) is None:
|
if self.credentials.get('subscription_id', None) is None:
|
||||||
self.fail("Credentials did not include a subscription_id value.")
|
self.fail("Credentials did not include a subscription_id value.")
|
||||||
self.log("setting subscription_id")
|
self.log("setting subscription_id")
|
||||||
@@ -272,16 +301,23 @@ class AzureRM(object):
|
|||||||
self.credentials.get('tenant') is not None:
|
self.credentials.get('tenant') is not None:
|
||||||
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
|
self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'],
|
||||||
secret=self.credentials['secret'],
|
secret=self.credentials['secret'],
|
||||||
tenant=self.credentials['tenant'])
|
tenant=self.credentials['tenant'],
|
||||||
|
cloud_environment=self._cloud_environment)
|
||||||
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
||||||
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'], self.credentials['password'])
|
tenant = self.credentials.get('tenant')
|
||||||
|
if not tenant:
|
||||||
|
tenant = 'common'
|
||||||
|
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'],
|
||||||
|
self.credentials['password'],
|
||||||
|
tenant=tenant,
|
||||||
|
cloud_environment=self._cloud_environment)
|
||||||
else:
|
else:
|
||||||
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
|
self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
|
||||||
"Credentials must include client_id, secret and tenant or ad_user and password.")
|
"Credentials must include client_id, secret and tenant or ad_user and password.")
|
||||||
|
|
||||||
def log(self, msg):
|
def log(self, msg):
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print (msg + u'\n')
|
print(msg + u'\n')
|
||||||
|
|
||||||
def fail(self, msg):
|
def fail(self, msg):
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
@@ -341,6 +377,10 @@ class AzureRM(object):
|
|||||||
self.log('Received credentials from parameters.')
|
self.log('Received credentials from parameters.')
|
||||||
return arg_credentials
|
return arg_credentials
|
||||||
|
|
||||||
|
if arg_credentials['ad_user'] is not None:
|
||||||
|
self.log('Received credentials from parameters.')
|
||||||
|
return arg_credentials
|
||||||
|
|
||||||
# try environment
|
# try environment
|
||||||
env_credentials = self._get_env_credentials()
|
env_credentials = self._get_env_credentials()
|
||||||
if env_credentials:
|
if env_credentials:
|
||||||
@@ -372,7 +412,12 @@ class AzureRM(object):
|
|||||||
def network_client(self):
|
def network_client(self):
|
||||||
self.log('Getting network client')
|
self.log('Getting network client')
|
||||||
if not self._network_client:
|
if not self._network_client:
|
||||||
self._network_client = NetworkManagementClient(self.azure_credentials, self.subscription_id)
|
self._network_client = NetworkManagementClient(
|
||||||
|
self.azure_credentials,
|
||||||
|
self.subscription_id,
|
||||||
|
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||||
|
api_version='2017-06-01'
|
||||||
|
)
|
||||||
self._register('Microsoft.Network')
|
self._register('Microsoft.Network')
|
||||||
return self._network_client
|
return self._network_client
|
||||||
|
|
||||||
@@ -380,14 +425,24 @@ class AzureRM(object):
|
|||||||
def rm_client(self):
|
def rm_client(self):
|
||||||
self.log('Getting resource manager client')
|
self.log('Getting resource manager client')
|
||||||
if not self._resource_client:
|
if not self._resource_client:
|
||||||
self._resource_client = ResourceManagementClient(self.azure_credentials, self.subscription_id)
|
self._resource_client = ResourceManagementClient(
|
||||||
|
self.azure_credentials,
|
||||||
|
self.subscription_id,
|
||||||
|
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||||
|
api_version='2017-05-10'
|
||||||
|
)
|
||||||
return self._resource_client
|
return self._resource_client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def compute_client(self):
|
def compute_client(self):
|
||||||
self.log('Getting compute client')
|
self.log('Getting compute client')
|
||||||
if not self._compute_client:
|
if not self._compute_client:
|
||||||
self._compute_client = ComputeManagementClient(self.azure_credentials, self.subscription_id)
|
self._compute_client = ComputeManagementClient(
|
||||||
|
self.azure_credentials,
|
||||||
|
self.subscription_id,
|
||||||
|
base_url=self._cloud_environment.endpoints.resource_manager,
|
||||||
|
api_version='2017-03-30'
|
||||||
|
)
|
||||||
self._register('Microsoft.Compute')
|
self._register('Microsoft.Compute')
|
||||||
return self._compute_client
|
return self._compute_client
|
||||||
|
|
||||||
@@ -440,7 +495,7 @@ class AzureInventory(object):
|
|||||||
self.include_powerstate = False
|
self.include_powerstate = False
|
||||||
|
|
||||||
self.get_inventory()
|
self.get_inventory()
|
||||||
print (self._json_format_dict(pretty=self._args.pretty))
|
print(self._json_format_dict(pretty=self._args.pretty))
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def _parse_cli_args(self):
|
def _parse_cli_args(self):
|
||||||
@@ -448,13 +503,13 @@ class AzureInventory(object):
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Produce an Ansible Inventory file for an Azure subscription')
|
description='Produce an Ansible Inventory file for an Azure subscription')
|
||||||
parser.add_argument('--list', action='store_true', default=True,
|
parser.add_argument('--list', action='store_true', default=True,
|
||||||
help='List instances (default: True)')
|
help='List instances (default: True)')
|
||||||
parser.add_argument('--debug', action='store_true', default=False,
|
parser.add_argument('--debug', action='store_true', default=False,
|
||||||
help='Send debug messages to STDOUT')
|
help='Send debug messages to STDOUT')
|
||||||
parser.add_argument('--host', action='store',
|
parser.add_argument('--host', action='store',
|
||||||
help='Get all information about an instance')
|
help='Get all information about an instance')
|
||||||
parser.add_argument('--pretty', action='store_true', default=False,
|
parser.add_argument('--pretty', action='store_true', default=False,
|
||||||
help='Pretty print JSON output(default: False)')
|
help='Pretty print JSON output(default: False)')
|
||||||
parser.add_argument('--profile', action='store',
|
parser.add_argument('--profile', action='store',
|
||||||
help='Azure profile contained in ~/.azure/credentials')
|
help='Azure profile contained in ~/.azure/credentials')
|
||||||
parser.add_argument('--subscription_id', action='store',
|
parser.add_argument('--subscription_id', action='store',
|
||||||
@@ -465,10 +520,12 @@ class AzureInventory(object):
|
|||||||
help='Azure Client Secret')
|
help='Azure Client Secret')
|
||||||
parser.add_argument('--tenant', action='store',
|
parser.add_argument('--tenant', action='store',
|
||||||
help='Azure Tenant Id')
|
help='Azure Tenant Id')
|
||||||
parser.add_argument('--ad-user', action='store',
|
parser.add_argument('--ad_user', action='store',
|
||||||
help='Active Directory User')
|
help='Active Directory User')
|
||||||
parser.add_argument('--password', action='store',
|
parser.add_argument('--password', action='store',
|
||||||
help='password')
|
help='password')
|
||||||
|
parser.add_argument('--cloud_environment', action='store',
|
||||||
|
help='Azure Cloud Environment name or metadata discovery URL')
|
||||||
parser.add_argument('--resource-groups', action='store',
|
parser.add_argument('--resource-groups', action='store',
|
||||||
help='Return inventory for comma separated list of resource group names')
|
help='Return inventory for comma separated list of resource group names')
|
||||||
parser.add_argument('--tags', action='store',
|
parser.add_argument('--tags', action='store',
|
||||||
@@ -486,8 +543,7 @@ class AzureInventory(object):
|
|||||||
try:
|
try:
|
||||||
virtual_machines = self._compute_client.virtual_machines.list(resource_group)
|
virtual_machines = self._compute_client.virtual_machines.list(resource_group)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group,
|
sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc)))
|
||||||
str(exc)))
|
|
||||||
if self._args.host or self.tags:
|
if self._args.host or self.tags:
|
||||||
selected_machines = self._selected_machines(virtual_machines)
|
selected_machines = self._selected_machines(virtual_machines)
|
||||||
self._load_machines(selected_machines)
|
self._load_machines(selected_machines)
|
||||||
@@ -510,7 +566,7 @@ class AzureInventory(object):
|
|||||||
for machine in machines:
|
for machine in machines:
|
||||||
id_dict = azure_id_to_dict(machine.id)
|
id_dict = azure_id_to_dict(machine.id)
|
||||||
|
|
||||||
#TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets
|
# TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets
|
||||||
# fixed, we should remove the .lower(). Opened Issue
|
# fixed, we should remove the .lower(). Opened Issue
|
||||||
# #574: https://github.com/Azure/azure-sdk-for-python/issues/574
|
# #574: https://github.com/Azure/azure-sdk-for-python/issues/574
|
||||||
resource_group = id_dict['resourceGroups'].lower()
|
resource_group = id_dict['resourceGroups'].lower()
|
||||||
@@ -538,7 +594,7 @@ class AzureInventory(object):
|
|||||||
mac_address=None,
|
mac_address=None,
|
||||||
plan=(machine.plan.name if machine.plan else None),
|
plan=(machine.plan.name if machine.plan else None),
|
||||||
virtual_machine_size=machine.hardware_profile.vm_size,
|
virtual_machine_size=machine.hardware_profile.vm_size,
|
||||||
computer_name=machine.os_profile.computer_name,
|
computer_name=(machine.os_profile.computer_name if machine.os_profile else None),
|
||||||
provisioning_state=machine.provisioning_state,
|
provisioning_state=machine.provisioning_state,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -559,7 +615,7 @@ class AzureInventory(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add windows details
|
# Add windows details
|
||||||
if machine.os_profile.windows_configuration is not None:
|
if machine.os_profile is not None and machine.os_profile.windows_configuration is not None:
|
||||||
host_vars['windows_auto_updates_enabled'] = \
|
host_vars['windows_auto_updates_enabled'] = \
|
||||||
machine.os_profile.windows_configuration.enable_automatic_updates
|
machine.os_profile.windows_configuration.enable_automatic_updates
|
||||||
host_vars['windows_timezone'] = machine.os_profile.windows_configuration.time_zone
|
host_vars['windows_timezone'] = machine.os_profile.windows_configuration.time_zone
|
||||||
@@ -790,13 +846,10 @@ class AzureInventory(object):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
if not HAS_AZURE:
|
if not HAS_AZURE:
|
||||||
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>=2.0.0rc5' --upgrade`) - {0}".format(HAS_AZURE_EXC))
|
sys.exit("The Azure python sdk is not installed (try `pip install 'azure>={0}' --upgrade`) - {1}".format(AZURE_MIN_VERSION, HAS_AZURE_EXC))
|
||||||
|
|
||||||
if Version(azure_compute_version) < Version(AZURE_MIN_VERSION):
|
|
||||||
sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} "
|
|
||||||
"Do you have Azure >= 2.0.0rc5 installed? (try `pip install 'azure>=2.0.0rc5' --upgrade`)".format(AZURE_MIN_VERSION, azure_compute_version))
|
|
||||||
|
|
||||||
AzureInventory()
|
AzureInventory()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -481,6 +481,9 @@ if is_testing():
|
|||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
},
|
},
|
||||||
|
'ephemeral': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
CACHES = {
|
CACHES = {
|
||||||
@@ -488,6 +491,9 @@ else:
|
|||||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||||
'LOCATION': 'memcached:11211',
|
'LOCATION': 'memcached:11211',
|
||||||
},
|
},
|
||||||
|
'ephemeral': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Social Auth configuration.
|
# Social Auth configuration.
|
||||||
@@ -716,7 +722,7 @@ VMWARE_GROUP_FILTER = r'^.+$'
|
|||||||
VMWARE_HOST_FILTER = r'^.+$'
|
VMWARE_HOST_FILTER = r'^.+$'
|
||||||
VMWARE_EXCLUDE_EMPTY_GROUPS = True
|
VMWARE_EXCLUDE_EMPTY_GROUPS = True
|
||||||
|
|
||||||
|
VMWARE_VALIDATE_CERTS = False
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
# -- Google Compute Engine --
|
# -- Google Compute Engine --
|
||||||
# ---------------------------
|
# ---------------------------
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export default ['i18n', function(i18n) {
|
|||||||
},
|
},
|
||||||
save: {
|
save: {
|
||||||
ngClick: 'vm.formSave()',
|
ngClick: 'vm.formSave()',
|
||||||
ngDisabled: "license_type !== 'enterprise' || form.$invalid || form.$pending"
|
ngDisabled: "license_type !== 'enterprise' && license_type !== 'open' || configuration_ldap_template_form.$invalid || configuration_ldap_template_form.$pending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default ['i18n', function(i18n) {
|
|||||||
},
|
},
|
||||||
save: {
|
save: {
|
||||||
ngClick: 'vm.formSave()',
|
ngClick: 'vm.formSave()',
|
||||||
ngDisabled: "license_type !== 'enterprise' || form.$invalid || form.$pending"
|
ngDisabled: "license_type !== 'enterprise' && license_type !== 'open' || configuration_radius_template_form.$invalid || configuration_radius_template_form.$pending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default ['i18n', function(i18n) {
|
|||||||
},
|
},
|
||||||
save: {
|
save: {
|
||||||
ngClick: 'vm.formSave()',
|
ngClick: 'vm.formSave()',
|
||||||
ngDisabled: "license_type !== 'enterprise' || form.$invalid || form.$pending"
|
ngDisabled: "license_type !== 'enterprise' && license_type !== 'open' || configuration_saml_template_form.$invalid || configuration_saml_template_form.$pending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export default ['i18n', function(i18n) {
|
|||||||
},
|
},
|
||||||
save: {
|
save: {
|
||||||
ngClick: 'vm.formSave()',
|
ngClick: 'vm.formSave()',
|
||||||
ngDisabled: "license_type !== 'enterprise' || form.$invalid || form.$pending"
|
ngDisabled: "license_type !== 'enterprise' && license_type !== 'open' || configuration_tacacs_template_form.$invalid || configuration_tacacs_template_form.$pending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,11 @@ export default [
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (key === "LICENSE") {
|
if (key === "LICENSE") {
|
||||||
$scope.license_type = data[key].license_type;
|
if (_.isEmpty(data[key])) {
|
||||||
|
$scope.license_type = "open";
|
||||||
|
} else {
|
||||||
|
$scope.license_type = data[key].license_type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//handle nested objects
|
//handle nested objects
|
||||||
if(ConfigurationUtils.isEmpty(data[key])) {
|
if(ConfigurationUtils.isEmpty(data[key])) {
|
||||||
|
|||||||
@@ -19,7 +19,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="Modal-body">
|
<div class="Modal-body">
|
||||||
<div>
|
<div>
|
||||||
<div class="Prompt-bodyQuery"><span translate>Are you sure you want to disassociate the host below from</span> {{disassociateGroup.name}}?</div>
|
<div class="Prompt-bodyQuery">
|
||||||
|
<span translate>Are you sure you want to disassociate the host below from</span> {{disassociateGroup.name}}?<br /><br />
|
||||||
|
<span translate>Note that only hosts directly in this group can be disassociated. Hosts in sub-groups must be disassociated directly from the sub-group level that they belong.</span>
|
||||||
|
</div>
|
||||||
<div class="Prompt-bodyTarget">{{ host.name }}</div>
|
<div class="Prompt-bodyTarget">{{ host.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="Modal-footer">
|
<div class="Modal-footer">
|
||||||
|
|||||||
@@ -81,9 +81,12 @@
|
|||||||
<label class="JobResults-resultRowLabel" translate>
|
<label class="JobResults-resultRowLabel" translate>
|
||||||
Explanation
|
Explanation
|
||||||
</label>
|
</label>
|
||||||
|
<div class="JobResults-resultRowText" ng-show="!previousTaskFailed">
|
||||||
|
{{ job.job_explanation }}
|
||||||
|
</div>
|
||||||
<div class="JobResults-resultRowText">
|
<div class="JobResults-resultRowText">
|
||||||
{{task_detail | limitTo:explanationLimit}}
|
{{task_detail | limitTo:explanationLimit}}
|
||||||
<span ng-show="explanationLimit && task_detail.length > explanationLimit">
|
<span ng-show="previousTaskFailed && explanationLimit && task_detail.length > explanationLimit">
|
||||||
<span>... </span>
|
<span>... </span>
|
||||||
<span class="JobResults-seeMoreLess" ng-click="explanationLimit=undefined">Show More</span>
|
<span class="JobResults-seeMoreLess" ng-click="explanationLimit=undefined">Show More</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -44,16 +44,7 @@ export default
|
|||||||
Rest.get()
|
Rest.get()
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
if(params.updateAllSources) {
|
if(params.updateAllSources) {
|
||||||
let userCanUpdateAllSources = true;
|
scope.$emit('StartTheUpdate', {});
|
||||||
_.forEach(data, function(inventory_source){
|
|
||||||
if (!inventory_source.can_update) {
|
|
||||||
userCanUpdateAllSources = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(userCanUpdateAllSources) {
|
|
||||||
scope.$emit('StartTheUpdate', {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
inventory_source = data;
|
inventory_source = data;
|
||||||
@@ -67,7 +58,7 @@ export default
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
Alert('Permission Denied', 'You do not have access to run the inventory sync. Please contact your system administrator.',
|
Alert('Error Launching Sync', 'Unable to execute the inventory sync. Please contact your system administrator.',
|
||||||
'alert-danger');
|
'alert-danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,9 +54,16 @@ export default ['$scope', '$rootScope', '$location', '$stateParams',
|
|||||||
.catch(({data, status}) => {
|
.catch(({data, status}) => {
|
||||||
ProcessErrors($scope, data, status, form, {
|
ProcessErrors($scope, data, status, form, {
|
||||||
hdr: 'Error!',
|
hdr: 'Error!',
|
||||||
msg: 'Failed to add new organization. Post returned status: ' + status
|
msg: 'Failed to save instance groups. POST returned status: ' + status
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(({data, status}) => {
|
||||||
|
let explanation = _.has(data, "name") ? data.name[0] : "";
|
||||||
|
ProcessErrors($scope, data, status, OrganizationForm, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: `Failed to save organization. PUT status: ${status}. ${explanation}`
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ angular.module('Utilities', ['RestServices', 'Utilities'])
|
|||||||
if (action) {
|
if (action) {
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
});
|
});
|
||||||
$('#alert-modal').on('shown.bs.modal', function() {
|
$('#alert-modal').on('shown.bs.modal', function() {
|
||||||
$('#alert_ok_btn').focus();
|
$('#alert_ok_btn').focus();
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ export default ['templateUrl', function(templateUrl) {
|
|||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
let list = $scope.list;
|
let list = $scope.list;
|
||||||
if($state.params.selected) {
|
if($state.params.selected) {
|
||||||
$scope.currentSelection = {
|
let selection = $scope[list.name].find(({id}) => id === parseInt($state.params.selected));
|
||||||
name: null,
|
$scope.currentSelection = _.pick(selection, 'id', 'name');
|
||||||
id: parseInt($state.params.selected)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
$scope.$watch(list.name, function(){
|
$scope.$watch(list.name, function(){
|
||||||
selectRowIfPresent();
|
selectRowIfPresent();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
.Paginate-total {
|
.Paginate-total {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
margin-bottom: -2px;
|
margin: 0 0 -2px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Paginate-filterLabel{
|
.Paginate-filterLabel{
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
.SmartSearch-searchTermContainer {
|
.SmartSearch-searchTermContainer {
|
||||||
flex: initial;
|
flex: initial;
|
||||||
width: 100%;
|
flex-grow: 1;
|
||||||
border: 1px solid @b7grey;
|
border: 1px solid @b7grey;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
color: @default-interface-txt;
|
color: @default-interface-txt;
|
||||||
border: 1px solid @b7grey;
|
border: 1px solid @b7grey;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 70px;
|
min-width: 70px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
@@ -240,15 +240,6 @@
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Additional modal specific styles
|
|
||||||
.modal-body, #add-permissions-modal,
|
|
||||||
.JobResults {
|
|
||||||
.SmartSearch-searchTermContainer {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.SmartSearch-bar {
|
.SmartSearch-bar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -161,10 +161,15 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
|
|||||||
terms = (terms) ? terms.trim() : "";
|
terms = (terms) ? terms.trim() : "";
|
||||||
|
|
||||||
if(terms && terms !== '') {
|
if(terms && terms !== '') {
|
||||||
// Split the terms up
|
let splitTerms;
|
||||||
let splitTerms = SmartSearchService.splitSearchIntoTerms(terms);
|
|
||||||
_.forEach(splitTerms, (term) => {
|
|
||||||
|
|
||||||
|
if ($scope.singleSearchParam === 'host_filter') {
|
||||||
|
splitTerms = SmartSearchService.splitFilterIntoTerms(terms);
|
||||||
|
} else {
|
||||||
|
splitTerms = SmartSearchService.splitSearchIntoTerms(terms);
|
||||||
|
}
|
||||||
|
|
||||||
|
_.forEach(splitTerms, (term) => {
|
||||||
let termParts = SmartSearchService.splitTermIntoParts(term);
|
let termParts = SmartSearchService.splitTermIntoParts(term);
|
||||||
|
|
||||||
function combineSameSearches(a,b){
|
function combineSameSearches(a,b){
|
||||||
|
|||||||
@@ -1,5 +1,45 @@
|
|||||||
export default [function() {
|
export default [function() {
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* For the Smart Host Filter, values with spaces are wrapped with double quotes on input.
|
||||||
|
* To avoid having these quoted values split up and treated as terms themselves, some
|
||||||
|
* work is done to encode quotes in quoted values and the spaces within those quoted
|
||||||
|
* values before calling to `splitSearchIntoTerms`.
|
||||||
|
*/
|
||||||
|
splitFilterIntoTerms (searchString) {
|
||||||
|
if (!searchString) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups = [];
|
||||||
|
let quoted;
|
||||||
|
|
||||||
|
searchString.split(' ').forEach(substring => {
|
||||||
|
if (/:"/g.test(substring)) {
|
||||||
|
if (/"$/.test(substring)) {
|
||||||
|
groups.push(this.encode(substring));
|
||||||
|
} else {
|
||||||
|
quoted = substring;
|
||||||
|
}
|
||||||
|
} else if (quoted) {
|
||||||
|
quoted += ` ${substring}`;
|
||||||
|
|
||||||
|
if (/"/g.test(substring)) {
|
||||||
|
groups.push(this.encode(quoted));
|
||||||
|
quoted = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
groups.push(substring);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.splitSearchIntoTerms(groups.join(' '));
|
||||||
|
},
|
||||||
|
encode (string) {
|
||||||
|
string = string.replace(/'/g, '%27');
|
||||||
|
|
||||||
|
return string.replace(/("| )/g, match => encodeURIComponent(match));
|
||||||
|
},
|
||||||
splitSearchIntoTerms(searchString) {
|
splitSearchIntoTerms(searchString) {
|
||||||
return searchString.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
return searchString.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default
|
|||||||
self.socket.onopen = function () {
|
self.socket.onopen = function () {
|
||||||
$log.debug("Websocket connection opened.");
|
$log.debug("Websocket connection opened.");
|
||||||
socketPromise.resolve();
|
socketPromise.resolve();
|
||||||
|
console.log('promise resolved, and readyState: '+ self.readyState);
|
||||||
self.checkStatus();
|
self.checkStatus();
|
||||||
if(needsResubscribing){
|
if(needsResubscribing){
|
||||||
self.subscribe(self.getLast());
|
self.subscribe(self.getLast());
|
||||||
@@ -116,6 +117,8 @@ export default
|
|||||||
disconnect: function(){
|
disconnect: function(){
|
||||||
if(this.socket){
|
if(this.socket){
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
|
delete this.socket;
|
||||||
|
console.log("Socket deleted: "+this.socket);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
subscribe: function(state){
|
subscribe: function(state){
|
||||||
@@ -186,14 +189,20 @@ export default
|
|||||||
var self = this;
|
var self = this;
|
||||||
$log.debug('Sent to Websocket Server: ' + data);
|
$log.debug('Sent to Websocket Server: ' + data);
|
||||||
socketPromise.promise.then(function(){
|
socketPromise.promise.then(function(){
|
||||||
self.socket.send(data, function () {
|
console.log("socket readyState at emit: " + self.socket.readyState);
|
||||||
var args = arguments;
|
// if(self.socket.readyState === 0){
|
||||||
self.scope.$apply(function () {
|
// self.subscribe(self.getLast());
|
||||||
if (callback) {
|
// }
|
||||||
callback.apply(self.socket, args);
|
if(self.socket.readyState === 1){
|
||||||
}
|
self.socket.send(data, function () {
|
||||||
|
var args = arguments;
|
||||||
|
self.scope.$apply(function () {
|
||||||
|
if (callback) {
|
||||||
|
callback.apply(self.socket, args);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addStateResolve: function(state, id){
|
addStateResolve: function(state, id){
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
$scope.mode = "add";
|
$scope.mode = "add";
|
||||||
$scope.parseType = 'yaml';
|
$scope.parseType = 'yaml';
|
||||||
$scope.credentialNotPresent = false;
|
$scope.credentialNotPresent = false;
|
||||||
|
$scope.canGetAllRelatedResources = true;
|
||||||
|
|
||||||
md5Setup({
|
md5Setup({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ export default
|
|||||||
'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit',
|
'Empty', 'Prompt', 'ToJSON', 'GetChoices', 'CallbackHelpInit',
|
||||||
'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
|
'InitiatePlaybookRun' , 'initSurvey', '$state', 'CreateSelect2',
|
||||||
'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', 'availableLabels',
|
'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', 'MultiCredentialService', 'availableLabels',
|
||||||
|
'canGetProject', 'canGetInventory', 'jobTemplateData', 'ParseVariableString',
|
||||||
function(
|
function(
|
||||||
$filter, $scope, $rootScope,
|
$filter, $scope, $rootScope,
|
||||||
$location, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
|
$location, $stateParams, JobTemplateForm, GenerateForm, Rest, Alert,
|
||||||
ProcessErrors, GetBasePath, md5Setup,
|
ProcessErrors, GetBasePath, md5Setup,
|
||||||
ParseTypeChange, Wait, selectedLabels, i18n,
|
ParseTypeChange, Wait, selectedLabels, i18n,
|
||||||
Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, InitiatePlaybookRun, SurveyControllerInit, $state,
|
Empty, Prompt, ToJSON, GetChoices, CallbackHelpInit, InitiatePlaybookRun, SurveyControllerInit, $state,
|
||||||
CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels
|
CreateSelect2, ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, MultiCredentialService, availableLabels,
|
||||||
|
canGetProject, canGetInventory, jobTemplateData, ParseVariableString
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) {
|
$scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) {
|
||||||
@@ -47,6 +49,7 @@ export default
|
|||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
CallbackHelpInit({ scope: $scope });
|
CallbackHelpInit({ scope: $scope });
|
||||||
|
|
||||||
$scope.playbook_options = null;
|
$scope.playbook_options = null;
|
||||||
$scope.playbook = null;
|
$scope.playbook = null;
|
||||||
$scope.mode = 'edit';
|
$scope.mode = 'edit';
|
||||||
@@ -253,7 +256,175 @@ export default
|
|||||||
$scope.rmoveLoadJobs();
|
$scope.rmoveLoadJobs();
|
||||||
}
|
}
|
||||||
$scope.removeLoadJobs = $scope.$on('LoadJobs', function() {
|
$scope.removeLoadJobs = $scope.$on('LoadJobs', function() {
|
||||||
$scope.fillJobTemplate();
|
$scope.job_template_obj = jobTemplateData;
|
||||||
|
$scope.name = jobTemplateData.name;
|
||||||
|
var fld, i;
|
||||||
|
for (fld in form.fields) {
|
||||||
|
if (fld !== 'variables' && fld !== 'survey' && fld !== 'forks' && jobTemplateData[fld] !== null && jobTemplateData[fld] !== undefined) {
|
||||||
|
if (form.fields[fld].type === 'select') {
|
||||||
|
if ($scope[fld + '_options'] && $scope[fld + '_options'].length > 0) {
|
||||||
|
for (i = 0; i < $scope[fld + '_options'].length; i++) {
|
||||||
|
if (jobTemplateData[fld] === $scope[fld + '_options'][i].value) {
|
||||||
|
$scope[fld] = $scope[fld + '_options'][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$scope[fld] = jobTemplateData[fld];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$scope[fld] = jobTemplateData[fld];
|
||||||
|
if(!Empty(jobTemplateData.summary_fields.survey)) {
|
||||||
|
$scope.survey_exists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
master[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
if (fld === 'forks') {
|
||||||
|
if (jobTemplateData[fld] !== 0) {
|
||||||
|
$scope[fld] = jobTemplateData[fld];
|
||||||
|
master[fld] = $scope[fld];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fld === 'variables') {
|
||||||
|
// Parse extra_vars, converting to YAML.
|
||||||
|
$scope.variables = ParseVariableString(jobTemplateData.extra_vars);
|
||||||
|
master.variables = $scope.variables;
|
||||||
|
}
|
||||||
|
if (form.fields[fld].type === 'lookup' && jobTemplateData.summary_fields[form.fields[fld].sourceModel]) {
|
||||||
|
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
jobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
||||||
|
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
||||||
|
$scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
|
||||||
|
}
|
||||||
|
if (form.fields[fld].type === 'checkbox_group') {
|
||||||
|
for(var j=0; j<form.fields[fld].fields.length; j++) {
|
||||||
|
$scope[form.fields[fld].fields[j].name] = jobTemplateData[form.fields[fld].fields[j].name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Wait('stop');
|
||||||
|
$scope.url = jobTemplateData.url;
|
||||||
|
|
||||||
|
$scope.survey_enabled = jobTemplateData.survey_enabled;
|
||||||
|
|
||||||
|
$scope.ask_variables_on_launch = (jobTemplateData.ask_variables_on_launch) ? true : false;
|
||||||
|
master.ask_variables_on_launch = $scope.ask_variables_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_verbosity_on_launch = (jobTemplateData.ask_verbosity_on_launch) ? true : false;
|
||||||
|
master.ask_verbosity_on_launch = $scope.ask_verbosity_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_limit_on_launch = (jobTemplateData.ask_limit_on_launch) ? true : false;
|
||||||
|
master.ask_limit_on_launch = $scope.ask_limit_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_tags_on_launch = (jobTemplateData.ask_tags_on_launch) ? true : false;
|
||||||
|
master.ask_tags_on_launch = $scope.ask_tags_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_skip_tags_on_launch = (jobTemplateData.ask_skip_tags_on_launch) ? true : false;
|
||||||
|
master.ask_skip_tags_on_launch = $scope.ask_skip_tags_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_diff_mode_on_launch = (jobTemplateData.ask_diff_mode_on_launch) ? true : false;
|
||||||
|
master.ask_diff_mode_on_launch = $scope.ask_diff_mode_on_launch;
|
||||||
|
|
||||||
|
$scope.job_tag_options = (jobTemplateData.job_tags) ? jobTemplateData.job_tags.split(',')
|
||||||
|
.map((i) => ({name: i, label: i, value: i})) : [];
|
||||||
|
$scope.job_tags = $scope.job_tag_options;
|
||||||
|
master.job_tags = $scope.job_tags;
|
||||||
|
|
||||||
|
$scope.skip_tag_options = (jobTemplateData.skip_tags) ? jobTemplateData.skip_tags.split(',')
|
||||||
|
.map((i) => ({name: i, label: i, value: i})) : [];
|
||||||
|
$scope.skip_tags = $scope.skip_tag_options;
|
||||||
|
master.skip_tags = $scope.skip_tags;
|
||||||
|
|
||||||
|
$scope.ask_job_type_on_launch = (jobTemplateData.ask_job_type_on_launch) ? true : false;
|
||||||
|
master.ask_job_type_on_launch = $scope.ask_job_type_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_inventory_on_launch = (jobTemplateData.ask_inventory_on_launch) ? true : false;
|
||||||
|
master.ask_inventory_on_launch = $scope.ask_inventory_on_launch;
|
||||||
|
|
||||||
|
$scope.ask_credential_on_launch = (jobTemplateData.ask_credential_on_launch) ? true : false;
|
||||||
|
master.ask_credential_on_launch = $scope.ask_credential_on_launch;
|
||||||
|
|
||||||
|
if (jobTemplateData.host_config_key) {
|
||||||
|
$scope.example_config_key = jobTemplateData.host_config_key;
|
||||||
|
}
|
||||||
|
$scope.example_template_id = id;
|
||||||
|
$scope.setCallbackHelp();
|
||||||
|
|
||||||
|
$scope.callback_url = $scope.callback_server_path + ((jobTemplateData.related.callback) ? jobTemplateData.related.callback :
|
||||||
|
GetBasePath('job_templates') + id + '/callback/');
|
||||||
|
master.callback_url = $scope.callback_url;
|
||||||
|
|
||||||
|
$scope.can_edit = jobTemplateData.summary_fields.user_capabilities.edit;
|
||||||
|
|
||||||
|
if($scope.job_template_obj.summary_fields.user_capabilities.edit) {
|
||||||
|
MultiCredentialService.loadCredentials(jobTemplateData)
|
||||||
|
.then(([selectedCredentials, credTypes, credTypeOptions,
|
||||||
|
credTags, credentialGetPermissionDenied]) => {
|
||||||
|
$scope.canGetAllRelatedResources = canGetProject && canGetInventory && !credentialGetPermissionDenied ? true : false;
|
||||||
|
$scope.selectedCredentials = selectedCredentials;
|
||||||
|
$scope.credential_types = credTypes;
|
||||||
|
$scope.credentialTypeOptions = credTypeOptions;
|
||||||
|
$scope.credentialsToPost = credTags;
|
||||||
|
$scope.$emit('jobTemplateLoaded', master);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (jobTemplateData.summary_fields.credential) {
|
||||||
|
$scope.selectedCredentials.machine = jobTemplateData.summary_fields.credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobTemplateData.summary_fields.vault_credential) {
|
||||||
|
$scope.selectedCredentials.vault = jobTemplateData.summary_fields.vault_credential;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jobTemplateData.summary_fields.extra_credentials) {
|
||||||
|
$scope.selectedCredentials.extra = jobTemplateData.summary_fields.extra_credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiCredentialService.getCredentialTypes()
|
||||||
|
.then(({credential_types, credentialTypeOptions}) => {
|
||||||
|
let typesArray = Object.keys(credential_types).map(key => credential_types[key]);
|
||||||
|
let credTypeOptions = credentialTypeOptions;
|
||||||
|
|
||||||
|
let machineAndVaultCreds = [],
|
||||||
|
extraCreds = [];
|
||||||
|
|
||||||
|
if($scope.selectedCredentials.machine) {
|
||||||
|
machineAndVaultCreds.push($scope.selectedCredentials.machine);
|
||||||
|
}
|
||||||
|
if($scope.selectedCredentials.vault) {
|
||||||
|
machineAndVaultCreds.push($scope.selectedCredentials.vault);
|
||||||
|
}
|
||||||
|
|
||||||
|
machineAndVaultCreds.map(cred => ({
|
||||||
|
name: cred.name,
|
||||||
|
id: cred.id,
|
||||||
|
postType: cred.postType,
|
||||||
|
kind: typesArray
|
||||||
|
.filter(type => {
|
||||||
|
return cred.kind === type.kind || parseInt(cred.credential_type) === type.value;
|
||||||
|
})[0].name + ":"
|
||||||
|
}));
|
||||||
|
|
||||||
|
if($scope.selectedCredentials.extra && $scope.selectedCredentials.extra.length > 0) {
|
||||||
|
extraCreds = extraCreds.concat($scope.selectedCredentials.extra).map(cred => ({
|
||||||
|
name: cred.name,
|
||||||
|
id: cred.id,
|
||||||
|
postType: cred.postType,
|
||||||
|
kind: credTypeOptions
|
||||||
|
.filter(type => {
|
||||||
|
return parseInt(cred.credential_type_id) === type.value;
|
||||||
|
})[0].name + ":"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.credentialsToPost = machineAndVaultCreds.concat(extraCreds);
|
||||||
|
|
||||||
|
$scope.$emit('jobTemplateLoaded', master);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ($scope.removeChoicesReady) {
|
if ($scope.removeChoicesReady) {
|
||||||
|
|||||||
@@ -2,14 +2,7 @@ export default
|
|||||||
function CallbackHelpInit($q, $location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors,
|
function CallbackHelpInit($q, $location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors,
|
||||||
ParseVariableString, Empty, Wait, MultiCredentialService, $rootScope) {
|
ParseVariableString, Empty, Wait, MultiCredentialService, $rootScope) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
var scope = params.scope,
|
var scope = params.scope;
|
||||||
defaultUrl = GetBasePath('job_templates'),
|
|
||||||
// generator = GenerateForm,
|
|
||||||
form = JobTemplateForm(),
|
|
||||||
// loadingFinishedCount = 0,
|
|
||||||
// base = $location.path().replace(/^\//, '').split('/')[0],
|
|
||||||
master = {},
|
|
||||||
id = $stateParams.job_template_id;
|
|
||||||
// checkSCMStatus, getPlaybooks, callback,
|
// checkSCMStatus, getPlaybooks, callback,
|
||||||
// choicesCount = 0;
|
// choicesCount = 0;
|
||||||
|
|
||||||
@@ -44,211 +37,6 @@ export default
|
|||||||
scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a';
|
scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a';
|
||||||
scope.example_template_id = 'N';
|
scope.example_template_id = 'N';
|
||||||
scope.setCallbackHelp();
|
scope.setCallbackHelp();
|
||||||
|
|
||||||
// this fills the job template form both on copy of the job template
|
|
||||||
// and on edit
|
|
||||||
scope.fillJobTemplate = function(){
|
|
||||||
// id = id || $rootScope.copy.id;
|
|
||||||
// Retrieve detail record and prepopulate the form
|
|
||||||
Rest.setUrl(defaultUrl + id);
|
|
||||||
Rest.get()
|
|
||||||
.success(function (data) {
|
|
||||||
scope.job_template_obj = data;
|
|
||||||
scope.name = data.name;
|
|
||||||
var fld, i;
|
|
||||||
for (fld in form.fields) {
|
|
||||||
if (fld !== 'variables' && fld !== 'survey' && fld !== 'forks' && data[fld] !== null && data[fld] !== undefined) {
|
|
||||||
if (form.fields[fld].type === 'select') {
|
|
||||||
if (scope[fld + '_options'] && scope[fld + '_options'].length > 0) {
|
|
||||||
for (i = 0; i < scope[fld + '_options'].length; i++) {
|
|
||||||
if (data[fld] === scope[fld + '_options'][i].value) {
|
|
||||||
scope[fld] = scope[fld + '_options'][i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scope[fld] = data[fld];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scope[fld] = data[fld];
|
|
||||||
if(!Empty(data.summary_fields.survey)) {
|
|
||||||
scope.survey_exists = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
master[fld] = scope[fld];
|
|
||||||
}
|
|
||||||
if (fld === 'forks') {
|
|
||||||
if (data[fld] !== 0) {
|
|
||||||
scope[fld] = data[fld];
|
|
||||||
master[fld] = scope[fld];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fld === 'variables') {
|
|
||||||
// Parse extra_vars, converting to YAML.
|
|
||||||
scope.variables = ParseVariableString(data.extra_vars);
|
|
||||||
master.variables = scope.variables;
|
|
||||||
}
|
|
||||||
if (form.fields[fld].type === 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
|
|
||||||
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
|
||||||
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
|
|
||||||
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
|
|
||||||
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
|
|
||||||
}
|
|
||||||
if (form.fields[fld].type === 'checkbox_group') {
|
|
||||||
for(var j=0; j<form.fields[fld].fields.length; j++) {
|
|
||||||
scope[form.fields[fld].fields[j].name] = data[form.fields[fld].fields[j].name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Wait('stop');
|
|
||||||
scope.url = data.url;
|
|
||||||
|
|
||||||
scope.survey_enabled = data.survey_enabled;
|
|
||||||
|
|
||||||
scope.ask_variables_on_launch = (data.ask_variables_on_launch) ? true : false;
|
|
||||||
master.ask_variables_on_launch = scope.ask_variables_on_launch;
|
|
||||||
|
|
||||||
scope.ask_verbosity_on_launch = (data.ask_verbosity_on_launch) ? true : false;
|
|
||||||
master.ask_verbosity_on_launch = scope.ask_verbosity_on_launch;
|
|
||||||
|
|
||||||
scope.ask_limit_on_launch = (data.ask_limit_on_launch) ? true : false;
|
|
||||||
master.ask_limit_on_launch = scope.ask_limit_on_launch;
|
|
||||||
|
|
||||||
scope.ask_tags_on_launch = (data.ask_tags_on_launch) ? true : false;
|
|
||||||
master.ask_tags_on_launch = scope.ask_tags_on_launch;
|
|
||||||
|
|
||||||
scope.ask_skip_tags_on_launch = (data.ask_skip_tags_on_launch) ? true : false;
|
|
||||||
master.ask_skip_tags_on_launch = scope.ask_skip_tags_on_launch;
|
|
||||||
|
|
||||||
scope.ask_diff_mode_on_launch = (data.ask_diff_mode_on_launch) ? true : false;
|
|
||||||
master.ask_diff_mode_on_launch = scope.ask_diff_mode_on_launch;
|
|
||||||
|
|
||||||
scope.job_tag_options = (data.job_tags) ? data.job_tags.split(',')
|
|
||||||
.map((i) => ({name: i, label: i, value: i})) : [];
|
|
||||||
scope.job_tags = scope.job_tag_options;
|
|
||||||
master.job_tags = scope.job_tags;
|
|
||||||
|
|
||||||
scope.skip_tag_options = (data.skip_tags) ? data.skip_tags.split(',')
|
|
||||||
.map((i) => ({name: i, label: i, value: i})) : [];
|
|
||||||
scope.skip_tags = scope.skip_tag_options;
|
|
||||||
master.skip_tags = scope.skip_tags;
|
|
||||||
|
|
||||||
scope.ask_job_type_on_launch = (data.ask_job_type_on_launch) ? true : false;
|
|
||||||
master.ask_job_type_on_launch = scope.ask_job_type_on_launch;
|
|
||||||
|
|
||||||
scope.ask_inventory_on_launch = (data.ask_inventory_on_launch) ? true : false;
|
|
||||||
master.ask_inventory_on_launch = scope.ask_inventory_on_launch;
|
|
||||||
|
|
||||||
scope.ask_credential_on_launch = (data.ask_credential_on_launch) ? true : false;
|
|
||||||
master.ask_credential_on_launch = scope.ask_credential_on_launch;
|
|
||||||
|
|
||||||
if (data.host_config_key) {
|
|
||||||
scope.example_config_key = data.host_config_key;
|
|
||||||
}
|
|
||||||
scope.example_template_id = id;
|
|
||||||
scope.setCallbackHelp();
|
|
||||||
|
|
||||||
scope.callback_url = scope.callback_server_path + ((data.related.callback) ? data.related.callback :
|
|
||||||
GetBasePath('job_templates') + id + '/callback/');
|
|
||||||
master.callback_url = scope.callback_url;
|
|
||||||
|
|
||||||
scope.can_edit = data.summary_fields.user_capabilities.edit;
|
|
||||||
|
|
||||||
if(scope.job_template_obj.summary_fields.user_capabilities.edit) {
|
|
||||||
MultiCredentialService.loadCredentials(data)
|
|
||||||
.then(([selectedCredentials, credTypes, credTypeOptions,
|
|
||||||
credTags]) => {
|
|
||||||
scope.selectedCredentials = selectedCredentials;
|
|
||||||
scope.credential_types = credTypes;
|
|
||||||
scope.credentialTypeOptions = credTypeOptions;
|
|
||||||
scope.credentialsToPost = credTags;
|
|
||||||
scope.$emit('jobTemplateLoaded', master);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
if (data.summary_fields.credential) {
|
|
||||||
scope.selectedCredentials.machine = data.summary_fields.credential;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.summary_fields.vault_credential) {
|
|
||||||
scope.selectedCredentials.vault = data.summary_fields.vault_credential;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extra credentials are not included in summary_fields so we have to go
|
|
||||||
// out and get them ourselves.
|
|
||||||
|
|
||||||
let defers = [],
|
|
||||||
typesArray = [],
|
|
||||||
credTypeOptions;
|
|
||||||
|
|
||||||
Rest.setUrl(data.related.extra_credentials);
|
|
||||||
defers.push(Rest.get()
|
|
||||||
.then((data) => {
|
|
||||||
scope.selectedCredentials.extra = data.data.results;
|
|
||||||
})
|
|
||||||
.catch(({data, status}) => {
|
|
||||||
ProcessErrors(null, data, status, null,
|
|
||||||
{
|
|
||||||
hdr: 'Error!',
|
|
||||||
msg: 'Failed to get extra credentials. ' +
|
|
||||||
'Get returned status: ' +
|
|
||||||
status
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
defers.push(MultiCredentialService.getCredentialTypes()
|
|
||||||
.then(({credential_types, credentialTypeOptions}) => {
|
|
||||||
typesArray = Object.keys(credential_types).map(key => credential_types[key]);
|
|
||||||
credTypeOptions = credentialTypeOptions;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return $q.all(defers).then(() => {
|
|
||||||
let machineAndVaultCreds = [],
|
|
||||||
extraCreds = [];
|
|
||||||
|
|
||||||
if(scope.selectedCredentials.machine) {
|
|
||||||
machineAndVaultCreds.push(scope.selectedCredentials.machine);
|
|
||||||
}
|
|
||||||
if(scope.selectedCredentials.vault) {
|
|
||||||
machineAndVaultCreds.push(scope.selectedCredentials.vault);
|
|
||||||
}
|
|
||||||
|
|
||||||
machineAndVaultCreds.map(cred => ({
|
|
||||||
name: cred.name,
|
|
||||||
id: cred.id,
|
|
||||||
postType: cred.postType,
|
|
||||||
kind: typesArray
|
|
||||||
.filter(type => {
|
|
||||||
return cred.kind === type.kind || parseInt(cred.credential_type) === type.value;
|
|
||||||
})[0].name + ":"
|
|
||||||
}));
|
|
||||||
|
|
||||||
extraCreds = extraCreds.concat(scope.selectedCredentials.extra).map(cred => ({
|
|
||||||
name: cred.name,
|
|
||||||
id: cred.id,
|
|
||||||
postType: cred.postType,
|
|
||||||
kind: credTypeOptions
|
|
||||||
.filter(type => {
|
|
||||||
return parseInt(cred.credential_type) === type.value;
|
|
||||||
})[0].name + ":"
|
|
||||||
}));
|
|
||||||
|
|
||||||
scope.credentialsToPost = machineAndVaultCreds.concat(extraCreds);
|
|
||||||
|
|
||||||
scope.$emit('jobTemplateLoaded', master);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.error(function (data, status) {
|
|
||||||
ProcessErrors(scope, data, status, form, {
|
|
||||||
hdr: 'Error!',
|
|
||||||
msg: 'Failed to retrieve job template: ' + $stateParams.id + '. GET status: ' + status
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function(NotificationsList, CompletedJobsList, i18n) {
|
|||||||
ngChange: 'job_template_form.inventory_name.$validate()',
|
ngChange: 'job_template_form.inventory_name.$validate()',
|
||||||
text: i18n._('Prompt on launch')
|
text: i18n._('Prompt on launch')
|
||||||
},
|
},
|
||||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources'
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
label: i18n._('Project'),
|
label: i18n._('Project'),
|
||||||
@@ -100,13 +100,14 @@ function(NotificationsList, CompletedJobsList, i18n) {
|
|||||||
dataTitle: i18n._('Project'),
|
dataTitle: i18n._('Project'),
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body",
|
dataContainer: "body",
|
||||||
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
|
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources',
|
||||||
|
awLookupWhen: 'canGetAllRelatedResources'
|
||||||
},
|
},
|
||||||
playbook: {
|
playbook: {
|
||||||
label: i18n._('Playbook'),
|
label: i18n._('Playbook'),
|
||||||
type:'select',
|
type:'select',
|
||||||
ngOptions: 'book for book in playbook_options track by book',
|
ngOptions: 'book for book in playbook_options track by book',
|
||||||
ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || disablePlaybookBecausePermissionDenied",
|
ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources",
|
||||||
id: 'playbook-select',
|
id: 'playbook-select',
|
||||||
required: true,
|
required: true,
|
||||||
column: 1,
|
column: 1,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
.then(kinds => {
|
.then(kinds => {
|
||||||
scope.credentialKinds = kinds;
|
scope.credentialKinds = kinds;
|
||||||
|
|
||||||
scope.credentialKind = "" + kinds.Machine;
|
scope.credentialKind = scope.selectedCredentials.machine && scope.selectedCredentials.machine.readOnly ? (scope.selectedCredentials.vault && scope.selectedCredentials.vault.readOnly ? "" + kinds.Network : "" + kinds.Vault) : "" + kinds.Machine;
|
||||||
|
|
||||||
scope.showModal = function() {
|
scope.showModal = function() {
|
||||||
$('#multi-credential-modal').modal('show');
|
$('#multi-credential-modal').modal('show');
|
||||||
@@ -50,6 +50,22 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
.then(({credential_types, credentialTypeOptions}) => {
|
.then(({credential_types, credentialTypeOptions}) => {
|
||||||
scope.credential_types = credential_types;
|
scope.credential_types = credential_types;
|
||||||
scope.credentialTypeOptions = credentialTypeOptions;
|
scope.credentialTypeOptions = credentialTypeOptions;
|
||||||
|
scope.allCredentialTypeOptions = _.cloneDeep(credentialTypeOptions);
|
||||||
|
|
||||||
|
// We want to hide machine and vault dropdown options if a credential
|
||||||
|
// has already been selected for those types and the user interacting
|
||||||
|
// with the form doesn't have the ability to change them
|
||||||
|
for(let i=scope.credentialTypeOptions.length - 1; i >=0; i--) {
|
||||||
|
if((scope.selectedCredentials.machine &&
|
||||||
|
scope.selectedCredentials.machine.credential_type_id === scope.credentialTypeOptions[i].value &&
|
||||||
|
scope.selectedCredentials.machine.readOnly) ||
|
||||||
|
(scope.selectedCredentials.vault &&
|
||||||
|
scope.selectedCredentials.vault.credential_type_id === scope.credentialTypeOptions[i].value &&
|
||||||
|
scope.selectedCredentials.vault.readOnly)) {
|
||||||
|
scope.credentialTypeOptions.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scope.$emit('multiCredentialModalLinked');
|
scope.$emit('multiCredentialModalLinked');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -70,7 +86,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
|
|
||||||
$scope.credTags = MultiCredentialService
|
$scope.credTags = MultiCredentialService
|
||||||
.updateCredentialTags($scope.selectedCredentials,
|
.updateCredentialTags($scope.selectedCredentials,
|
||||||
$scope.credentialTypeOptions);
|
$scope.allCredentialTypeOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
let updateMachineCredentialList = function() {
|
let updateMachineCredentialList = function() {
|
||||||
@@ -85,7 +101,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
|
|
||||||
$scope.credTags = MultiCredentialService
|
$scope.credTags = MultiCredentialService
|
||||||
.updateCredentialTags($scope.selectedCredentials,
|
.updateCredentialTags($scope.selectedCredentials,
|
||||||
$scope.credentialTypeOptions);
|
$scope.allCredentialTypeOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
let updateVaultCredentialList = function() {
|
let updateVaultCredentialList = function() {
|
||||||
@@ -100,7 +116,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
|
|
||||||
$scope.credTags = MultiCredentialService
|
$scope.credTags = MultiCredentialService
|
||||||
.updateCredentialTags($scope.selectedCredentials,
|
.updateCredentialTags($scope.selectedCredentials,
|
||||||
$scope.credentialTypeOptions);
|
$scope.allCredentialTypeOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
let uncheckAllCredentials = function() {
|
let uncheckAllCredentials = function() {
|
||||||
@@ -110,7 +126,7 @@ export default ['templateUrl', 'Rest', 'GetBasePath', 'generateList', '$compile'
|
|||||||
|
|
||||||
$scope.credTags = MultiCredentialService
|
$scope.credTags = MultiCredentialService
|
||||||
.updateCredentialTags($scope.selectedCredentials,
|
.updateCredentialTags($scope.selectedCredentials,
|
||||||
$scope.credentialTypeOptions);
|
$scope.allCredentialTypeOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
let init = function() {
|
let init = function() {
|
||||||
|
|||||||
@@ -27,13 +27,13 @@
|
|||||||
class="MultiCredential-tagContainer ng-scope"
|
class="MultiCredential-tagContainer ng-scope"
|
||||||
ng-repeat="tag in credTags track by $index">
|
ng-repeat="tag in credTags track by $index">
|
||||||
<div class="MultiCredential-deleteContainer"
|
<div class="MultiCredential-deleteContainer"
|
||||||
ng-click="removeCredential(tag.id)">
|
ng-click="removeCredential(tag.id)"
|
||||||
|
ng-if="!tag.readOnly">
|
||||||
<i class="fa fa-times
|
<i class="fa fa-times
|
||||||
MultiCredential-tagDelete">
|
MultiCredential-tagDelete">
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="MultiCredential-tag
|
<div class="MultiCredential-tag" ng-class="tag.readOnly ? 'MultiCredential-tag--disabled' : 'MultiCredential-tag--deletable'">
|
||||||
MultiCredential-tag--deletable">
|
|
||||||
<span
|
<span
|
||||||
class="MultiCredential-name--label
|
class="MultiCredential-name--label
|
||||||
ng-binding">
|
ng-binding">
|
||||||
|
|||||||
@@ -51,6 +51,10 @@
|
|||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MultiCredential-tag--disabled {
|
||||||
|
background-color: @default-icon;
|
||||||
|
}
|
||||||
|
|
||||||
.MultiCredential-tag--deletable {
|
.MultiCredential-tag--deletable {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
border-top-left-radius: 0px;
|
border-top-left-radius: 0px;
|
||||||
|
|||||||
@@ -23,12 +23,12 @@
|
|||||||
ng-repeat="tag in credentialsToPost track by $index">
|
ng-repeat="tag in credentialsToPost track by $index">
|
||||||
<div class="MultiCredential-deleteContainer"
|
<div class="MultiCredential-deleteContainer"
|
||||||
ng-click="removeCredential(tag.id)"
|
ng-click="removeCredential(tag.id)"
|
||||||
ng-hide="fieldIsDisabled">
|
ng-hide="fieldIsDisabled || tag.readOnly">
|
||||||
<i class="fa fa-times MultiCredential-tagDelete">
|
<i class="fa fa-times MultiCredential-tagDelete">
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="MultiCredential-tag"
|
<div class="MultiCredential-tag"
|
||||||
ng-class="{'MultiCredential-tag--deletable': !fieldIsDisabled}">
|
ng-class="{'MultiCredential-tag--deletable': !fieldIsDisabled && !tag.readOnly, 'MultiCredential-tag--disabled': tag.readOnly}">
|
||||||
<span class="MultiCredential-name--label
|
<span class="MultiCredential-name--label
|
||||||
ng-binding">
|
ng-binding">
|
||||||
{{ tag.kind }}
|
{{ tag.kind }}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
name: cred.name,
|
name: cred.name,
|
||||||
id: cred.id,
|
id: cred.id,
|
||||||
postType: cred.postType,
|
postType: cred.postType,
|
||||||
|
readOnly: cred.readOnly ? true : false,
|
||||||
kind: typeOpts
|
kind: typeOpts
|
||||||
.filter(type => {
|
.filter(type => {
|
||||||
return parseInt(cred.credential_type) === type.value;
|
return parseInt(cred.credential_type) === type.value;
|
||||||
@@ -178,6 +179,8 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
|
|
||||||
let credDefers = [];
|
let credDefers = [];
|
||||||
let job_template_obj = data;
|
let job_template_obj = data;
|
||||||
|
let credentialGetPermissionDenied = false;
|
||||||
|
|
||||||
// get machine credential
|
// get machine credential
|
||||||
if (data.related.credential) {
|
if (data.related.credential) {
|
||||||
Rest.setUrl(data.related.credential);
|
Rest.setUrl(data.related.credential);
|
||||||
@@ -188,8 +191,10 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
.catch(({data, status}) => {
|
.catch(({data, status}) => {
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
/* User doesn't have read access to the machine credential, so use summary_fields */
|
/* User doesn't have read access to the machine credential, so use summary_fields */
|
||||||
|
credentialGetPermissionDenied = true;
|
||||||
selectedCredentials.machine = job_template_obj.summary_fields.credential;
|
selectedCredentials.machine = job_template_obj.summary_fields.credential;
|
||||||
selectedCredentials.machine.credential_type = job_template_obj.summary_fields.credential.credential_type_id;
|
selectedCredentials.machine.credential_type = job_template_obj.summary_fields.credential.credential_type_id;
|
||||||
|
selectedCredentials.machine.readOnly = true;
|
||||||
} else {
|
} else {
|
||||||
ProcessErrors(
|
ProcessErrors(
|
||||||
null, data, status, null,
|
null, data, status, null,
|
||||||
@@ -212,8 +217,10 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
.catch(({data, status}) => {
|
.catch(({data, status}) => {
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
/* User doesn't have read access to the vault credential, so use summary_fields */
|
/* User doesn't have read access to the vault credential, so use summary_fields */
|
||||||
|
credentialGetPermissionDenied = true;
|
||||||
selectedCredentials.vault = job_template_obj.summary_fields.vault_credential;
|
selectedCredentials.vault = job_template_obj.summary_fields.vault_credential;
|
||||||
selectedCredentials.vault.credential_type = job_template_obj.summary_fields.vault_credential.credential_type_id;
|
selectedCredentials.vault.credential_type = job_template_obj.summary_fields.vault_credential.credential_type_id;
|
||||||
|
selectedCredentials.vault.readOnly = true;
|
||||||
} else {
|
} else {
|
||||||
ProcessErrors(
|
ProcessErrors(
|
||||||
null, data, status, null,
|
null, data, status, null,
|
||||||
@@ -237,9 +244,11 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
.catch(({data, status}) => {
|
.catch(({data, status}) => {
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
/* User doesn't have read access to the extra credentials, so use summary_fields */
|
/* User doesn't have read access to the extra credentials, so use summary_fields */
|
||||||
|
credentialGetPermissionDenied = true;
|
||||||
selectedCredentials.extra = job_template_obj.summary_fields.extra_credentials;
|
selectedCredentials.extra = job_template_obj.summary_fields.extra_credentials;
|
||||||
_.map(selectedCredentials.extra, (cred) => {
|
_.map(selectedCredentials.extra, (cred) => {
|
||||||
cred.credential_type = cred.credential_type_id;
|
cred.credential_type = cred.credential_type_id;
|
||||||
|
cred.readOnly = true;
|
||||||
return cred;
|
return cred;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -267,7 +276,7 @@ export default ['Rest', 'ProcessErrors', '$q', 'GetBasePath', function(Rest, Pro
|
|||||||
.updateCredentialTags(selectedCredentials, credTypeOptions);
|
.updateCredentialTags(selectedCredentials, credTypeOptions);
|
||||||
|
|
||||||
return [selectedCredentials, credTypes, credTypeOptions,
|
return [selectedCredentials, credTypes, credTypeOptions,
|
||||||
credTags];
|
credTags, credentialGetPermissionDenied];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,61 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
edit: {
|
edit: {
|
||||||
|
jobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors',
|
||||||
|
function($stateParams, TemplatesService, ProcessErrors) {
|
||||||
|
return TemplatesService.getJobTemplate($stateParams.job_template_id)
|
||||||
|
.then(function(res) {
|
||||||
|
return res.data;
|
||||||
|
}).catch(function(response){
|
||||||
|
ProcessErrors(null, response.data, response.status, null, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to get job template. GET returned status: ' +
|
||||||
|
response.status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}],
|
||||||
|
canGetProject: ['Rest', 'ProcessErrors', 'jobTemplateData',
|
||||||
|
function(Rest, ProcessErrors, jobTemplateData) {
|
||||||
|
Rest.setUrl(jobTemplateData.related.project);
|
||||||
|
return Rest.get()
|
||||||
|
.then(() => {
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(({data, status}) => {
|
||||||
|
if (status === 403) {
|
||||||
|
/* User doesn't have read access to the project, no problem. */
|
||||||
|
} else {
|
||||||
|
ProcessErrors(null, data, status, null, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to get project. GET returned ' +
|
||||||
|
'status: ' + status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}],
|
||||||
|
canGetInventory: ['Rest', 'ProcessErrors', 'jobTemplateData',
|
||||||
|
function(Rest, ProcessErrors, jobTemplateData) {
|
||||||
|
Rest.setUrl(jobTemplateData.related.inventory);
|
||||||
|
return Rest.get()
|
||||||
|
.then(() => {
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.catch(({data, status}) => {
|
||||||
|
if (status === 403) {
|
||||||
|
/* User doesn't have read access to the project, no problem. */
|
||||||
|
} else {
|
||||||
|
ProcessErrors(null, data, status, null, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to get project. GET returned ' +
|
||||||
|
'status: ' + status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}],
|
||||||
InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors',
|
InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors',
|
||||||
function($stateParams, Rest, GetBasePath, ProcessErrors){
|
function($stateParams, Rest, GetBasePath, ProcessErrors){
|
||||||
let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/instance_groups/`;
|
let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/instance_groups/`;
|
||||||
@@ -155,32 +210,32 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}],
|
}],
|
||||||
availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService',
|
availableLabels: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'TemplatesService',
|
||||||
function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) {
|
function(Rest, $stateParams, GetBasePath, ProcessErrors, TemplatesService) {
|
||||||
return TemplatesService.getAllLabelOptions()
|
return TemplatesService.getAllLabelOptions()
|
||||||
.then(function(labels){
|
.then(function(labels){
|
||||||
return labels;
|
return labels;
|
||||||
}).catch(function(response){
|
}).catch(function(response){
|
||||||
ProcessErrors(null, response.data, response.status, null, {
|
ProcessErrors(null, response.data, response.status, null, {
|
||||||
hdr: 'Error!',
|
hdr: 'Error!',
|
||||||
msg: 'Failed to get labels. GET returned status: ' +
|
msg: 'Failed to get labels. GET returned status: ' +
|
||||||
response.status
|
response.status
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}],
|
});
|
||||||
selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors',
|
}],
|
||||||
function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) {
|
selectedLabels: ['Rest', '$stateParams', 'GetBasePath', 'TemplatesService', 'ProcessErrors',
|
||||||
return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id)
|
function(Rest, $stateParams, GetBasePath, TemplatesService, ProcessErrors) {
|
||||||
.then(function(labels){
|
return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id)
|
||||||
return labels;
|
.then(function(labels){
|
||||||
}).catch(function(response){
|
return labels;
|
||||||
ProcessErrors(null, response.data, response.status, null, {
|
}).catch(function(response){
|
||||||
hdr: 'Error!',
|
ProcessErrors(null, response.data, response.status, null, {
|
||||||
msg: 'Failed to get workflow job template labels. GET returned status: ' +
|
hdr: 'Error!',
|
||||||
response.status
|
msg: 'Failed to get workflow job template labels. GET returned status: ' +
|
||||||
});
|
response.status
|
||||||
});
|
});
|
||||||
}]
|
});
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict'
|
'use strict';
|
||||||
|
|
||||||
describe('MultiCredentialService', () => {
|
describe('MultiCredentialService', () => {
|
||||||
let MultiCredentialService;
|
let MultiCredentialService;
|
||||||
@@ -79,7 +79,7 @@ describe('MultiCredentialService', () => {
|
|||||||
expect(equal).toBe(true);
|
expect(equal).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return array of selected credentials (populated)', () => {
|
it('should return array of selected credentials (populated, not read only)', () => {
|
||||||
let creds = {
|
let creds = {
|
||||||
machine: {
|
machine: {
|
||||||
credential_type: 1,
|
credential_type: 1,
|
||||||
@@ -120,18 +120,92 @@ describe('MultiCredentialService', () => {
|
|||||||
name: 'ssh',
|
name: 'ssh',
|
||||||
id: 3,
|
id: 3,
|
||||||
postType: 'machine',
|
postType: 'machine',
|
||||||
|
readOnly: false,
|
||||||
kind: 'SSH:'
|
kind: 'SSH:'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'aws',
|
name: 'aws',
|
||||||
id: 4,
|
id: 4,
|
||||||
postType: 'extra',
|
postType: 'extra',
|
||||||
|
readOnly: false,
|
||||||
kind: 'Amazon Web Services:'
|
kind: 'Amazon Web Services:'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'gce',
|
name: 'gce',
|
||||||
id: 5,
|
id: 5,
|
||||||
postType: 'extra',
|
postType: 'extra',
|
||||||
|
readOnly: false,
|
||||||
|
kind: 'Google Compute Engine:'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let actual = MultiCredentialService
|
||||||
|
.updateCredentialTags(creds, typeOpts);
|
||||||
|
|
||||||
|
let equal = _.isEqual(expected.sort(), actual.sort());
|
||||||
|
|
||||||
|
expect(equal).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return array of selected credentials (populated, read only)', () => {
|
||||||
|
let creds = {
|
||||||
|
machine: {
|
||||||
|
credential_type: 1,
|
||||||
|
id: 3,
|
||||||
|
name: 'ssh',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
extra: [
|
||||||
|
{
|
||||||
|
credential_type: 2,
|
||||||
|
id: 4,
|
||||||
|
name: 'aws',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
credential_type: 3,
|
||||||
|
id: 5,
|
||||||
|
name: 'gce',
|
||||||
|
readOnly: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let typeOpts = [
|
||||||
|
{
|
||||||
|
name: 'SSH',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Amazon Web Services',
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Compute Engine',
|
||||||
|
value: 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let expected = [
|
||||||
|
{
|
||||||
|
name: 'ssh',
|
||||||
|
id: 3,
|
||||||
|
postType: 'machine',
|
||||||
|
readOnly: true,
|
||||||
|
kind: 'SSH:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aws',
|
||||||
|
id: 4,
|
||||||
|
postType: 'extra',
|
||||||
|
readOnly: true,
|
||||||
|
kind: 'Amazon Web Services:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gce',
|
||||||
|
id: 5,
|
||||||
|
postType: 'extra',
|
||||||
|
readOnly: true,
|
||||||
kind: 'Google Compute Engine:'
|
kind: 'Google Compute Engine:'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -42,4 +42,22 @@ describe('Service: SmartSearch', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('fn splitFilterIntoTerms', () => {
|
||||||
|
it('should convert the filter term to a key and value with encode quotes and spaces', () => {
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms()).toEqual(null);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('foo')).toEqual(["foo"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('foo bar')).toEqual(["foo", "bar"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:foo bar')).toEqual(["name:foo", "bar"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:foo description:bar')).toEqual(["name:foo", "description:bar"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar"')).toEqual(["name:%22foo%20bar%22"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:%22foo%20bar%22", "description:%22bar%20foo%22"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar" a b c')).toEqual(["name:%22foo%20bar%22", 'a', 'b', 'c']);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"1"')).toEqual(["name:%221%22"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:1')).toEqual(["name:1"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foo ba\'r" a b c')).toEqual(["name:%22foo%20ba%27r%22", 'a', 'b', 'c']);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foobar" other:"barbaz"')).toEqual(["name:%22foobar%22", "other:%22barbaz%22"]);
|
||||||
|
expect(SmartSearchService.splitFilterIntoTerms('name:"foobar" other:"bar baz"')).toEqual(["name:%22foobar%22", "other:%22bar%20baz%22"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
apache-libcloud==2.0.0
|
apache-libcloud==2.0.0
|
||||||
azure==2.0.0rc6
|
# azure deps from https://github.com/ansible/ansible/blob/fe1153c0afa1ffd648147af97454e900560b3532/packaging/requirements/requirements-azure.txt
|
||||||
|
azure-mgmt-compute>=2.0.0,<3
|
||||||
|
azure-mgmt-network>=1.3.0,<2
|
||||||
|
azure-mgmt-storage>=1.2.0,<2
|
||||||
|
azure-mgmt-resource>=1.1.0,<2
|
||||||
|
azure-storage>=0.35.1,<0.36
|
||||||
|
azure-cli-core>=2.0.12,<3
|
||||||
|
msrestazure>=0.4.11,<0.5
|
||||||
|
azure-mgmt-dns>=1.0.1,<2
|
||||||
|
azure-mgmt-keyvault>=0.40.0,<0.41
|
||||||
|
azure-mgmt-batch>=4.1.0,<5
|
||||||
|
azure-mgmt-sql>=0.7.1,<0.8
|
||||||
|
azure-mgmt-web>=0.32.0,<0.33
|
||||||
|
azure-mgmt-containerservice>=1.0.0
|
||||||
backports.ssl-match-hostname==3.5.0.1
|
backports.ssl-match-hostname==3.5.0.1
|
||||||
kombu==3.0.37
|
kombu==3.0.37
|
||||||
boto==2.46.1
|
boto==2.46.1
|
||||||
|
|||||||
@@ -4,30 +4,30 @@
|
|||||||
#
|
#
|
||||||
# pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in
|
# pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in
|
||||||
#
|
#
|
||||||
adal==0.4.5 # via msrestazure
|
adal==0.4.7 # via azure-cli-core, msrestazure
|
||||||
amqp==1.4.9 # via kombu
|
amqp==1.4.9 # via kombu
|
||||||
anyjson==0.3.3 # via kombu
|
anyjson==0.3.3 # via kombu
|
||||||
apache-libcloud==2.0.0
|
apache-libcloud==2.0.0
|
||||||
appdirs==1.4.3 # via os-client-config, python-ironicclient, setuptools
|
appdirs==1.4.3 # via os-client-config, python-ironicclient, setuptools
|
||||||
|
applicationinsights==0.11.0 # via azure-cli-core
|
||||||
|
argcomplete==1.9.2 # via azure-cli-core
|
||||||
asn1crypto==0.22.0 # via cryptography
|
asn1crypto==0.22.0 # via cryptography
|
||||||
azure-batch==1.0.0 # via azure
|
azure-cli-core==2.0.15
|
||||||
azure-common[autorest]==1.1.4 # via azure-batch, azure-mgmt-batch, azure-mgmt-compute, azure-mgmt-keyvault, azure-mgmt-logic, azure-mgmt-network, azure-mgmt-redis, azure-mgmt-resource, azure-mgmt-scheduler, azure-mgmt-storage, azure-servicebus, azure-servicemanagement-legacy, azure-storage
|
azure-cli-nspkg==3.0.1 # via azure-cli-core
|
||||||
azure-mgmt-batch==1.0.0 # via azure-mgmt
|
azure-common==1.1.8 # via azure-mgmt-batch, azure-mgmt-compute, azure-mgmt-containerservice, azure-mgmt-dns, azure-mgmt-keyvault, azu
|
||||||
azure-mgmt-compute==0.30.0rc6 # via azure-mgmt
|
azure-mgmt-batch==4.1.0
|
||||||
azure-mgmt-keyvault==0.30.0rc6 # via azure-mgmt
|
azure-mgmt-compute==2.1.0
|
||||||
azure-mgmt-logic==1.0.0 # via azure-mgmt
|
azure-mgmt-containerservice==1.0.0
|
||||||
azure-mgmt-network==0.30.0rc6 # via azure-mgmt
|
azure-mgmt-dns==1.0.1
|
||||||
azure-mgmt-nspkg==2.0.0 # via azure-batch, azure-mgmt-batch, azure-mgmt-compute, azure-mgmt-keyvault, azure-mgmt-logic, azure-mgmt-network, azure-mgmt-redis, azure-mgmt-resource, azure-mgmt-scheduler, azure-mgmt-storage
|
azure-mgmt-keyvault==0.40.0
|
||||||
azure-mgmt-redis==1.0.0 # via azure-mgmt
|
azure-mgmt-network==1.4.0
|
||||||
azure-mgmt-resource==0.30.0rc6 # via azure-mgmt
|
azure-mgmt-nspkg==2.0.0 # via azure-mgmt-batch, azure-mgmt-compute, azure-mgmt-containerservice, azure-mgmt-dns, azure-mgmt-keyvault, azu
|
||||||
azure-mgmt-scheduler==1.0.0 # via azure-mgmt
|
azure-mgmt-resource==1.1.0
|
||||||
azure-mgmt-storage==0.30.0rc6 # via azure-mgmt
|
azure-mgmt-sql==0.7.1
|
||||||
azure-mgmt==0.30.0rc6 # via azure
|
azure-mgmt-storage==1.2.1
|
||||||
azure-nspkg==2.0.0 # via azure-common, azure-mgmt-nspkg, azure-storage
|
azure-mgmt-web==0.32.0
|
||||||
azure-servicebus==0.20.3 # via azure
|
azure-nspkg==2.0.0 # via azure-cli-nspkg, azure-common, azure-mgmt-nspkg, azure-storage
|
||||||
azure-servicemanagement-legacy==0.20.4 # via azure
|
azure-storage==0.35.1
|
||||||
azure-storage==0.33.0 # via azure
|
|
||||||
azure==2.0.0rc6
|
|
||||||
babel==2.3.4 # via osc-lib, oslo.i18n, python-cinderclient, python-glanceclient, python-neutronclient, python-novaclient, python-openstackclient
|
babel==2.3.4 # via osc-lib, oslo.i18n, python-cinderclient, python-glanceclient, python-neutronclient, python-novaclient, python-openstackclient
|
||||||
backports.ssl-match-hostname==3.5.0.1
|
backports.ssl-match-hostname==3.5.0.1
|
||||||
boto3==1.4.4
|
boto3==1.4.4
|
||||||
@@ -37,7 +37,8 @@ certifi==2017.4.17 # via msrest
|
|||||||
cffi==1.10.0 # via cryptography
|
cffi==1.10.0 # via cryptography
|
||||||
cliff==2.7.0 # via osc-lib, python-designateclient, python-neutronclient, python-openstackclient
|
cliff==2.7.0 # via osc-lib, python-designateclient, python-neutronclient, python-openstackclient
|
||||||
cmd2==0.7.2 # via cliff
|
cmd2==0.7.2 # via cliff
|
||||||
cryptography==1.9 # via adal, azure-storage, pyopenssl, secretstorage
|
colorama==0.3.9 # via azure-cli-core
|
||||||
|
cryptography==2.0.3 # via adal, azure-storage, paramiko, pyopenssl, secretstorage
|
||||||
debtcollector==1.15.0 # via oslo.config, oslo.utils, python-designateclient, python-keystoneclient, python-neutronclient
|
debtcollector==1.15.0 # via oslo.config, oslo.utils, python-designateclient, python-keystoneclient, python-neutronclient
|
||||||
decorator==4.0.11 # via shade
|
decorator==4.0.11 # via shade
|
||||||
deprecation==1.0.1 # via openstacksdk
|
deprecation==1.0.1 # via openstacksdk
|
||||||
@@ -47,6 +48,7 @@ enum34==1.1.6 # via cryptography, msrest
|
|||||||
funcsigs==1.0.2 # via debtcollector, oslo.utils
|
funcsigs==1.0.2 # via debtcollector, oslo.utils
|
||||||
functools32==3.2.3.post2 # via jsonschema
|
functools32==3.2.3.post2 # via jsonschema
|
||||||
futures==3.1.1 # via azure-storage, s3transfer, shade
|
futures==3.1.1 # via azure-storage, s3transfer, shade
|
||||||
|
humanfriendly==4.4.1 # via azure-cli-core
|
||||||
idna==2.5 # via cryptography
|
idna==2.5 # via cryptography
|
||||||
ipaddress==1.0.18 # via cryptography, shade
|
ipaddress==1.0.18 # via cryptography, shade
|
||||||
iso8601==0.1.11 # via keystoneauth1, oslo.utils, python-neutronclient, python-novaclient
|
iso8601==0.1.11 # via keystoneauth1, oslo.utils, python-neutronclient, python-novaclient
|
||||||
@@ -61,8 +63,8 @@ kombu==3.0.37
|
|||||||
lxml==3.8.0 # via pyvmomi
|
lxml==3.8.0 # via pyvmomi
|
||||||
monotonic==1.3 # via oslo.utils
|
monotonic==1.3 # via oslo.utils
|
||||||
msgpack-python==0.4.8 # via oslo.serialization
|
msgpack-python==0.4.8 # via oslo.serialization
|
||||||
msrest==0.4.10 # via azure-common, msrestazure
|
msrest==0.4.14 # via azure-cli-core, msrestazure
|
||||||
msrestazure==0.4.9 # via azure-common
|
msrestazure==0.4.13
|
||||||
munch==2.1.1 # via shade
|
munch==2.1.1 # via shade
|
||||||
netaddr==0.7.19 # via oslo.config, oslo.utils, python-neutronclient
|
netaddr==0.7.19 # via oslo.config, oslo.utils, python-neutronclient
|
||||||
netifaces==0.10.6 # via oslo.utils, shade
|
netifaces==0.10.6 # via oslo.utils, shade
|
||||||
@@ -83,9 +85,10 @@ prettytable==0.7.2 # via cliff, python-cinderclient, python-glanceclient,
|
|||||||
psphere==0.5.2
|
psphere==0.5.2
|
||||||
psutil==5.2.2
|
psutil==5.2.2
|
||||||
pycparser==2.17 # via cffi
|
pycparser==2.17 # via cffi
|
||||||
pyjwt==1.5.0 # via adal
|
pygments==2.2.0 # via azure-cli-core
|
||||||
|
pyjwt==1.5.2 # via adal, azure-cli-core
|
||||||
pykerberos==1.1.14 # via requests-kerberos
|
pykerberos==1.1.14 # via requests-kerberos
|
||||||
pyopenssl==17.0.0 # via pyvmomi
|
pyopenssl==17.2.0 # via azure-cli-core, python-glanceclient, pyvmomi
|
||||||
pyparsing==2.2.0 # via cliff, cmd2, oslo.utils, packaging
|
pyparsing==2.2.0 # via cliff, cmd2, oslo.utils, packaging
|
||||||
python-cinderclient==2.2.0 # via python-openstackclient, shade
|
python-cinderclient==2.2.0 # via python-openstackclient, shade
|
||||||
python-dateutil==2.6.0 # via adal, azure-storage, botocore
|
python-dateutil==2.6.0 # via adal, azure-storage, botocore
|
||||||
@@ -114,6 +117,7 @@ simplejson==3.11.1 # via osc-lib, python-cinderclient, python-neutronclie
|
|||||||
six==1.10.0 # via cliff, cmd2, cryptography, debtcollector, keystoneauth1, munch, ntlm-auth, openstacksdk, osc-lib, oslo.config, oslo.i18n, oslo.serialization, oslo.utils, packaging, pyopenssl, python-cinderclient, python-dateutil, python-designateclient, python-glanceclient, python-ironicclient, python-keystoneclient, python-memcached, python-neutronclient, python-novaclient, python-openstackclient, pyvmomi, pywinrm, setuptools, shade, stevedore, warlock
|
six==1.10.0 # via cliff, cmd2, cryptography, debtcollector, keystoneauth1, munch, ntlm-auth, openstacksdk, osc-lib, oslo.config, oslo.i18n, oslo.serialization, oslo.utils, packaging, pyopenssl, python-cinderclient, python-dateutil, python-designateclient, python-glanceclient, python-ironicclient, python-keystoneclient, python-memcached, python-neutronclient, python-novaclient, python-openstackclient, pyvmomi, pywinrm, setuptools, shade, stevedore, warlock
|
||||||
stevedore==1.23.0 # via cliff, keystoneauth1, openstacksdk, osc-lib, oslo.config, python-designateclient, python-keystoneclient
|
stevedore==1.23.0 # via cliff, keystoneauth1, openstacksdk, osc-lib, oslo.config, python-designateclient, python-keystoneclient
|
||||||
suds==0.4 # via psphere
|
suds==0.4 # via psphere
|
||||||
|
tabulate==0.7.7 # via azure-cli-core
|
||||||
unicodecsv==0.14.1 # via cliff
|
unicodecsv==0.14.1 # via cliff
|
||||||
warlock==1.2.0 # via python-glanceclient
|
warlock==1.2.0 # via python-glanceclient
|
||||||
wrapt==1.10.10 # via debtcollector, positional, python-glanceclient
|
wrapt==1.10.10 # via debtcollector, positional, python-glanceclient
|
||||||
|
|||||||
Reference in New Issue
Block a user