mirror of
https://github.com/ansible/awx.git
synced 2026-05-03 07:35:28 -02:30
Merge branch 'devel' of github.com:ansible/ansible-tower into devel
This commit is contained in:
25
Makefile
25
Makefile
@@ -249,7 +249,19 @@ rebase:
|
|||||||
push:
|
push:
|
||||||
git push origin master
|
git push origin master
|
||||||
|
|
||||||
virtualenv:
|
virtualenv: virtualenv_ansible virtualenv_tower
|
||||||
|
|
||||||
|
virtualenv_ansible:
|
||||||
|
if [ "$(VENV_BASE)" ]; then \
|
||||||
|
if [ ! -d "$(VENV_BASE)" ]; then \
|
||||||
|
mkdir $(VENV_BASE); \
|
||||||
|
fi; \
|
||||||
|
if [ ! -d "$(VENV_BASE)/ansible" ]; then \
|
||||||
|
virtualenv --system-site-packages $(VENV_BASE)/ansible; \
|
||||||
|
fi; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
virtualenv_tower:
|
||||||
if [ "$(VENV_BASE)" ]; then \
|
if [ "$(VENV_BASE)" ]; then \
|
||||||
if [ ! -d "$(VENV_BASE)" ]; then \
|
if [ ! -d "$(VENV_BASE)" ]; then \
|
||||||
mkdir $(VENV_BASE); \
|
mkdir $(VENV_BASE); \
|
||||||
@@ -257,12 +269,9 @@ virtualenv:
|
|||||||
if [ ! -d "$(VENV_BASE)/tower" ]; then \
|
if [ ! -d "$(VENV_BASE)/tower" ]; then \
|
||||||
virtualenv --system-site-packages $(VENV_BASE)/tower; \
|
virtualenv --system-site-packages $(VENV_BASE)/tower; \
|
||||||
fi; \
|
fi; \
|
||||||
if [ ! -d "$(VENV_BASE)/ansible" ]; then \
|
|
||||||
virtualenv --system-site-packages $(VENV_BASE)/ansible; \
|
|
||||||
fi; \
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
requirements_ansible:
|
requirements_ansible: virtualenv_ansible
|
||||||
if [ "$(VENV_BASE)" ]; then \
|
if [ "$(VENV_BASE)" ]; then \
|
||||||
. $(VENV_BASE)/ansible/bin/activate; \
|
. $(VENV_BASE)/ansible/bin/activate; \
|
||||||
$(VENV_BASE)/ansible/bin/pip install -U pip==8.1.1; \
|
$(VENV_BASE)/ansible/bin/pip install -U pip==8.1.1; \
|
||||||
@@ -273,7 +282,7 @@ requirements_ansible:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Install third-party requirements needed for Tower's environment.
|
# Install third-party requirements needed for Tower's environment.
|
||||||
requirements_tower:
|
requirements_tower: virtualenv_tower
|
||||||
if [ "$(VENV_BASE)" ]; then \
|
if [ "$(VENV_BASE)" ]; then \
|
||||||
. $(VENV_BASE)/tower/bin/activate; \
|
. $(VENV_BASE)/tower/bin/activate; \
|
||||||
$(VENV_BASE)/tower/bin/pip install -U pip==8.1.1; \
|
$(VENV_BASE)/tower/bin/pip install -U pip==8.1.1; \
|
||||||
@@ -299,7 +308,7 @@ requirements_jenkins:
|
|||||||
fi && \
|
fi && \
|
||||||
$(NPM_BIN) install csslint jshint
|
$(NPM_BIN) install csslint jshint
|
||||||
|
|
||||||
requirements: virtualenv requirements_ansible requirements_tower
|
requirements: requirements_ansible requirements_tower
|
||||||
|
|
||||||
requirements_dev: requirements requirements_tower_dev
|
requirements_dev: requirements requirements_tower_dev
|
||||||
|
|
||||||
@@ -640,7 +649,7 @@ tar-build/$(SETUP_TAR_FILE):
|
|||||||
@cp -a setup tar-build/$(SETUP_TAR_NAME)
|
@cp -a setup tar-build/$(SETUP_TAR_NAME)
|
||||||
@rsync -az docs/licenses tar-build/$(SETUP_TAR_NAME)/
|
@rsync -az docs/licenses tar-build/$(SETUP_TAR_NAME)/
|
||||||
@cd tar-build/$(SETUP_TAR_NAME) && sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%RELEASE%#$(RELEASE)#;' group_vars/all.in > group_vars/all
|
@cd tar-build/$(SETUP_TAR_NAME) && sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%RELEASE%#$(RELEASE)#;' group_vars/all.in > group_vars/all
|
||||||
@cd tar-build && tar -czf $(SETUP_TAR_FILE) --exclude "*/all.in" $(SETUP_TAR_NAME)/
|
@cd tar-build && tar -czf $(SETUP_TAR_FILE) --exclude "*/all.in" --exclude "**/test/*" $(SETUP_TAR_NAME)/
|
||||||
@ln -sf $(SETUP_TAR_FILE) tar-build/$(SETUP_TAR_LINK)
|
@ln -sf $(SETUP_TAR_FILE) tar-build/$(SETUP_TAR_LINK)
|
||||||
|
|
||||||
tar-build/$(SETUP_TAR_CHECKSUM):
|
tar-build/$(SETUP_TAR_CHECKSUM):
|
||||||
|
|||||||
@@ -131,6 +131,8 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
value = to_python_boolean(value)
|
value = to_python_boolean(value)
|
||||||
elif new_lookup.endswith('__in'):
|
elif new_lookup.endswith('__in'):
|
||||||
items = []
|
items = []
|
||||||
|
if not value:
|
||||||
|
raise ValueError('cannot provide empty value for __in')
|
||||||
for item in value.split(','):
|
for item in value.split(','):
|
||||||
items.append(self.value_to_python_for_field(field, item))
|
items.append(self.value_to_python_for_field(field, item))
|
||||||
value = items
|
value = items
|
||||||
@@ -218,7 +220,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
q = Q(**{k:v})
|
q = Q(**{k:v})
|
||||||
queryset = queryset.filter(q)
|
queryset = queryset.filter(q)
|
||||||
queryset = queryset.filter(*args)
|
queryset = queryset.filter(*args)
|
||||||
return queryset.distinct()
|
return queryset
|
||||||
except (FieldError, FieldDoesNotExist, ValueError), e:
|
except (FieldError, FieldDoesNotExist, ValueError), e:
|
||||||
raise ParseError(e.args[0])
|
raise ParseError(e.args[0])
|
||||||
except ValidationError, e:
|
except ValidationError, e:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from rest_framework import views
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
|
from awx.main.models import Label
|
||||||
from awx.main.utils import * # noqa
|
from awx.main.utils import * # noqa
|
||||||
from awx.api.serializers import ResourceAccessListElementSerializer
|
from awx.api.serializers import ResourceAccessListElementSerializer
|
||||||
|
|
||||||
@@ -35,7 +36,8 @@ __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
|
|||||||
'RetrieveUpdateDestroyAPIView', 'DestroyAPIView',
|
'RetrieveUpdateDestroyAPIView', 'DestroyAPIView',
|
||||||
'SubDetailAPIView',
|
'SubDetailAPIView',
|
||||||
'ResourceAccessList',
|
'ResourceAccessList',
|
||||||
'ParentMixin',]
|
'ParentMixin',
|
||||||
|
'DeleteLastUnattachLabelMixin',]
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.generics')
|
logger = logging.getLogger('awx.api.generics')
|
||||||
|
|
||||||
@@ -399,12 +401,15 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
else:
|
else:
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def unattach(self, request, *args, **kwargs):
|
def unattach_validate(self, request):
|
||||||
sub_id = request.data.get('id', None)
|
sub_id = request.data.get('id', None)
|
||||||
|
res = None
|
||||||
if not sub_id:
|
if not sub_id:
|
||||||
data = dict(msg='"id" is required to disassociate')
|
data = dict(msg='"id" is required to disassociate')
|
||||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
res = Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
return (sub_id, res)
|
||||||
|
|
||||||
|
def unattach_by_id(self, request, sub_id):
|
||||||
parent = self.get_parent_object()
|
parent = self.get_parent_object()
|
||||||
parent_key = getattr(self, 'parent_key', None)
|
parent_key = getattr(self, 'parent_key', None)
|
||||||
relationship = getattrd(parent, self.relationship)
|
relationship = getattrd(parent, self.relationship)
|
||||||
@@ -421,6 +426,12 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
|
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def unattach(self, request, *args, **kwargs):
|
||||||
|
(sub_id, res) = self.unattach_validate(request)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
return self.unattach_by_id(request, sub_id)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if not isinstance(request.data, dict):
|
if not isinstance(request.data, dict):
|
||||||
return Response('invalid type for post data',
|
return Response('invalid type for post data',
|
||||||
@@ -430,6 +441,21 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
else:
|
else:
|
||||||
return self.attach(request, *args, **kwargs)
|
return self.attach(request, *args, **kwargs)
|
||||||
|
|
||||||
|
class DeleteLastUnattachLabelMixin(object):
|
||||||
|
def unattach(self, request, *args, **kwargs):
|
||||||
|
(sub_id, res) = super(DeleteLastUnattachLabelMixin, self).unattach_validate(request, *args, **kwargs)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
|
||||||
|
res = super(DeleteLastUnattachLabelMixin, self).unattach_by_id(request, sub_id)
|
||||||
|
|
||||||
|
label = Label.objects.get(id=sub_id)
|
||||||
|
|
||||||
|
if label.is_detached():
|
||||||
|
label.delete()
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
class SubDetailAPIView(generics.RetrieveAPIView, GenericAPIView, ParentMixin):
|
class SubDetailAPIView(generics.RetrieveAPIView, GenericAPIView, ParentMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -474,7 +500,7 @@ class ResourceAccessList(ListAPIView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.object_id = self.kwargs['pk']
|
self.object_id = self.kwargs['pk']
|
||||||
resource_model = getattr(self, 'resource_model')
|
resource_model = getattr(self, 'resource_model')
|
||||||
obj = resource_model.objects.get(pk=self.object_id)
|
obj = get_object_or_404(resource_model, pk=self.object_id)
|
||||||
|
|
||||||
content_type = ContentType.objects.get_for_model(obj)
|
content_type = ContentType.objects.get_for_model(obj)
|
||||||
roles = set(Role.objects.filter(content_type=content_type, object_id=obj.id))
|
roles = set(Role.objects.filter(content_type=content_type, object_id=obj.id))
|
||||||
@@ -483,4 +509,3 @@ class ResourceAccessList(ListAPIView):
|
|||||||
for r in roles:
|
for r in roles:
|
||||||
ancestors.update(set(r.ancestors.all()))
|
ancestors.update(set(r.ancestors.all()))
|
||||||
return User.objects.filter(roles__in=list(ancestors))
|
return User.objects.filter(roles__in=list(ancestors))
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from polymorphic import PolymorphicModel
|
|||||||
from awx.main.constants import SCHEDULEABLE_PROVIDERS
|
from awx.main.constants import SCHEDULEABLE_PROVIDERS
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat, camelcase_to_underscore
|
from awx.main.utils import get_type_for_model, get_model_for_type, build_url, timestamp_apiformat, camelcase_to_underscore, getattrd
|
||||||
from awx.main.redact import REPLACE_STR
|
from awx.main.redact import REPLACE_STR
|
||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
|
|
||||||
@@ -78,7 +78,6 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
|
'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
|
||||||
'cloud_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
|
'cloud_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud'),
|
||||||
'network_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'net'),
|
'network_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'net'),
|
||||||
'permission': DEFAULT_SUMMARY_FIELDS,
|
|
||||||
'job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
|
'job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',),
|
||||||
'job_template': DEFAULT_SUMMARY_FIELDS,
|
'job_template': DEFAULT_SUMMARY_FIELDS,
|
||||||
'schedule': DEFAULT_SUMMARY_FIELDS + ('next_run',),
|
'schedule': DEFAULT_SUMMARY_FIELDS + ('next_run',),
|
||||||
@@ -90,6 +89,7 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
|
'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'),
|
||||||
'inventory_source': ('source', 'last_updated', 'status'),
|
'inventory_source': ('source', 'last_updated', 'status'),
|
||||||
'source_script': ('name', 'description'),
|
'source_script': ('name', 'description'),
|
||||||
|
'role': ('id', 'role_field')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -340,16 +340,18 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
return None
|
return None
|
||||||
elif isinstance(obj, User):
|
elif isinstance(obj, User):
|
||||||
return obj.date_joined
|
return obj.date_joined
|
||||||
else:
|
elif hasattr(obj, 'created'):
|
||||||
return obj.created
|
return obj.created
|
||||||
|
return None
|
||||||
|
|
||||||
def get_modified(self, obj):
|
def get_modified(self, obj):
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return None
|
return None
|
||||||
elif isinstance(obj, User):
|
elif isinstance(obj, User):
|
||||||
return obj.last_login # Not actually exposed for User.
|
return obj.last_login # Not actually exposed for User.
|
||||||
else:
|
elif hasattr(obj, 'modified'):
|
||||||
return obj.modified
|
return obj.modified
|
||||||
|
return None
|
||||||
|
|
||||||
def build_standard_field(self, field_name, model_field):
|
def build_standard_field(self, field_name, model_field):
|
||||||
# DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits
|
# DRF 3.3 serializers.py::build_standard_field() -> utils/field_mapping.py::get_field_kwargs() short circuits
|
||||||
@@ -699,7 +701,7 @@ class UserSerializer(BaseSerializer):
|
|||||||
|
|
||||||
def validate_password(self, value):
|
def validate_password(self, value):
|
||||||
if not self.instance and value in (None, ''):
|
if not self.instance and value in (None, ''):
|
||||||
raise serializers.ValidationError('Password required for new User')
|
raise serializers.ValidationError('Password required for new User.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _update_password(self, obj, new_password):
|
def _update_password(self, obj, new_password):
|
||||||
@@ -763,7 +765,7 @@ class UserSerializer(BaseSerializer):
|
|||||||
ldap_managed_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys())
|
ldap_managed_fields.extend(getattr(settings, 'AUTH_LDAP_USER_FLAGS_BY_GROUP', {}).keys())
|
||||||
if field_name in ldap_managed_fields:
|
if field_name in ldap_managed_fields:
|
||||||
if value != getattr(self.instance, field_name):
|
if value != getattr(self.instance, field_name):
|
||||||
raise serializers.ValidationError('Unable to change %s on user managed by LDAP' % field_name)
|
raise serializers.ValidationError('Unable to change %s on user managed by LDAP.' % field_name)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate_username(self, value):
|
def validate_username(self, value):
|
||||||
@@ -796,6 +798,7 @@ class OrganizationSerializer(BaseSerializer):
|
|||||||
users = reverse('api:organization_users_list', args=(obj.pk,)),
|
users = reverse('api:organization_users_list', args=(obj.pk,)),
|
||||||
admins = reverse('api:organization_admins_list', args=(obj.pk,)),
|
admins = reverse('api:organization_admins_list', args=(obj.pk,)),
|
||||||
teams = reverse('api:organization_teams_list', args=(obj.pk,)),
|
teams = reverse('api:organization_teams_list', args=(obj.pk,)),
|
||||||
|
credentials = reverse('api:organization_credential_list', args=(obj.pk,)),
|
||||||
activity_stream = reverse('api:organization_activity_stream_list', args=(obj.pk,)),
|
activity_stream = reverse('api:organization_activity_stream_list', args=(obj.pk,)),
|
||||||
notifiers = reverse('api:organization_notifiers_list', args=(obj.pk,)),
|
notifiers = reverse('api:organization_notifiers_list', args=(obj.pk,)),
|
||||||
notifiers_any = reverse('api:organization_notifiers_any_list', args=(obj.pk,)),
|
notifiers_any = reverse('api:organization_notifiers_any_list', args=(obj.pk,)),
|
||||||
@@ -961,7 +964,7 @@ class BaseSerializerWithVariables(BaseSerializer):
|
|||||||
try:
|
try:
|
||||||
yaml.safe_load(value)
|
yaml.safe_load(value)
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
raise serializers.ValidationError('Must be valid JSON or YAML')
|
raise serializers.ValidationError('Must be valid JSON or YAML.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -1113,7 +1116,7 @@ class HostSerializer(BaseSerializerWithVariables):
|
|||||||
vars_dict['ansible_ssh_port'] = port
|
vars_dict['ansible_ssh_port'] = port
|
||||||
attrs['variables'] = yaml.dump(vars_dict)
|
attrs['variables'] = yaml.dump(vars_dict)
|
||||||
except (yaml.YAMLError, TypeError):
|
except (yaml.YAMLError, TypeError):
|
||||||
raise serializers.ValidationError('Must be valid JSON or YAML')
|
raise serializers.ValidationError('Must be valid JSON or YAML.')
|
||||||
|
|
||||||
return super(HostSerializer, self).validate(attrs)
|
return super(HostSerializer, self).validate(attrs)
|
||||||
|
|
||||||
@@ -1170,7 +1173,7 @@ class GroupSerializer(BaseSerializerWithVariables):
|
|||||||
|
|
||||||
def validate_name(self, value):
|
def validate_name(self, value):
|
||||||
if value in ('all', '_meta'):
|
if value in ('all', '_meta'):
|
||||||
raise serializers.ValidationError('Invalid group name')
|
raise serializers.ValidationError('Invalid group name.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def to_representation(self, obj):
|
def to_representation(self, obj):
|
||||||
@@ -1303,10 +1306,10 @@ class InventorySourceOptionsSerializer(BaseSerializer):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if source_script.organization != self.instance.inventory.organization:
|
if source_script.organization != self.instance.inventory.organization:
|
||||||
errors['source_script'] = 'source_script does not belong to the same organization as the inventory'
|
errors['source_script'] = 'source_script does not belong to the same organization as the inventory.'
|
||||||
except Exception:
|
except Exception:
|
||||||
# TODO: Log
|
# TODO: Log
|
||||||
errors['source_script'] = 'source_script doesn\'t exist'
|
errors['source_script'] = 'source_script doesn\'t exist.'
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
raise serializers.ValidationError(errors)
|
raise serializers.ValidationError(errors)
|
||||||
@@ -1441,8 +1444,26 @@ class RoleSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = ('*', 'description', 'name')
|
read_only_fields = ('id', 'role_field', 'description', 'name')
|
||||||
read_only_fields = ('description', 'name')
|
|
||||||
|
def to_representation(self, obj):
|
||||||
|
ret = super(RoleSerializer, self).to_representation(obj)
|
||||||
|
|
||||||
|
def spacify_type_name(cls):
|
||||||
|
return re.sub(r'([a-z])([A-Z])', '\g<1> \g<2>', cls.__name__)
|
||||||
|
|
||||||
|
if obj.object_id:
|
||||||
|
content_object = obj.content_object
|
||||||
|
if hasattr(content_object, 'username'):
|
||||||
|
ret['summary_fields']['resource_name'] = obj.content_object.username
|
||||||
|
if hasattr(content_object, 'name'):
|
||||||
|
ret['summary_fields']['resource_name'] = obj.content_object.name
|
||||||
|
ret['summary_fields']['resource_type'] = obj.content_type.name
|
||||||
|
ret['summary_fields']['resource_type_display_name'] = spacify_type_name(obj.content_type.model_class())
|
||||||
|
|
||||||
|
ret.pop('created')
|
||||||
|
ret.pop('modified')
|
||||||
|
return ret
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
ret = super(RoleSerializer, self).get_related(obj)
|
ret = super(RoleSerializer, self).get_related(obj)
|
||||||
@@ -1524,6 +1545,15 @@ class ResourceAccessListElementSerializer(UserSerializer):
|
|||||||
.filter(content_type=team_content_type,
|
.filter(content_type=team_content_type,
|
||||||
members=user,
|
members=user,
|
||||||
children__in=direct_permissive_role_ids)
|
children__in=direct_permissive_role_ids)
|
||||||
|
if content_type == team_content_type:
|
||||||
|
# When looking at the access list for a team, exclude the entries
|
||||||
|
# for that team. This exists primarily so we don't list the read role
|
||||||
|
# as a direct role when a user is a member or admin of a team
|
||||||
|
direct_team_roles = direct_team_roles.exclude(
|
||||||
|
children__content_type=team_content_type,
|
||||||
|
children__object_id=obj.id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
indirect_team_roles = Role.objects \
|
indirect_team_roles = Role.objects \
|
||||||
.filter(content_type=team_content_type,
|
.filter(content_type=team_content_type,
|
||||||
@@ -1553,6 +1583,18 @@ class ResourceAccessListElementSerializer(UserSerializer):
|
|||||||
class CredentialSerializer(BaseSerializer):
|
class CredentialSerializer(BaseSerializer):
|
||||||
|
|
||||||
# FIXME: may want to make some fields filtered based on user accessing
|
# FIXME: may want to make some fields filtered based on user accessing
|
||||||
|
user = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=User.objects.all(), required=False, default=None, write_only=True,
|
||||||
|
help_text='Write-only field used to add user to owner role. If provided, '
|
||||||
|
'do not give either team or organization. Only valid for creation.')
|
||||||
|
team = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=Team.objects.all(), required=False, default=None, write_only=True,
|
||||||
|
help_text='Write-only field used to add team to owner role. If provided, '
|
||||||
|
'do not give either user or organization. Only valid for creation.')
|
||||||
|
organization = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=Organization.objects.all(), required=False, default=None, write_only=True,
|
||||||
|
help_text='Write-only field used to add organization to owner role. If provided, '
|
||||||
|
'do not give either team or team. Only valid for creation.')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Credential
|
model = Credential
|
||||||
@@ -1561,7 +1603,14 @@ class CredentialSerializer(BaseSerializer):
|
|||||||
'ssh_key_data', 'ssh_key_unlock',
|
'ssh_key_data', 'ssh_key_unlock',
|
||||||
'become_method', 'become_username', 'become_password',
|
'become_method', 'become_username', 'become_password',
|
||||||
'vault_password', 'subscription', 'tenant', 'secret', 'client',
|
'vault_password', 'subscription', 'tenant', 'secret', 'client',
|
||||||
'authorize', 'authorize_password')
|
'authorize', 'authorize_password',
|
||||||
|
'user', 'team', 'organization')
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
# Remove the user, team, and organization processed in view
|
||||||
|
for field in ['user', 'team', 'organization']:
|
||||||
|
validated_data.pop(field, None)
|
||||||
|
return super(CredentialSerializer, self).create(validated_data)
|
||||||
|
|
||||||
def build_standard_field(self, field_name, model_field):
|
def build_standard_field(self, field_name, model_field):
|
||||||
field_class, field_kwargs = super(CredentialSerializer, self).build_standard_field(field_name, model_field)
|
field_class, field_kwargs = super(CredentialSerializer, self).build_standard_field(field_name, model_field)
|
||||||
@@ -1657,9 +1706,9 @@ class JobOptionsSerializer(BaseSerializer):
|
|||||||
if not project and job_type != PERM_INVENTORY_SCAN:
|
if not project and job_type != PERM_INVENTORY_SCAN:
|
||||||
raise serializers.ValidationError({'project': 'This field is required.'})
|
raise serializers.ValidationError({'project': 'This field is required.'})
|
||||||
if project and playbook and force_text(playbook) not in project.playbooks:
|
if project and playbook and force_text(playbook) not in project.playbooks:
|
||||||
raise serializers.ValidationError({'playbook': 'Playbook not found for project'})
|
raise serializers.ValidationError({'playbook': 'Playbook not found for project.'})
|
||||||
if project and not playbook:
|
if project and not playbook:
|
||||||
raise serializers.ValidationError({'playbook': 'Must select playbook for project'})
|
raise serializers.ValidationError({'playbook': 'Must select playbook for project.'})
|
||||||
|
|
||||||
return super(JobOptionsSerializer, self).validate(attrs)
|
return super(JobOptionsSerializer, self).validate(attrs)
|
||||||
|
|
||||||
@@ -1720,7 +1769,7 @@ class JobTemplateSerializer(UnifiedJobTemplateSerializer, JobOptionsSerializer):
|
|||||||
survey_enabled = attrs.get('survey_enabled', self.instance and self.instance.survey_enabled or False)
|
survey_enabled = attrs.get('survey_enabled', self.instance and self.instance.survey_enabled or False)
|
||||||
job_type = attrs.get('job_type', self.instance and self.instance.job_type or None)
|
job_type = attrs.get('job_type', self.instance and self.instance.job_type or None)
|
||||||
if survey_enabled and job_type == PERM_INVENTORY_SCAN:
|
if survey_enabled and job_type == PERM_INVENTORY_SCAN:
|
||||||
raise serializers.ValidationError({'survey_enabled': 'Survey Enabled can not be used with scan jobs'})
|
raise serializers.ValidationError({'survey_enabled': 'Survey Enabled can not be used with scan jobs.'})
|
||||||
|
|
||||||
return super(JobTemplateSerializer, self).validate(attrs)
|
return super(JobTemplateSerializer, self).validate(attrs)
|
||||||
|
|
||||||
@@ -1733,12 +1782,13 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
|||||||
ask_tags_on_launch = serializers.ReadOnlyField()
|
ask_tags_on_launch = serializers.ReadOnlyField()
|
||||||
ask_job_type_on_launch = serializers.ReadOnlyField()
|
ask_job_type_on_launch = serializers.ReadOnlyField()
|
||||||
ask_inventory_on_launch = serializers.ReadOnlyField()
|
ask_inventory_on_launch = serializers.ReadOnlyField()
|
||||||
|
ask_credential_on_launch = serializers.ReadOnlyField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Job
|
model = Job
|
||||||
fields = ('*', 'job_template', 'passwords_needed_to_start', 'ask_variables_on_launch',
|
fields = ('*', 'job_template', 'passwords_needed_to_start', 'ask_variables_on_launch',
|
||||||
'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch',
|
'ask_limit_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch',
|
||||||
'ask_inventory_on_launch')
|
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
res = super(JobSerializer, self).get_related(obj)
|
res = super(JobSerializer, self).get_related(obj)
|
||||||
@@ -1865,9 +1915,9 @@ class JobRelaunchSerializer(JobSerializer):
|
|||||||
if not obj.credential:
|
if not obj.credential:
|
||||||
raise serializers.ValidationError(dict(credential=["Credential not found or deleted."]))
|
raise serializers.ValidationError(dict(credential=["Credential not found or deleted."]))
|
||||||
if obj.job_type != PERM_INVENTORY_SCAN and obj.project is None:
|
if obj.job_type != PERM_INVENTORY_SCAN and obj.project is None:
|
||||||
raise serializers.ValidationError(dict(errors=["Job Template Project is missing or undefined"]))
|
raise serializers.ValidationError(dict(errors=["Job Template Project is missing or undefined."]))
|
||||||
if obj.inventory is None:
|
if obj.inventory is None:
|
||||||
raise serializers.ValidationError(dict(errors=["Job Template Inventory is missing or undefined"]))
|
raise serializers.ValidationError(dict(errors=["Job Template Inventory is missing or undefined."]))
|
||||||
attrs = super(JobRelaunchSerializer, self).validate(attrs)
|
attrs = super(JobRelaunchSerializer, self).validate(attrs)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@@ -1876,7 +1926,7 @@ class AdHocCommandSerializer(UnifiedJobSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = AdHocCommand
|
model = AdHocCommand
|
||||||
fields = ('*', 'job_type', 'inventory', 'limit', 'credential',
|
fields = ('*', 'job_type', 'inventory', 'limit', 'credential',
|
||||||
'module_name', 'module_args', 'forks', 'verbosity',
|
'module_name', 'module_args', 'forks', 'verbosity', 'extra_vars',
|
||||||
'become_enabled', '-unified_job_template', '-description')
|
'become_enabled', '-unified_job_template', '-description')
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {
|
'name': {
|
||||||
@@ -2104,6 +2154,8 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
inventory_needed_to_start = serializers.SerializerMethodField()
|
inventory_needed_to_start = serializers.SerializerMethodField()
|
||||||
survey_enabled = serializers.SerializerMethodField()
|
survey_enabled = serializers.SerializerMethodField()
|
||||||
extra_vars = VerbatimField(required=False, write_only=True)
|
extra_vars = VerbatimField(required=False, write_only=True)
|
||||||
|
job_template_data = serializers.SerializerMethodField()
|
||||||
|
defaults = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = JobTemplate
|
model = JobTemplate
|
||||||
@@ -2113,7 +2165,8 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
'ask_job_type_on_launch', 'ask_limit_on_launch',
|
'ask_job_type_on_launch', 'ask_limit_on_launch',
|
||||||
'ask_inventory_on_launch', 'ask_credential_on_launch',
|
'ask_inventory_on_launch', 'ask_credential_on_launch',
|
||||||
'survey_enabled', 'variables_needed_to_start',
|
'survey_enabled', 'variables_needed_to_start',
|
||||||
'credential_needed_to_start', 'inventory_needed_to_start',)
|
'credential_needed_to_start', 'inventory_needed_to_start',
|
||||||
|
'job_template_data', 'defaults')
|
||||||
read_only_fields = ('ask_variables_on_launch', 'ask_limit_on_launch',
|
read_only_fields = ('ask_variables_on_launch', 'ask_limit_on_launch',
|
||||||
'ask_tags_on_launch', 'ask_job_type_on_launch',
|
'ask_tags_on_launch', 'ask_job_type_on_launch',
|
||||||
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
'ask_inventory_on_launch', 'ask_credential_on_launch')
|
||||||
@@ -2137,6 +2190,21 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
return obj.survey_enabled and 'spec' in obj.survey_spec
|
return obj.survey_enabled and 'spec' in obj.survey_spec
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_defaults(self, obj):
|
||||||
|
ask_for_vars_dict = obj._ask_for_vars_dict()
|
||||||
|
defaults_dict = {}
|
||||||
|
for field in ask_for_vars_dict:
|
||||||
|
if field in ('inventory', 'credential'):
|
||||||
|
defaults_dict[field] = dict(
|
||||||
|
name=getattrd(obj, '%s.name' % field, None),
|
||||||
|
id=getattrd(obj, '%s.pk' % field, None))
|
||||||
|
else:
|
||||||
|
defaults_dict[field] = getattr(obj, field)
|
||||||
|
return defaults_dict
|
||||||
|
|
||||||
|
def get_job_template_data(self, obj):
|
||||||
|
return dict(name=obj.name, id=obj.id, description=obj.description)
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
errors = {}
|
errors = {}
|
||||||
obj = self.context.get('obj')
|
obj = self.context.get('obj')
|
||||||
@@ -2166,8 +2234,9 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
try:
|
try:
|
||||||
extra_vars = yaml.safe_load(extra_vars)
|
extra_vars = yaml.safe_load(extra_vars)
|
||||||
except (yaml.YAMLError, TypeError, AttributeError):
|
assert isinstance(extra_vars, dict)
|
||||||
errors['extra_vars'] = 'Must be valid JSON or YAML'
|
except (yaml.YAMLError, TypeError, AttributeError, AssertionError):
|
||||||
|
errors['extra_vars'] = 'Must be a valid JSON or YAML dictionary'
|
||||||
|
|
||||||
if not isinstance(extra_vars, dict):
|
if not isinstance(extra_vars, dict):
|
||||||
extra_vars = {}
|
extra_vars = {}
|
||||||
@@ -2178,9 +2247,9 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
errors['variables_needed_to_start'] = validation_errors
|
errors['variables_needed_to_start'] = validation_errors
|
||||||
|
|
||||||
if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None):
|
if obj.job_type != PERM_INVENTORY_SCAN and (obj.project is None):
|
||||||
errors['project'] = 'Job Template Project is missing or undefined'
|
errors['project'] = 'Job Template Project is missing or undefined.'
|
||||||
if (obj.inventory is None) and not attrs.get('inventory', None):
|
if (obj.inventory is None) and not attrs.get('inventory', None):
|
||||||
errors['inventory'] = 'Job Template Inventory is missing or undefined'
|
errors['inventory'] = 'Job Template Inventory is missing or undefined.'
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
raise serializers.ValidationError(errors)
|
raise serializers.ValidationError(errors)
|
||||||
@@ -2318,7 +2387,7 @@ class ScheduleSerializer(BaseSerializer):
|
|||||||
|
|
||||||
def validate_unified_job_template(self, value):
|
def validate_unified_job_template(self, value):
|
||||||
if type(value) == InventorySource and value.source not in SCHEDULEABLE_PROVIDERS:
|
if type(value) == InventorySource and value.source not in SCHEDULEABLE_PROVIDERS:
|
||||||
raise serializers.ValidationError('Inventory Source must be a cloud resource')
|
raise serializers.ValidationError('Inventory Source must be a cloud resource.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
# We reject rrules if:
|
# We reject rrules if:
|
||||||
@@ -2510,7 +2579,7 @@ class TowerSettingsSerializer(BaseSerializer):
|
|||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
manifest = settings.TOWER_SETTINGS_MANIFEST
|
manifest = settings.TOWER_SETTINGS_MANIFEST
|
||||||
if attrs['key'] not in manifest:
|
if attrs['key'] not in manifest:
|
||||||
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key".format(attrs['key'])]))
|
raise serializers.ValidationError(dict(key=["Key {0} is not a valid settings key.".format(attrs['key'])]))
|
||||||
|
|
||||||
if attrs['value_type'] == 'json':
|
if attrs['value_type'] == 'json':
|
||||||
attrs['value'] = json.dumps(attrs['value'])
|
attrs['value'] = json.dumps(attrs['value'])
|
||||||
@@ -2544,7 +2613,7 @@ class AuthTokenSerializer(serializers.Serializer):
|
|||||||
else:
|
else:
|
||||||
raise serializers.ValidationError('Unable to login with provided credentials.')
|
raise serializers.ValidationError('Unable to login with provided credentials.')
|
||||||
else:
|
else:
|
||||||
raise serializers.ValidationError('Must include "username" and "password"')
|
raise serializers.ValidationError('Must include "username" and "password".')
|
||||||
|
|
||||||
|
|
||||||
class FactVersionSerializer(BaseFactSerializer):
|
class FactVersionSerializer(BaseFactSerializer):
|
||||||
|
|||||||
@@ -1,20 +1,3 @@
|
|||||||
TOWER SOFTWARE END USER LICENSE AGREEMENT
|
TOWER SOFTWARE END USER LICENSE AGREEMENT
|
||||||
|
|
||||||
Unless otherwise agreed to, and executed in a definitive agreement, between
|
Unless otherwise agreed to, and executed in a definitive agreement, between Ansible, Inc. (“Ansible”) and the individual or entity (“Customer”) signing or electronically accepting these terms of use for the Tower Software (“EULA”), all Tower Software, including any and all versions released or made available by Ansible, shall be subject to the Ansible Software Subscription and Services Agreement found at www.ansible.com/subscription-agreement (“Agreement”). Ansible is not responsible for any additional obligations, conditions or warranties agreed to between Customer and an authorized distributor, or reseller, of the Tower Software. BY DOWNLOADING AND USING THE TOWER SOFTWARE, OR BY CLICKING ON THE “YES” BUTTON OR OTHER BUTTON OR MECHANISM DESIGNED TO ACKNOWLEDGE CONSENT TO THE TERMS OF AN ELECTRONIC COPY OF THIS EULA, THE CUSTOMER HEREBY ACKNOWLEDGES THAT CUSTOMER HAS READ, UNDERSTOOD, AND AGREES TO BE BOUND BY THE TERMS OF THIS EULA AND AGREEMENT, INCLUDING ALL TERMS INCORPORATED HEREIN BY REFERENCE, AND THAT THIS EULA AND AGREEMENT IS EQUIVALENT TO ANY WRITTEN NEGOTIATED AGREEMENT BETWEEN CUSTOMER AND ANSIBLE. THIS EULA AND AGREEMENT IS ENFORCEABLE AGAINST ANY PERSON OR ENTITY THAT USES OR AVAILS ITSELF OF THE TOWER SOFTWARE OR ANY PERSON OR ENTITY THAT USES THE OR AVAILS ITSELF OF THE TOWER SOFTWARE ON ANOTHER PERSON’S OR ENTITY’S BEHALF.
|
||||||
Ansible, Inc. (“Ansible”) and the individual or entity (“Customer”) signing or
|
|
||||||
electronically accepting these terms of use for the Tower Software (“EULA”),
|
|
||||||
all Tower Software, including any and all versions released or made available
|
|
||||||
by Ansible, shall be subject to the Ansible Software Subscription and Services
|
|
||||||
Agreement found at www.ansible.com/subscription-agreement (“Agreement”).
|
|
||||||
Ansible is not responsible for any additional obligations, conditions or
|
|
||||||
warranties agreed to between Customer and an authorized distributor, or
|
|
||||||
reseller, of the Tower Software. BY DOWNLOADING AND USING THE TOWER SOFTWARE,
|
|
||||||
OR BY CLICKING ON THE “YES” BUTTON OR OTHER BUTTON OR MECHANISM DESIGNED TO
|
|
||||||
ACKNOWLEDGE CONSENT TO THE TERMS OF AN ELECTRONIC COPY OF THIS EULA, THE
|
|
||||||
CUSTOMER HEREBY ACKNOWLEDGES THAT CUSTOMER HAS READ, UNDERSTOOD, AND AGREES TO
|
|
||||||
BE BOUND BY THE TERMS OF THIS EULA AND AGREEMENT, INCLUDING ALL TERMS
|
|
||||||
INCORPORATED HEREIN BY REFERENCE, AND THAT THIS EULA AND AGREEMENT IS
|
|
||||||
EQUIVALENT TO ANY WRITTEN NEGOTIATED AGREEMENT BETWEEN CUSTOMER AND ANSIBLE.
|
|
||||||
THIS EULA AND AGREEMENT IS ENFORCEABLE AGAINST ANY PERSON OR ENTITY THAT USES
|
|
||||||
OR AVAILS ITSELF OF THE TOWER SOFTWARE OR ANY PERSON OR ENTITY THAT USES THE OR
|
|
||||||
AVAILS ITSELF OF THE TOWER SOFTWARE ON ANOTHER PERSON’S OR ENTITY’S BEHALF.
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.core.servers.basehttp import FileWrapper
|
from django.core.servers.basehttp import FileWrapper
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
from rest_framework.exceptions import PermissionDenied, ParseError
|
from rest_framework.exceptions import PermissionDenied, ParseError
|
||||||
@@ -616,9 +618,14 @@ class OrganizationList(ListCreateAPIView):
|
|||||||
|
|
||||||
JT_reference = 'project__organization'
|
JT_reference = 'project__organization'
|
||||||
db_results['job_templates'] = JobTemplate.accessible_objects(
|
db_results['job_templates'] = JobTemplate.accessible_objects(
|
||||||
self.request.user, 'read_role').values(JT_reference).annotate(
|
self.request.user, 'read_role').exclude(job_type='scan').values(JT_reference).annotate(
|
||||||
Count(JT_reference)).order_by(JT_reference)
|
Count(JT_reference)).order_by(JT_reference)
|
||||||
|
|
||||||
|
JT_scan_reference = 'inventory__organization'
|
||||||
|
db_results['job_templates_scan'] = JobTemplate.accessible_objects(
|
||||||
|
self.request.user, 'read_role').filter(job_type='scan').values(JT_scan_reference).annotate(
|
||||||
|
Count(JT_scan_reference)).order_by(JT_scan_reference)
|
||||||
|
|
||||||
db_results['projects'] = project_qs\
|
db_results['projects'] = project_qs\
|
||||||
.values('organization').annotate(Count('organization')).order_by('organization')
|
.values('organization').annotate(Count('organization')).order_by('organization')
|
||||||
|
|
||||||
@@ -638,6 +645,8 @@ class OrganizationList(ListCreateAPIView):
|
|||||||
for res in db_results:
|
for res in db_results:
|
||||||
if res == 'job_templates':
|
if res == 'job_templates':
|
||||||
org_reference = JT_reference
|
org_reference = JT_reference
|
||||||
|
elif res == 'job_templates_scan':
|
||||||
|
org_reference = JT_scan_reference
|
||||||
elif res == 'users':
|
elif res == 'users':
|
||||||
org_reference = 'id'
|
org_reference = 'id'
|
||||||
else:
|
else:
|
||||||
@@ -651,6 +660,12 @@ class OrganizationList(ListCreateAPIView):
|
|||||||
continue
|
continue
|
||||||
count_context[org_id][res] = entry['%s__count' % org_reference]
|
count_context[org_id][res] = entry['%s__count' % org_reference]
|
||||||
|
|
||||||
|
# Combine the counts for job templates with scan job templates
|
||||||
|
for org in org_id_list:
|
||||||
|
org_id = org['id']
|
||||||
|
if 'job_templates_scan' in count_context[org_id]:
|
||||||
|
count_context[org_id]['job_templates'] += count_context[org_id].pop('job_templates_scan')
|
||||||
|
|
||||||
full_context['related_field_counts'] = count_context
|
full_context['related_field_counts'] = count_context
|
||||||
|
|
||||||
return full_context
|
return full_context
|
||||||
@@ -684,8 +699,10 @@ class OrganizationDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
organization__id=org_id).count()
|
organization__id=org_id).count()
|
||||||
org_counts['projects'] = Project.accessible_objects(**access_kwargs).filter(
|
org_counts['projects'] = Project.accessible_objects(**access_kwargs).filter(
|
||||||
organization__id=org_id).count()
|
organization__id=org_id).count()
|
||||||
org_counts['job_templates'] = JobTemplate.accessible_objects(**access_kwargs).filter(
|
org_counts['job_templates'] = JobTemplate.accessible_objects(**access_kwargs).exclude(
|
||||||
project__organization__id=org_id).count()
|
job_type='scan').filter(project__organization__id=org_id).count()
|
||||||
|
org_counts['job_templates'] += JobTemplate.accessible_objects(**access_kwargs).filter(
|
||||||
|
job_type='scan').filter(inventory__organization__id=org_id).count()
|
||||||
|
|
||||||
full_context['related_field_counts'] = {}
|
full_context['related_field_counts'] = {}
|
||||||
full_context['related_field_counts'][org_id] = org_counts
|
full_context['related_field_counts'][org_id] = org_counts
|
||||||
@@ -814,10 +831,11 @@ class TeamRolesList(SubListCreateAttachDetachAPIView):
|
|||||||
relationship='member_role.children'
|
relationship='member_role.children'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
team = Team.objects.get(pk=self.kwargs['pk'])
|
team = get_object_or_404(Team, pk=self.kwargs['pk'])
|
||||||
return team.member_role.children.filter(id__in=Role.visible_roles(self.request.user))
|
if not self.request.user.can_access(Team, 'read', team):
|
||||||
|
raise PermissionDenied()
|
||||||
|
return Role.filter_visible_roles(self.request.user, team.member_role.children.all())
|
||||||
|
|
||||||
# XXX: Need to enforce permissions
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
# Forbid implicit role creation here
|
# Forbid implicit role creation here
|
||||||
sub_id = request.data.get('id', None)
|
sub_id = request.data.get('id', None)
|
||||||
@@ -1081,8 +1099,12 @@ class UserRolesList(SubListCreateAttachDetachAPIView):
|
|||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
#u = User.objects.get(pk=self.kwargs['pk'])
|
u = get_object_or_404(User, pk=self.kwargs['pk'])
|
||||||
return Role.visible_roles(self.request.user).filter(members__in=[int(self.kwargs['pk']), ])
|
if not self.request.user.can_access(User, 'read', u):
|
||||||
|
raise PermissionDenied()
|
||||||
|
content_type = ContentType.objects.get_for_model(User)
|
||||||
|
return Role.filter_visible_roles(self.request.user, u.roles.all()) \
|
||||||
|
.exclude(content_type=content_type, object_id=u.id)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
# Forbid implicit role creation here
|
# Forbid implicit role creation here
|
||||||
@@ -1090,6 +1112,10 @@ class UserRolesList(SubListCreateAttachDetachAPIView):
|
|||||||
if not sub_id:
|
if not sub_id:
|
||||||
data = dict(msg='Role "id" field is missing')
|
data = dict(msg='Role "id" field is missing')
|
||||||
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
return Response(data, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if sub_id == self.request.user.admin_role.pk:
|
||||||
|
raise PermissionDenied('You may not remove your own admin_role')
|
||||||
|
|
||||||
return super(UserRolesList, self).post(request, *args, **kwargs)
|
return super(UserRolesList, self).post(request, *args, **kwargs)
|
||||||
|
|
||||||
def check_parent_access(self, parent=None):
|
def check_parent_access(self, parent=None):
|
||||||
@@ -1205,6 +1231,10 @@ class CredentialList(ListCreateAPIView):
|
|||||||
serializer_class = CredentialSerializer
|
serializer_class = CredentialSerializer
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
for field in [x for x in ['user', 'team', 'organization'] if x in request.data and request.data[x] in ('', None)]:
|
||||||
|
request.data.pop(field)
|
||||||
|
kwargs.pop(field, None)
|
||||||
|
|
||||||
if not any([x in request.data for x in ['user', 'team', 'organization']]):
|
if not any([x in request.data for x in ['user', 'team', 'organization']]):
|
||||||
return Response({'detail': 'Missing user, team, or organization'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': 'Missing user, team, or organization'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@@ -1213,15 +1243,15 @@ class CredentialList(ListCreateAPIView):
|
|||||||
|
|
||||||
if 'user' in request.data:
|
if 'user' in request.data:
|
||||||
user = User.objects.get(pk=request.data['user'])
|
user = User.objects.get(pk=request.data['user'])
|
||||||
obj = user
|
can_add_params = {'user': user.id}
|
||||||
if 'team' in request.data:
|
if 'team' in request.data:
|
||||||
team = Team.objects.get(pk=request.data['team'])
|
team = Team.objects.get(pk=request.data['team'])
|
||||||
obj = team
|
can_add_params = {'team': team.id}
|
||||||
if 'organization' in request.data:
|
if 'organization' in request.data:
|
||||||
organization = Organization.objects.get(pk=request.data['organization'])
|
organization = Organization.objects.get(pk=request.data['organization'])
|
||||||
obj = organization
|
can_add_params = {'organization': organization.id}
|
||||||
|
|
||||||
if self.request.user not in obj.admin_role:
|
if not self.request.user.can_access(Credential, 'add', can_add_params):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
ret = super(CredentialList, self).post(request, *args, **kwargs)
|
ret = super(CredentialList, self).post(request, *args, **kwargs)
|
||||||
@@ -1251,8 +1281,7 @@ class UserCredentialsList(CredentialList):
|
|||||||
return user_creds & visible_creds
|
return user_creds & visible_creds
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
user = User.objects.get(pk=self.kwargs['pk'])
|
request.data['user'] = self.kwargs['pk']
|
||||||
request.data['user'] = user.id
|
|
||||||
# The following post takes care of ensuring the current user can add a cred to this user
|
# The following post takes care of ensuring the current user can add a cred to this user
|
||||||
return super(UserCredentialsList, self).post(request, args, kwargs)
|
return super(UserCredentialsList, self).post(request, args, kwargs)
|
||||||
|
|
||||||
@@ -1271,8 +1300,7 @@ class TeamCredentialsList(CredentialList):
|
|||||||
return team_creds & visible_creds
|
return team_creds & visible_creds
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
team = Team.objects.get(pk=self.kwargs['pk'])
|
request.data['team'] = self.kwargs['pk']
|
||||||
request.data['team'] = team.id
|
|
||||||
# The following post takes care of ensuring the current user can add a cred to this user
|
# The following post takes care of ensuring the current user can add a cred to this user
|
||||||
return super(TeamCredentialsList, self).post(request, args, kwargs)
|
return super(TeamCredentialsList, self).post(request, args, kwargs)
|
||||||
|
|
||||||
@@ -1479,7 +1507,7 @@ class HostAllGroupsList(SubListAPIView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
parent = self.get_parent_object()
|
parent = self.get_parent_object()
|
||||||
self.check_parent_access(parent)
|
self.check_parent_access(parent)
|
||||||
qs = self.request.user.get_queryset(self.model)
|
qs = self.request.user.get_queryset(self.model).distinct()
|
||||||
sublist_qs = parent.all_groups.distinct()
|
sublist_qs = parent.all_groups.distinct()
|
||||||
return qs & sublist_qs
|
return qs & sublist_qs
|
||||||
|
|
||||||
@@ -2263,7 +2291,7 @@ class JobTemplateNotifiersSuccessList(SubListCreateAttachDetachAPIView):
|
|||||||
parent_model = JobTemplate
|
parent_model = JobTemplate
|
||||||
relationship = 'notifiers_success'
|
relationship = 'notifiers_success'
|
||||||
|
|
||||||
class JobTemplateLabelList(SubListCreateAttachDetachAPIView):
|
class JobTemplateLabelList(SubListCreateAttachDetachAPIView, DeleteLastUnattachLabelMixin):
|
||||||
|
|
||||||
model = Label
|
model = Label
|
||||||
serializer_class = LabelSerializer
|
serializer_class = LabelSerializer
|
||||||
@@ -2454,7 +2482,7 @@ class SystemJobTemplateList(ListAPIView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
raise PermissionDenied("Superuser privileges needed")
|
||||||
return super(SystemJobTemplateList, self).get(request, *args, **kwargs)
|
return super(SystemJobTemplateList, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
class SystemJobTemplateDetail(RetrieveAPIView):
|
class SystemJobTemplateDetail(RetrieveAPIView):
|
||||||
@@ -3184,7 +3212,7 @@ class SystemJobList(ListCreateAPIView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
return Response(status=status.HTTP_404_NOT_FOUND)
|
raise PermissionDenied("Superuser privileges needed")
|
||||||
return super(SystemJobList, self).get(request, *args, **kwargs)
|
return super(SystemJobList, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -3573,7 +3601,7 @@ class RoleChildrenList(SubListAPIView):
|
|||||||
# XXX: This should be the intersection between the roles of the user
|
# XXX: This should be the intersection between the roles of the user
|
||||||
# and the roles that the requesting user has access to see
|
# and the roles that the requesting user has access to see
|
||||||
role = Role.objects.get(pk=self.kwargs['pk'])
|
role = Role.objects.get(pk=self.kwargs['pk'])
|
||||||
return role.children
|
return role.children.all()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,18 @@ def check_user_access(user, model_class, action, *args, **kwargs):
|
|||||||
return result
|
return result
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def check_superuser(func):
|
||||||
|
'''
|
||||||
|
check_superuser is a decorator that provides a simple short circuit
|
||||||
|
for access checks. If the User object is a superuser, return True, otherwise
|
||||||
|
execute the logic of the can_access method.
|
||||||
|
'''
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return True
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
class BaseAccess(object):
|
class BaseAccess(object):
|
||||||
'''
|
'''
|
||||||
Base class for checking user access to a given model. Subclasses should
|
Base class for checking user access to a given model. Subclasses should
|
||||||
@@ -242,10 +254,8 @@ class UserAccess(BaseAccess):
|
|||||||
# that a user should be able to edit for themselves.
|
# that a user should be able to edit for themselves.
|
||||||
return bool(self.user == obj or self.can_admin(obj, data))
|
return bool(self.user == obj or self.can_admin(obj, data))
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_admin(self, obj, data):
|
def can_admin(self, obj, data):
|
||||||
# Admin implies changing all user fields.
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return Organization.objects.filter(member_role__members=obj, admin_role__members=self.user).exists()
|
return Organization.objects.filter(member_role__members=obj, admin_role__members=self.user).exists()
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
@@ -277,9 +287,8 @@ class OrganizationAccess(BaseAccess):
|
|||||||
qs = self.model.accessible_objects(self.user, 'read_role')
|
qs = self.model.accessible_objects(self.user, 'read_role')
|
||||||
return qs.select_related('created_by', 'modified_by').all()
|
return qs.select_related('created_by', 'modified_by').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.admin_role
|
return self.user in obj.admin_role
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
@@ -312,27 +321,25 @@ class InventoryAccess(BaseAccess):
|
|||||||
qs = self.model.accessible_objects(self.user, 'read_role')
|
qs = self.model.accessible_objects(self.user, 'read_role')
|
||||||
return qs.select_related('created_by', 'modified_by', 'organization').all()
|
return qs.select_related('created_by', 'modified_by', 'organization').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.read_role
|
return self.user in obj.read_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_use(self, obj):
|
def can_use(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.use_role
|
return self.user in obj.use_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
# If no data is specified, just checking for generic add permission?
|
# If no data is specified, just checking for generic add permission?
|
||||||
if not data:
|
if not data:
|
||||||
return Organization.accessible_objects(self.user, 'admin_role').exists()
|
return Organization.accessible_objects(self.user, 'admin_role').exists()
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
|
|
||||||
org_pk = get_pk_from_dict(data, 'organization')
|
org_pk = get_pk_from_dict(data, 'organization')
|
||||||
org = get_object_or_400(Organization, pk=org_pk)
|
org = get_object_or_400(Organization, pk=org_pk)
|
||||||
return self.user in org.admin_role
|
return self.user in org.admin_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Verify that the user has access to the new organization if moving an
|
# Verify that the user has access to the new organization if moving an
|
||||||
# inventory to a new organization.
|
# inventory to a new organization.
|
||||||
@@ -342,8 +349,9 @@ class InventoryAccess(BaseAccess):
|
|||||||
if self.user not in org.admin_role:
|
if self.user not in org.admin_role:
|
||||||
return False
|
return False
|
||||||
# Otherwise, just check for write permission.
|
# Otherwise, just check for write permission.
|
||||||
return self.user in obj.admin_role
|
return self.user in obj.update_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_admin(self, obj, data):
|
def can_admin(self, obj, data):
|
||||||
# Verify that the user has access to the new organization if moving an
|
# Verify that the user has access to the new organization if moving an
|
||||||
# inventory to a new organization.
|
# inventory to a new organization.
|
||||||
@@ -371,12 +379,16 @@ class HostAccess(BaseAccess):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
inv_qs = Inventory.accessible_objects(self.user, 'read_role')
|
inv_qs = Inventory.accessible_objects(self.user, 'read_role')
|
||||||
group_qs = Group.accessible_objects(self.user, 'read_role')
|
group_qs = Group.accessible_objects(self.user, 'read_role').exclude(inventory__in=inv_qs)
|
||||||
qs = (self.model.objects.filter(inventory=inv_qs) | self.model.objects.filter(groups=group_qs)).distinct()
|
if group_qs.count():
|
||||||
#qs = qs.select_related('created_by', 'modified_by', 'inventory',
|
qs = self.model.objects.filter(Q(inventory__in=inv_qs) | Q(groups__in=group_qs))
|
||||||
# 'last_job__job_template',
|
else:
|
||||||
# 'last_job_host_summary__job')
|
qs = self.model.objects.filter(inventory__in=inv_qs)
|
||||||
#return qs.prefetch_related('groups').all()
|
|
||||||
|
qs = qs.select_related('created_by', 'modified_by', 'inventory',
|
||||||
|
'last_job__job_template',
|
||||||
|
'last_job_host_summary__job')
|
||||||
|
qs =qs.prefetch_related('groups').all()
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
@@ -389,7 +401,7 @@ class HostAccess(BaseAccess):
|
|||||||
# Checks for admin or change permission on inventory.
|
# Checks for admin or change permission on inventory.
|
||||||
inventory_pk = get_pk_from_dict(data, 'inventory')
|
inventory_pk = get_pk_from_dict(data, 'inventory')
|
||||||
inventory = get_object_or_400(Inventory, pk=inventory_pk)
|
inventory = get_object_or_400(Inventory, pk=inventory_pk)
|
||||||
if self.user not in inventory.admin_role:
|
if self.user not in inventory.update_role:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check to see if we have enough licenses
|
# Check to see if we have enough licenses
|
||||||
@@ -403,7 +415,7 @@ class HostAccess(BaseAccess):
|
|||||||
raise PermissionDenied('Unable to change inventory on a host')
|
raise PermissionDenied('Unable to change inventory on a host')
|
||||||
# Checks for admin or change permission on inventory, controls whether
|
# Checks for admin or change permission on inventory, controls whether
|
||||||
# the user can edit variable data.
|
# the user can edit variable data.
|
||||||
return obj and self.user in obj.inventory.admin_role
|
return obj and self.user in obj.inventory.update_role
|
||||||
|
|
||||||
def can_attach(self, obj, sub_obj, relationship, data,
|
def can_attach(self, obj, sub_obj, relationship, data,
|
||||||
skip_sub_obj_read_check=False):
|
skip_sub_obj_read_check=False):
|
||||||
@@ -440,7 +452,7 @@ class GroupAccess(BaseAccess):
|
|||||||
# Checks for admin or change permission on inventory.
|
# Checks for admin or change permission on inventory.
|
||||||
inventory_pk = get_pk_from_dict(data, 'inventory')
|
inventory_pk = get_pk_from_dict(data, 'inventory')
|
||||||
inventory = get_object_or_400(Inventory, pk=inventory_pk)
|
inventory = get_object_or_400(Inventory, pk=inventory_pk)
|
||||||
return self.user in inventory.admin_role
|
return self.user in inventory.update_role
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Prevent moving a group to a different inventory.
|
# Prevent moving a group to a different inventory.
|
||||||
@@ -449,7 +461,7 @@ class GroupAccess(BaseAccess):
|
|||||||
raise PermissionDenied('Unable to change inventory on a group')
|
raise PermissionDenied('Unable to change inventory on a group')
|
||||||
# Checks for admin or change permission on inventory, controls whether
|
# Checks for admin or change permission on inventory, controls whether
|
||||||
# the user can attach subgroups or edit variable data.
|
# the user can attach subgroups or edit variable data.
|
||||||
return obj and self.user in obj.inventory.admin_role
|
return obj and self.user in obj.inventory.update_role
|
||||||
|
|
||||||
def can_attach(self, obj, sub_obj, relationship, data,
|
def can_attach(self, obj, sub_obj, relationship, data,
|
||||||
skip_sub_obj_read_check=False):
|
skip_sub_obj_read_check=False):
|
||||||
@@ -483,7 +495,7 @@ class InventorySourceAccess(BaseAccess):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = self.model.objects.all()
|
qs = self.model.objects.all()
|
||||||
qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory')
|
qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory')
|
||||||
inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True))
|
inventory_ids = self.user.get_queryset(Inventory)
|
||||||
return qs.filter(Q(inventory_id__in=inventory_ids) |
|
return qs.filter(Q(inventory_id__in=inventory_ids) |
|
||||||
Q(group__inventory_id__in=inventory_ids))
|
Q(group__inventory_id__in=inventory_ids))
|
||||||
|
|
||||||
@@ -502,7 +514,7 @@ class InventorySourceAccess(BaseAccess):
|
|||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
# Checks for admin or change permission on group.
|
# Checks for admin or change permission on group.
|
||||||
if obj and obj.group:
|
if obj and obj.group:
|
||||||
return self.user in obj.group.admin_role
|
return self.user in obj.group.update_role
|
||||||
# Can't change inventory sources attached to only the inventory, since
|
# Can't change inventory sources attached to only the inventory, since
|
||||||
# these are created automatically from the management command.
|
# these are created automatically from the management command.
|
||||||
else:
|
else:
|
||||||
@@ -555,21 +567,36 @@ class CredentialAccess(BaseAccess):
|
|||||||
qs = self.model.accessible_objects(self.user, 'read_role')
|
qs = self.model.accessible_objects(self.user, 'read_role')
|
||||||
return qs.select_related('created_by', 'modified_by').all()
|
return qs.select_related('created_by', 'modified_by').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
return self.user in obj.read_role
|
return self.user in obj.read_role
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
# Access enforced in our view where we have context enough to make a decision
|
|
||||||
return True
|
|
||||||
|
|
||||||
def can_use(self, obj):
|
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
user_pk = get_pk_from_dict(data, 'user')
|
||||||
|
if user_pk:
|
||||||
|
user_obj = get_object_or_400(User, pk=user_pk)
|
||||||
|
return check_user_access(self.user, User, 'change', user_obj, None)
|
||||||
|
team_pk = get_pk_from_dict(data, 'team')
|
||||||
|
if team_pk:
|
||||||
|
team_obj = get_object_or_400(Team, pk=team_pk)
|
||||||
|
return check_user_access(self.user, Team, 'change', team_obj, None)
|
||||||
|
organization_pk = get_pk_from_dict(data, 'organization')
|
||||||
|
if organization_pk:
|
||||||
|
organization_obj = get_object_or_400(Organization, pk=organization_pk)
|
||||||
|
return check_user_access(self.user, Organization, 'change', organization_obj, None)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
|
def can_use(self, obj):
|
||||||
return self.user in obj.use_role
|
return self.user in obj.use_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
if not self.can_add(data):
|
||||||
return True
|
return False
|
||||||
return self.user in obj.owner_role
|
return self.user in obj.owner_role
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
@@ -596,14 +623,12 @@ class TeamAccess(BaseAccess):
|
|||||||
qs = self.model.accessible_objects(self.user, 'read_role')
|
qs = self.model.accessible_objects(self.user, 'read_role')
|
||||||
return qs.select_related('created_by', 'modified_by', 'organization').all()
|
return qs.select_related('created_by', 'modified_by', 'organization').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
org_pk = get_pk_from_dict(data, 'organization')
|
||||||
|
org = get_object_or_400(Organization, pk=org_pk)
|
||||||
|
if self.user in org.admin_role:
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
org_pk = get_pk_from_dict(data, 'organization')
|
|
||||||
org = get_object_or_400(Organization, pk=org_pk)
|
|
||||||
if self.user in org.admin_role:
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
@@ -611,6 +636,8 @@ class TeamAccess(BaseAccess):
|
|||||||
org_pk = get_pk_from_dict(data, 'organization')
|
org_pk = get_pk_from_dict(data, 'organization')
|
||||||
if obj and org_pk and obj.organization.pk != org_pk:
|
if obj and org_pk and obj.organization.pk != org_pk:
|
||||||
raise PermissionDenied('Unable to change organization on a team')
|
raise PermissionDenied('Unable to change organization on a team')
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return True
|
||||||
return self.user in obj.admin_role
|
return self.user in obj.admin_role
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
@@ -640,15 +667,13 @@ class ProjectAccess(BaseAccess):
|
|||||||
qs = self.model.accessible_objects(self.user, 'read_role')
|
qs = self.model.accessible_objects(self.user, 'read_role')
|
||||||
return qs.select_related('modified_by', 'credential', 'current_job', 'last_job').all()
|
return qs.select_related('modified_by', 'credential', 'current_job', 'last_job').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
qs = Organization.accessible_objects(self.user, 'admin_role')
|
qs = Organization.accessible_objects(self.user, 'admin_role')
|
||||||
return qs.exists()
|
return qs.exists()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.admin_role
|
return self.user in obj.admin_role
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
@@ -674,9 +699,11 @@ class ProjectUpdateAccess(BaseAccess):
|
|||||||
project_ids = set(self.user.get_queryset(Project).values_list('id', flat=True))
|
project_ids = set(self.user.get_queryset(Project).values_list('id', flat=True))
|
||||||
return qs.filter(project_id__in=project_ids)
|
return qs.filter(project_id__in=project_ids)
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_cancel(self, obj):
|
def can_cancel(self, obj):
|
||||||
return self.can_change(obj, {}) and obj.can_cancel
|
return self.can_change(obj, {}) and obj.can_cancel
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return obj and self.user in obj.project.admin_role
|
return obj and self.user in obj.project.admin_role
|
||||||
|
|
||||||
@@ -704,8 +731,7 @@ class JobTemplateAccess(BaseAccess):
|
|||||||
'credential', 'cloud_credential', 'next_schedule').all()
|
'credential', 'cloud_credential', 'next_schedule').all()
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
# you can only see the job templates that you have permission to launch.
|
return self.user in obj.read_role
|
||||||
return self.can_start(obj, validate_license=False)
|
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
'''
|
'''
|
||||||
@@ -847,10 +873,8 @@ class JobAccess(BaseAccess):
|
|||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
return obj.status == 'new' and self.can_read(obj) and self.can_add(data)
|
return obj.status == 'new' and self.can_read(obj) and self.can_add(data)
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
# Allow org admins and superusers to delete jobs
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.inventory.admin_role
|
return self.user in obj.inventory.admin_role
|
||||||
|
|
||||||
def can_start(self, obj):
|
def can_start(self, obj):
|
||||||
@@ -866,11 +890,12 @@ class JobAccess(BaseAccess):
|
|||||||
return self.user in obj.job_template.execute_role
|
return self.user in obj.job_template.execute_role
|
||||||
|
|
||||||
inventory_access = self.user in obj.inventory.use_role
|
inventory_access = self.user in obj.inventory.use_role
|
||||||
|
credential_access = self.user in obj.credential.use_role
|
||||||
|
|
||||||
org_access = self.user in obj.inventory.organization.admin_role
|
org_access = self.user in obj.inventory.organization.admin_role
|
||||||
project_access = obj.project is None or self.user in obj.project.admin_role
|
project_access = obj.project is None or self.user in obj.project.admin_role
|
||||||
|
|
||||||
return inventory_access and (org_access or project_access)
|
return inventory_access and credential_access and (org_access or project_access)
|
||||||
|
|
||||||
def can_cancel(self, obj):
|
def can_cancel(self, obj):
|
||||||
return self.can_read(obj) and obj.can_cancel
|
return self.can_read(obj) and obj.can_cancel
|
||||||
@@ -1146,18 +1171,16 @@ class ScheduleAccess(BaseAccess):
|
|||||||
UnifiedJobTemplate.objects.filter(Q(inventorysource__in=inventory_source_qs))
|
UnifiedJobTemplate.objects.filter(Q(inventorysource__in=inventory_source_qs))
|
||||||
return qs.filter(unified_job_template__in=unified_qs)
|
return qs.filter(unified_job_template__in=unified_qs)
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if obj and obj.unified_job_template:
|
if obj and obj.unified_job_template:
|
||||||
job_class = obj.unified_job_template
|
job_class = obj.unified_job_template
|
||||||
return self.user.can_access(type(job_class), 'read', obj.unified_job_template)
|
return self.user.can_access(type(job_class), 'read', obj.unified_job_template)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
pk = get_pk_from_dict(data, 'unified_job_template')
|
pk = get_pk_from_dict(data, 'unified_job_template')
|
||||||
obj = get_object_or_400(UnifiedJobTemplate, pk=pk)
|
obj = get_object_or_400(UnifiedJobTemplate, pk=pk)
|
||||||
if obj:
|
if obj:
|
||||||
@@ -1165,18 +1188,16 @@ class ScheduleAccess(BaseAccess):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if obj and obj.unified_job_template:
|
if obj and obj.unified_job_template:
|
||||||
job_class = obj.unified_job_template
|
job_class = obj.unified_job_template
|
||||||
return self.user.can_access(type(job_class), 'change', job_class, None)
|
return self.user.can_access(type(job_class), 'change', job_class, None)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if obj and obj.unified_job_template:
|
if obj and obj.unified_job_template:
|
||||||
job_class = obj.unified_job_template
|
job_class = obj.unified_job_template
|
||||||
return self.user.can_access(type(job_class), 'change', job_class, None)
|
return self.user.can_access(type(job_class), 'change', job_class, None)
|
||||||
@@ -1195,25 +1216,22 @@ class NotifierAccess(BaseAccess):
|
|||||||
return qs
|
return qs
|
||||||
return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, 'admin_role').all())
|
return self.model.objects.filter(organization__in=Organization.accessible_objects(self.user, 'admin_role').all())
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if obj.organization is not None:
|
if obj.organization is not None:
|
||||||
return self.user in obj.organization.admin_role
|
return self.user in obj.organization.admin_role
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if not data:
|
if not data:
|
||||||
return Organization.accessible_objects(self.user, 'admin_role').exists()
|
return Organization.accessible_objects(self.user, 'admin_role').exists()
|
||||||
org_pk = get_pk_from_dict(data, 'organization')
|
org_pk = get_pk_from_dict(data, 'organization')
|
||||||
org = get_object_or_400(Organization, pk=org_pk)
|
org = get_object_or_400(Organization, pk=org_pk)
|
||||||
return self.user in org.admin_role
|
return self.user in org.admin_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
org_pk = get_pk_from_dict(data, 'organization')
|
org_pk = get_pk_from_dict(data, 'organization')
|
||||||
if obj and org_pk and obj.organization.pk != org_pk:
|
if obj and org_pk and obj.organization.pk != org_pk:
|
||||||
org = get_object_or_400(Organization, pk=org_pk)
|
org = get_object_or_400(Organization, pk=org_pk)
|
||||||
@@ -1260,15 +1278,12 @@ class LabelAccess(BaseAccess):
|
|||||||
organization__in=Organization.accessible_objects(self.user, 'read_role')
|
organization__in=Organization.accessible_objects(self.user, 'read_role')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.organization.read_role
|
return self.user in obj.organization.read_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not data or '_method' in data: # So the browseable API will work?
|
if not data or '_method' in data: # So the browseable API will work?
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1276,10 +1291,8 @@ class LabelAccess(BaseAccess):
|
|||||||
org = get_object_or_400(Organization, pk=org_pk)
|
org = get_object_or_400(Organization, pk=org_pk)
|
||||||
return self.user in org.read_role
|
return self.user in org.read_role
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_change(self, obj, data):
|
def can_change(self, obj, data):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.can_add(data) is False:
|
if self.can_add(data) is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1376,26 +1389,10 @@ class CustomInventoryScriptAccess(BaseAccess):
|
|||||||
return self.model.objects.distinct().all()
|
return self.model.objects.distinct().all()
|
||||||
return self.model.accessible_objects(self.user, 'read_role').all()
|
return self.model.accessible_objects(self.user, 'read_role').all()
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return self.user in obj.read_role
|
return self.user in obj.read_role
|
||||||
|
|
||||||
def can_add(self, data):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_delete(self, obj):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class TowerSettingsAccess(BaseAccess):
|
class TowerSettingsAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -1409,17 +1406,6 @@ class TowerSettingsAccess(BaseAccess):
|
|||||||
|
|
||||||
model = TowerSettings
|
model = TowerSettings
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return self.model.objects.all()
|
|
||||||
return self.model.objects.none()
|
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
|
||||||
return self.user.is_superuser
|
|
||||||
|
|
||||||
def can_delete(self, obj):
|
|
||||||
return self.user.is_superuser
|
|
||||||
|
|
||||||
|
|
||||||
class RoleAccess(BaseAccess):
|
class RoleAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -1432,14 +1418,6 @@ class RoleAccess(BaseAccess):
|
|||||||
|
|
||||||
model = Role
|
model = Role
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
if self.user.is_superuser:
|
|
||||||
return self.model.objects.all()
|
|
||||||
return Role.objects.none()
|
|
||||||
|
|
||||||
def can_change(self, obj, data):
|
|
||||||
return self.user.is_superuser
|
|
||||||
|
|
||||||
def can_read(self, obj):
|
def can_read(self, obj):
|
||||||
if not obj:
|
if not obj:
|
||||||
return False
|
return False
|
||||||
@@ -1463,9 +1441,8 @@ class RoleAccess(BaseAccess):
|
|||||||
skip_sub_obj_read_check=False):
|
skip_sub_obj_read_check=False):
|
||||||
return self.can_unattach(obj, sub_obj, relationship)
|
return self.can_unattach(obj, sub_obj, relationship)
|
||||||
|
|
||||||
|
@check_superuser
|
||||||
def can_unattach(self, obj, sub_obj, relationship):
|
def can_unattach(self, obj, sub_obj, relationship):
|
||||||
if self.user.is_superuser:
|
|
||||||
return True
|
|
||||||
if obj.object_id and \
|
if obj.object_id and \
|
||||||
isinstance(obj.content_object, ResourceMixin) and \
|
isinstance(obj.content_object, ResourceMixin) and \
|
||||||
self.user in obj.content_object.admin_role:
|
self.user in obj.content_object.admin_role:
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ from django.db.models.fields.related import (
|
|||||||
ReverseManyRelatedObjectsDescriptor,
|
ReverseManyRelatedObjectsDescriptor,
|
||||||
)
|
)
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.timezone import now
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.rbac import batch_role_ancestor_rebuilding
|
from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
|
||||||
from awx.main.utils import get_current_apps
|
from awx.main.utils import get_current_apps
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
__all__ = ['AutoOneToOneField', 'ImplicitRoleField']
|
||||||
|
|
||||||
|
|
||||||
@@ -92,9 +92,7 @@ class ImplicitRoleDescriptor(ReverseSingleRelatedObjectDescriptor):
|
|||||||
class ImplicitRoleField(models.ForeignKey):
|
class ImplicitRoleField(models.ForeignKey):
|
||||||
"""Implicitly creates a role entry for a resource"""
|
"""Implicitly creates a role entry for a resource"""
|
||||||
|
|
||||||
def __init__(self, role_name=None, role_description=None, parent_role=None, *args, **kwargs):
|
def __init__(self, parent_role=None, *args, **kwargs):
|
||||||
self.role_name = role_name
|
|
||||||
self.role_description = role_description if role_description else ""
|
|
||||||
self.parent_role = parent_role
|
self.parent_role = parent_role
|
||||||
|
|
||||||
kwargs.setdefault('to', 'Role')
|
kwargs.setdefault('to', 'Role')
|
||||||
@@ -104,8 +102,6 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
|
|
||||||
def deconstruct(self):
|
def deconstruct(self):
|
||||||
name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct()
|
name, path, args, kwargs = super(ImplicitRoleField, self).deconstruct()
|
||||||
kwargs['role_name'] = self.role_name
|
|
||||||
kwargs['role_description'] = self.role_description
|
|
||||||
kwargs['parent_role'] = self.parent_role
|
kwargs['parent_role'] = self.parent_role
|
||||||
return name, path, args, kwargs
|
return name, path, args, kwargs
|
||||||
|
|
||||||
@@ -190,11 +186,7 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
if cur_role is None:
|
if cur_role is None:
|
||||||
missing_roles.append(
|
missing_roles.append(
|
||||||
Role_(
|
Role_(
|
||||||
created=now(),
|
|
||||||
modified=now(),
|
|
||||||
role_field=implicit_role_field.name,
|
role_field=implicit_role_field.name,
|
||||||
name=implicit_role_field.role_name,
|
|
||||||
description=implicit_role_field.role_description,
|
|
||||||
content_type_id=ct_id,
|
content_type_id=ct_id,
|
||||||
object_id=instance.id
|
object_id=instance.id
|
||||||
)
|
)
|
||||||
@@ -208,7 +200,7 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
updates[role.role_field] = role.id
|
updates[role.role_field] = role.id
|
||||||
role_ids.append(role.id)
|
role_ids.append(role.id)
|
||||||
type(instance).objects.filter(pk=instance.pk).update(**updates)
|
type(instance).objects.filter(pk=instance.pk).update(**updates)
|
||||||
Role_._simultaneous_ancestry_rebuild(role_ids)
|
Role.rebuild_role_ancestor_list(role_ids, [])
|
||||||
|
|
||||||
# Update parentage if necessary
|
# Update parentage if necessary
|
||||||
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
|
||||||
@@ -247,12 +239,7 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
if qs.count() >= 1:
|
if qs.count() >= 1:
|
||||||
role = qs[0]
|
role = qs[0]
|
||||||
else:
|
else:
|
||||||
role = Role_.objects.create(created=now(),
|
role = Role_.objects.create(singleton_name=singleton_name, role_field=singleton_name)
|
||||||
modified=now(),
|
|
||||||
role_field=path,
|
|
||||||
singleton_name=singleton_name,
|
|
||||||
name=singleton_name,
|
|
||||||
description=singleton_name)
|
|
||||||
parents = [role.id]
|
parents = [role.id]
|
||||||
else:
|
else:
|
||||||
parents = resolve_role_field(instance, path)
|
parents = resolve_role_field(instance, path)
|
||||||
@@ -269,4 +256,4 @@ class ImplicitRoleField(models.ForeignKey):
|
|||||||
Role_ = get_current_apps().get_model('main', 'Role')
|
Role_ = get_current_apps().get_model('main', 'Role')
|
||||||
child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)]
|
child_ids = [x for x in Role_.parents.through.objects.filter(to_role_id__in=role_ids).distinct().values_list('from_role_id', flat=True)]
|
||||||
Role_.objects.filter(id__in=role_ids).delete()
|
Role_.objects.filter(id__in=role_ids).delete()
|
||||||
Role_._simultaneous_ancestry_rebuild(child_ids)
|
Role.rebuild_role_ancestor_list([], child_ids)
|
||||||
|
|||||||
@@ -468,9 +468,7 @@ def load_inventory_source(source, all_group=None, group_filter_re=None,
|
|||||||
'''
|
'''
|
||||||
# Sanity check: We sanitize these module names for our API but Ansible proper doesn't follow
|
# Sanity check: We sanitize these module names for our API but Ansible proper doesn't follow
|
||||||
# good naming conventions
|
# good naming conventions
|
||||||
if source == 'azure':
|
source = source.replace('azure.py', 'windows_azure.py')
|
||||||
source = 'windows_azure'
|
|
||||||
|
|
||||||
logger.debug('Analyzing type of source: %s', source)
|
logger.debug('Analyzing type of source: %s', source)
|
||||||
original_all_group = all_group
|
original_all_group = all_group
|
||||||
if not os.path.exists(source):
|
if not os.path.exists(source):
|
||||||
|
|||||||
@@ -2,21 +2,21 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import taggit.managers
|
|
||||||
import awx.main.fields
|
import awx.main.fields
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('taggit', '0002_auto_20150616_2121'),
|
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('main', '0007_v300_active_flag_removal'),
|
('main', '0007_v300_active_flag_removal'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
#
|
||||||
|
# Patch up existing
|
||||||
|
#
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
'Organization',
|
'Organization',
|
||||||
'admins',
|
'admins',
|
||||||
@@ -47,300 +47,6 @@ class Migration(migrations.Migration):
|
|||||||
name='deprecated_projects',
|
name='deprecated_projects',
|
||||||
field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True),
|
field=models.ManyToManyField(related_name='deprecated_teams', to='main.Project', blank=True),
|
||||||
),
|
),
|
||||||
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='RoleAncestorEntry',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('role_field', models.TextField()),
|
|
||||||
('content_type_id', models.PositiveIntegerField(null=False)),
|
|
||||||
('object_id', models.PositiveIntegerField(null=False)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'main_rbac_role_ancestors',
|
|
||||||
'verbose_name_plural': 'role_ancestors',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Role',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('created', models.DateTimeField(default=None, editable=False)),
|
|
||||||
('modified', models.DateTimeField(default=None, editable=False)),
|
|
||||||
('description', models.TextField(default=b'', blank=True)),
|
|
||||||
('name', models.CharField(max_length=512)),
|
|
||||||
('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)),
|
|
||||||
('object_id', models.PositiveIntegerField(default=None, null=True)),
|
|
||||||
('ancestors', models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role')),
|
|
||||||
('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)),
|
|
||||||
('created_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_created+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
|
||||||
('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)),
|
|
||||||
('modified_by', models.ForeignKey(related_name="{u'class': 'role', u'app_label': 'main'}(class)s_modified+", on_delete=django.db.models.deletion.SET_NULL, default=None, editable=False, to=settings.AUTH_USER_MODEL, null=True)),
|
|
||||||
('parents', models.ManyToManyField(related_name='children', to='main.Role')),
|
|
||||||
('implicit_parents', models.TextField(null=False, default=b'[]')),
|
|
||||||
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'main_rbac_roles',
|
|
||||||
'verbose_name_plural': 'roles',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='roleancestorentry',
|
|
||||||
name='ancestor',
|
|
||||||
field=models.ForeignKey(related_name='+', to='main.Role'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='roleancestorentry',
|
|
||||||
name='descendent',
|
|
||||||
field=models.ForeignKey(related_name='+', to='main.Role'),
|
|
||||||
),
|
|
||||||
migrations.AlterIndexTogether(
|
|
||||||
name='roleancestorentry',
|
|
||||||
index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field')]),
|
|
||||||
),
|
|
||||||
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='credential',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Auditor of the credential', parent_role=[b'singleton:System Auditor'], to='main.Role', role_name=b'Credential Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='credential',
|
|
||||||
name='owner_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Owner of the credential', parent_role=[b'singleton:System Administrator'], to='main.Role', role_name=b'Credential Owner', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='credential',
|
|
||||||
name='use_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Credential User', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'CustomInventory Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'CustomInventory Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.member_role', to='main.Role', role_name=b'CustomInventory Member', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', role_name=b'Inventory Group Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', role_name=b'Inventory Group Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.executor_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='update_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.updater_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this inventory', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Inventory Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Inventory Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=None, to='main.Role', role_name=b'Inventory Executor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='update_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=None, to='main.Role', role_name=b'Inventory Updater', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='use_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=None, to='main.Role', role_name=b'Inventory User', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobtemplate',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Full access to all settings', parent_role=[(b'project.admin_role', b'inventory.admin_role')], to='main.Role', role_name=b'Job Template Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobtemplate',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read-only access to all settings', parent_role=[(b'project.auditor_role', b'inventory.auditor_role')], to='main.Role', role_name=b'Job Template Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobtemplate',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=None, to='main.Role', role_name=b'Job Template Runner', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='organization',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage all aspects of this organization', parent_role=b'singleton:System Administrator', to='main.Role', role_name=b'Organization Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='organization',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this organization', parent_role=b'singleton:System Auditor', to='main.Role', role_name=b'Organization Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='organization',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this organization', parent_role=b'admin_role', to='main.Role', role_name=b'Organization Member', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this project', parent_role=[b'organization.admin_role', b'singleton:System Administrator'], to='main.Role', role_name=b'Project Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this project', parent_role=[b'organization.auditor_role', b'singleton:System Auditor'], to='main.Role', role_name=b'Project Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=None, to='main.Role', role_name=b'Project Member', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='scm_update_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update this project from the source control management system', parent_role=b'admin_role', to='main.Role', role_name=b'Project Updater', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='team',
|
|
||||||
name='admin_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May manage this team', parent_role=b'organization.admin_role', to='main.Role', role_name=b'Team Administrator', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='team',
|
|
||||||
name='auditor_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read all settings associated with this team', parent_role=b'organization.auditor_role', to='main.Role', role_name=b'Team Auditor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='team',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'A member of this team', to='main.Role', role_name=b'Team Member', null=b'True'),
|
|
||||||
),
|
|
||||||
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='credential',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May read this credential', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', role_name=b'Credential REad', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='custominventoryscript',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view but not modify this inventory', parent_role=[b'auditor_role', b'member_role', b'admin_role'], to='main.Role', role_name=b'CustomInventory Read', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='adhoc_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='group',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='adhoc_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute ad hoc commands against this inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Ad Hoc', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May view this inventory', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', role_name=b'Read', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jobtemplate',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'execute_role', b'auditor_role', b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='organization',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read an organization', parent_role=[b'member_role', b'auditor_role'], to='main.Role', role_name=b'Organization Read Access', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='project',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Read access to this project', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', role_name=b'Project Read Access', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='role',
|
|
||||||
name='role_field',
|
|
||||||
field=models.TextField(default=b''),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='team',
|
|
||||||
name='read_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Can view this team', parent_role=[b'admin_role', b'auditor_role', b'member_role'], to='main.Role', role_name=b'Read', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='credential',
|
|
||||||
name='use_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this credential, but not read sensitive portions or modify it', parent_role=[b'owner_role'], to='main.Role', role_name=b'Credential User', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='group',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', role_name=b'Inventory Group Executor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='group',
|
|
||||||
name='update_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', role_name=b'Inventory Group Updater', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May execute jobs against this inventory', parent_role=b'adhoc_role', to='main.Role', role_name=b'Inventory Executor', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='update_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May update the inventory', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory Updater', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='use_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May use this inventory, but not read sensitive portions or modify it', parent_role=[b'admin_role'], to='main.Role', role_name=b'Inventory User', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='jobtemplate',
|
|
||||||
name='execute_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'May run the job template', parent_role=[b'admin_role'], to='main.Role', role_name=b'Job Template Runner', null=b'True'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='project',
|
|
||||||
name='member_role',
|
|
||||||
field=awx.main.fields.ImplicitRoleField(related_name='+', role_description=b'Implies membership within this project', parent_role=b'admin_role', to='main.Role', role_name=b'Project Member', null=b'True'),
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name='organization',
|
model_name='organization',
|
||||||
old_name='projects',
|
old_name='projects',
|
||||||
@@ -380,4 +86,245 @@ class Migration(migrations.Migration):
|
|||||||
name='credential',
|
name='credential',
|
||||||
unique_together=set([]),
|
unique_together=set([]),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# New RBAC models and fields
|
||||||
|
#
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Role',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('role_field', models.TextField()),
|
||||||
|
('singleton_name', models.TextField(default=None, unique=True, null=True, db_index=True)),
|
||||||
|
('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('parents', models.ManyToManyField(related_name='children', to='main.Role')),
|
||||||
|
('implicit_parents', models.TextField(default=b'[]')),
|
||||||
|
('content_type', models.ForeignKey(default=None, to='contenttypes.ContentType', null=True)),
|
||||||
|
('object_id', models.PositiveIntegerField(default=None, null=True)),
|
||||||
|
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'main_rbac_roles',
|
||||||
|
'verbose_name_plural': 'roles',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RoleAncestorEntry',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('role_field', models.TextField()),
|
||||||
|
('content_type_id', models.PositiveIntegerField()),
|
||||||
|
('object_id', models.PositiveIntegerField()),
|
||||||
|
('ancestor', models.ForeignKey(related_name='+', to='main.Role')),
|
||||||
|
('descendent', models.ForeignKey(related_name='+', to='main.Role')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'main_rbac_role_ancestors',
|
||||||
|
'verbose_name_plural': 'role_ancestors',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='role',
|
||||||
|
name='ancestors',
|
||||||
|
field=models.ManyToManyField(related_name='descendents', through='main.RoleAncestorEntry', to='main.Role'),
|
||||||
|
),
|
||||||
|
migrations.AlterIndexTogether(
|
||||||
|
name='role',
|
||||||
|
index_together=set([('content_type', 'object_id')]),
|
||||||
|
),
|
||||||
|
migrations.AlterIndexTogether(
|
||||||
|
name='roleancestorentry',
|
||||||
|
index_together=set([('ancestor', 'content_type_id', 'object_id'), ('ancestor', 'content_type_id', 'role_field'), ('ancestor', 'descendent')]),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='credential',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_auditor'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='credential',
|
||||||
|
name='owner_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'singleton:system_administrator'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='credential',
|
||||||
|
name='use_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'owner_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='credential',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'use_role', b'auditor_role', b'owner_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='member_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.member_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominventoryscript',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'member_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.admin_role', b'parents.admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='adhoc_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.adhoc_role', b'parents.adhoc_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.auditor_role', b'parents.auditor_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='execute_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.execute_role', b'parents.execute_role', b'adhoc_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='update_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'inventory.update_role', b'parents.update_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='group',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'update_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='adhoc_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='execute_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'adhoc_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='update_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='use_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventory',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'auditor_role', b'execute_role', b'update_role', b'use_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[(b'project.admin_role', b'inventory.admin_role')], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[(b'project.auditor_role', b'inventory.auditor_role')], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='execute_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'execute_role', b'auditor_role', b'admin_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organization',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'singleton:system_administrator', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organization',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'singleton:system_auditor', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organization',
|
||||||
|
name='member_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='organization',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'organization.admin_role', b'singleton:system_administrator'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'organization.auditor_role', b'singleton:system_auditor'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='member_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='scm_update_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'member_role', b'auditor_role', b'scm_update_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='admin_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.admin_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='auditor_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=b'organization.auditor_role', to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='member_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=None, to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='team',
|
||||||
|
name='read_role',
|
||||||
|
field=awx.main.fields.ImplicitRoleField(related_name='+', parent_role=[b'admin_role', b'auditor_role', b'member_role'], to='main.Role', null=b'True'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from awx.main.migrations import _rbac as rbac
|
from awx.main.migrations import _rbac as rbac
|
||||||
|
from awx.main.migrations import _migration_utils as migration_utils
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -12,11 +13,14 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(rbac.init_rbac_migration),
|
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
||||||
migrations.RunPython(rbac.migrate_users),
|
migrations.RunPython(rbac.migrate_users),
|
||||||
|
migrations.RunPython(rbac.create_roles),
|
||||||
migrations.RunPython(rbac.migrate_organization),
|
migrations.RunPython(rbac.migrate_organization),
|
||||||
migrations.RunPython(rbac.migrate_team),
|
migrations.RunPython(rbac.migrate_team),
|
||||||
migrations.RunPython(rbac.migrate_inventory),
|
migrations.RunPython(rbac.migrate_inventory),
|
||||||
migrations.RunPython(rbac.migrate_projects),
|
migrations.RunPython(rbac.migrate_projects),
|
||||||
migrations.RunPython(rbac.migrate_credential),
|
migrations.RunPython(rbac.migrate_credential),
|
||||||
|
migrations.RunPython(rbac.migrate_job_templates),
|
||||||
|
migrations.RunPython(rbac.rebuild_role_hierarchy),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from awx.main.migrations import _ask_for_variables as ask_for_variables
|
from awx.main.migrations import _ask_for_variables as ask_for_variables
|
||||||
|
from awx.main.migrations import _migration_utils as migration_utils
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -12,5 +13,6 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
||||||
migrations.RunPython(ask_for_variables.migrate_credential),
|
migrations.RunPython(ask_for_variables.migrate_credential),
|
||||||
]
|
]
|
||||||
|
|||||||
32
awx/main/migrations/0020_v300_labels_changes.py
Normal file
32
awx/main/migrations/0020_v300_labels_changes.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0019_v300_new_azure_credential'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='job',
|
||||||
|
name='labels',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='labels',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='unifiedjob',
|
||||||
|
name='labels',
|
||||||
|
field=models.ManyToManyField(related_name='unifiedjob_labels', to='main.Label', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='unifiedjobtemplate',
|
||||||
|
name='labels',
|
||||||
|
field=models.ManyToManyField(related_name='unifiedjobtemplate_labels', to='main.Label', blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
awx/main/migrations/0021_v300_activity_stream.py
Normal file
19
awx/main/migrations/0021_v300_activity_stream.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0020_v300_labels_changes'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activitystream',
|
||||||
|
name='role',
|
||||||
|
field=models.ManyToManyField(to='main.Role', blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
34
awx/main/migrations/0022_v300_adhoc_extravars.py
Normal file
34
awx/main/migrations/0022_v300_adhoc_extravars.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0021_v300_activity_stream'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='adhoccommand',
|
||||||
|
name='extra_vars',
|
||||||
|
field=models.TextField(default=b'', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='credential',
|
||||||
|
name='kind',
|
||||||
|
field=models.CharField(default=b'ssh', max_length=32, choices=[(b'ssh', 'Machine'), (b'net', 'Network'), (b'scm', 'Source Control'), (b'aws', 'Amazon Web Services'), (b'rax', 'Rackspace'), (b'vmware', 'VMware vCenter'), (b'foreman', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'openstack', 'OpenStack')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventorysource',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'Local File, Directory or Script'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'foreman', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inventoryupdate',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(default=b'', max_length=32, blank=True, choices=[(b'', 'Manual'), (b'file', 'Local File, Directory or Script'), (b'rax', 'Rackspace Cloud Servers'), (b'ec2', 'Amazon EC2'), (b'gce', 'Google Compute Engine'), (b'azure', 'Microsoft Azure Classic (deprecated)'), (b'azure_rm', 'Microsoft Azure Resource Manager'), (b'vmware', 'VMware vCenter'), (b'foreman', 'Red Hat Satellite 6'), (b'cloudforms', 'Red Hat CloudForms'), (b'openstack', 'OpenStack'), (b'custom', 'Custom Script')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
11
awx/main/migrations/_migration_utils.py
Normal file
11
awx/main/migrations/_migration_utils.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from awx.main.utils import set_current_apps
|
||||||
|
|
||||||
|
|
||||||
|
def set_current_apps_for_migrations(apps, schema_editor):
|
||||||
|
'''
|
||||||
|
This is necessary for migrations which do explicit saves on any model that
|
||||||
|
has an ImplicitRoleFIeld (which generally means anything that has
|
||||||
|
some RBAC bindings associated with it). This sets the current 'apps' that
|
||||||
|
the ImplicitRoleFIeld should be using when creating new roles.
|
||||||
|
'''
|
||||||
|
set_current_apps(apps)
|
||||||
@@ -206,9 +206,9 @@ class UserAccess(BaseAccess):
|
|||||||
return qs
|
return qs
|
||||||
return qs.filter(
|
return qs.filter(
|
||||||
Q(pk=self.user.pk) |
|
Q(pk=self.user.pk) |
|
||||||
Q(organizations__in=self.user.deprecated_admin_of_organizations) |
|
Q(deprecated_organizations__in=self.user.deprecated_admin_of_organizations.all()) |
|
||||||
Q(organizations__in=self.user.deprecated_organizations) |
|
Q(deprecated_organizations__in=self.user.deprecated_organizations.all()) |
|
||||||
Q(deprecated_teams__in=self.user.deprecated_teams)
|
Q(deprecated_teams__in=self.user.deprecated_teams.all())
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
@@ -563,18 +563,18 @@ class CredentialAccess(BaseAccess):
|
|||||||
# If the user is a superuser, and therefore can see everything, this
|
# If the user is a superuser, and therefore can see everything, this
|
||||||
# is also sufficient, and we are done.
|
# is also sufficient, and we are done.
|
||||||
qs = self.model.objects.distinct()
|
qs = self.model.objects.distinct()
|
||||||
qs = qs.select_related('created_by', 'modified_by', 'user', 'team')
|
qs = qs.select_related('created_by', 'modified_by')
|
||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
# Get the list of organizations for which the user is an admin
|
# Get the list of organizations for which the user is an admin
|
||||||
orgs_as_admin_ids = set(self.user.deprecated_admin_of_organizations.values_list('id', flat=True))
|
orgs_as_admin_ids = set(self.user.deprecated_admin_of_organizations.values_list('id', flat=True))
|
||||||
return qs.filter(
|
return qs.filter(
|
||||||
Q(user=self.user) |
|
Q(deprecated_user=self.user) |
|
||||||
Q(user__deprecated_organizations__id__in=orgs_as_admin_ids) |
|
Q(deprecated_user__deprecated_organizations__id__in=orgs_as_admin_ids) |
|
||||||
Q(user__deprecated_admin_of_organizations__id__in=orgs_as_admin_ids) |
|
Q(deprecated_user__deprecated_admin_of_organizations__id__in=orgs_as_admin_ids) |
|
||||||
Q(team__organization__id__in=orgs_as_admin_ids) |
|
Q(deprecated_team__organization__id__in=orgs_as_admin_ids) |
|
||||||
Q(team__deprecated_users__in=[self.user])
|
Q(deprecated_team__deprecated_users__in=[self.user])
|
||||||
)
|
)
|
||||||
|
|
||||||
def can_add(self, data):
|
def can_add(self, data):
|
||||||
@@ -597,22 +597,22 @@ class CredentialAccess(BaseAccess):
|
|||||||
return False
|
return False
|
||||||
if self.user == obj.created_by:
|
if self.user == obj.created_by:
|
||||||
return True
|
return True
|
||||||
if obj.user:
|
if obj.deprecated_user:
|
||||||
if self.user == obj.user:
|
if self.user == obj.deprecated_user:
|
||||||
return True
|
return True
|
||||||
if obj.user.deprecated_organizations.filter(deprecated_admins__in=[self.user]).exists():
|
if obj.deprecated_user.deprecated_organizations.filter(deprecated_admins__in=[self.user]).exists():
|
||||||
return True
|
return True
|
||||||
if obj.user.deprecated_admin_of_organizations.filter(deprecated_admins__in=[self.user]).exists():
|
if obj.deprecated_user.deprecated_admin_of_organizations.filter(deprecated_admins__in=[self.user]).exists():
|
||||||
return True
|
return True
|
||||||
if obj.team:
|
if obj.deprecated_team:
|
||||||
if self.user in obj.team.organization.deprecated_admins.all():
|
if self.user in obj.deprecated_team.organization.deprecated_admins.all():
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
# Unassociated credentials may be marked deleted by anyone, though we
|
# Unassociated credentials may be marked deleted by anyone, though we
|
||||||
# shouldn't ever end up with those.
|
# shouldn't ever end up with those.
|
||||||
if obj.user is None and obj.team is None:
|
if obj.deprecated_user is None and obj.deprecated_team is None:
|
||||||
return True
|
return True
|
||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from time import time
|
||||||
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.timezone import now
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from awx.main.utils import getattrd, set_current_apps
|
from awx.main.utils import getattrd
|
||||||
|
from awx.main.models.rbac import Role, batch_role_ancestor_rebuilding
|
||||||
|
|
||||||
import _old_access as old_access
|
import _old_access as old_access
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -27,8 +28,35 @@ def log_migration(wrapped):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@log_migration
|
@log_migration
|
||||||
def init_rbac_migration(apps, schema_editor):
|
def create_roles(apps, schema_editor):
|
||||||
set_current_apps(apps)
|
'''
|
||||||
|
Implicit role creation happens in our post_save hook for all of our
|
||||||
|
resources. Here we iterate through all of our resource types and call
|
||||||
|
.save() to ensure all that happens for every object in the system before we
|
||||||
|
get busy with the actual migration work.
|
||||||
|
|
||||||
|
This gets run after migrate_users, which does role creation for users a
|
||||||
|
little differently.
|
||||||
|
'''
|
||||||
|
|
||||||
|
models = [
|
||||||
|
apps.get_model('main', m) for m in [
|
||||||
|
'Organization',
|
||||||
|
'Team',
|
||||||
|
'Inventory',
|
||||||
|
'Group',
|
||||||
|
'Project',
|
||||||
|
'Credential',
|
||||||
|
'CustomInventoryScript',
|
||||||
|
'JobTemplate',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
with batch_role_ancestor_rebuilding():
|
||||||
|
for model in models:
|
||||||
|
for obj in model.objects.iterator():
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
|
||||||
@log_migration
|
@log_migration
|
||||||
def migrate_users(apps, schema_editor):
|
def migrate_users(apps, schema_editor):
|
||||||
@@ -44,9 +72,7 @@ def migrate_users(apps, schema_editor):
|
|||||||
logger.info(smart_text(u"found existing role for user: {}".format(user.username)))
|
logger.info(smart_text(u"found existing role for user: {}".format(user.username)))
|
||||||
except Role.DoesNotExist:
|
except Role.DoesNotExist:
|
||||||
role = Role.objects.create(
|
role = Role.objects.create(
|
||||||
created=now(),
|
role_field='admin_role',
|
||||||
modified=now(),
|
|
||||||
singleton_name = smart_text(u'{}-admin_role'.format(user.username)),
|
|
||||||
content_type = user_content_type,
|
content_type = user_content_type,
|
||||||
object_id = user.id
|
object_id = user.id
|
||||||
)
|
)
|
||||||
@@ -54,14 +80,12 @@ def migrate_users(apps, schema_editor):
|
|||||||
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
|
logger.info(smart_text(u"migrating to new role for user: {}".format(user.username)))
|
||||||
|
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
if Role.objects.filter(singleton_name='System Administrator').exists():
|
if Role.objects.filter(singleton_name='system_administrator').exists():
|
||||||
sa_role = Role.objects.get(singleton_name='System Administrator')
|
sa_role = Role.objects.get(singleton_name='system_administrator')
|
||||||
else:
|
else:
|
||||||
sa_role = Role.objects.create(
|
sa_role = Role.objects.create(
|
||||||
created=now(),
|
singleton_name='system_administrator',
|
||||||
modified=now(),
|
role_field='system_administrator'
|
||||||
singleton_name='System Administrator',
|
|
||||||
name='System Administrator'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sa_role.members.add(user)
|
sa_role.members.add(user)
|
||||||
@@ -71,19 +95,17 @@ def migrate_users(apps, schema_editor):
|
|||||||
def migrate_organization(apps, schema_editor):
|
def migrate_organization(apps, schema_editor):
|
||||||
Organization = apps.get_model('main', "Organization")
|
Organization = apps.get_model('main', "Organization")
|
||||||
for org in Organization.objects.iterator():
|
for org in Organization.objects.iterator():
|
||||||
org.save() # force creates missing roles
|
|
||||||
for admin in org.deprecated_admins.all():
|
for admin in org.deprecated_admins.all():
|
||||||
org.admin_role.members.add(admin)
|
org.admin_role.members.add(admin)
|
||||||
logger.info(smart_text(u"added admin: {}, {}".format(org.name, admin.username)))
|
logger.info(smart_text(u"added admin: {}, {}".format(org.name, admin.username)))
|
||||||
for user in org.deprecated_users.all():
|
for user in org.deprecated_users.all():
|
||||||
org.auditor_role.members.add(user)
|
org.member_role.members.add(user)
|
||||||
logger.info(smart_text(u"added auditor: {}, {}".format(org.name, user.username)))
|
logger.info(smart_text(u"added member: {}, {}".format(org.name, user.username)))
|
||||||
|
|
||||||
@log_migration
|
@log_migration
|
||||||
def migrate_team(apps, schema_editor):
|
def migrate_team(apps, schema_editor):
|
||||||
Team = apps.get_model('main', 'Team')
|
Team = apps.get_model('main', 'Team')
|
||||||
for t in Team.objects.iterator():
|
for t in Team.objects.iterator():
|
||||||
t.save()
|
|
||||||
for user in t.deprecated_users.all():
|
for user in t.deprecated_users.all():
|
||||||
t.member_role.members.add(user)
|
t.member_role.members.add(user)
|
||||||
logger.info(smart_text(u"team: {}, added user: {}".format(t.name, user.username)))
|
logger.info(smart_text(u"team: {}, added user: {}".format(t.name, user.username)))
|
||||||
@@ -103,8 +125,6 @@ def attrfunc(attr_path):
|
|||||||
|
|
||||||
def _update_credential_parents(org, cred):
|
def _update_credential_parents(org, cred):
|
||||||
org.admin_role.children.add(cred.owner_role)
|
org.admin_role.children.add(cred.owner_role)
|
||||||
org.member_role.children.add(cred.use_role)
|
|
||||||
cred.deprecated_user, cred.deprecated_team = None, None
|
|
||||||
cred.save()
|
cred.save()
|
||||||
|
|
||||||
def _discover_credentials(instances, cred, orgfunc):
|
def _discover_credentials(instances, cred, orgfunc):
|
||||||
@@ -122,7 +142,12 @@ def _discover_credentials(instances, cred, orgfunc):
|
|||||||
'''
|
'''
|
||||||
orgs = defaultdict(list)
|
orgs = defaultdict(list)
|
||||||
for inst in instances:
|
for inst in instances:
|
||||||
orgs[orgfunc(inst)].append(inst)
|
try:
|
||||||
|
orgs[orgfunc(inst)].append(inst)
|
||||||
|
except AttributeError:
|
||||||
|
# JobTemplate.inventory can be NULL sometimes, eg when an inventory
|
||||||
|
# has been deleted. This protects against that.
|
||||||
|
pass
|
||||||
|
|
||||||
if len(orgs) == 1:
|
if len(orgs) == 1:
|
||||||
_update_credential_parents(orgfunc(instances[0]), cred)
|
_update_credential_parents(orgfunc(instances[0]), cred)
|
||||||
@@ -136,7 +161,6 @@ def _discover_credentials(instances, cred, orgfunc):
|
|||||||
cred.save()
|
cred.save()
|
||||||
|
|
||||||
# Unlink the old information from the new credential
|
# Unlink the old information from the new credential
|
||||||
cred.deprecated_user, cred.deprecated_team = None, None
|
|
||||||
cred.owner_role, cred.use_role = None, None
|
cred.owner_role, cred.use_role = None, None
|
||||||
cred.save()
|
cred.save()
|
||||||
|
|
||||||
@@ -150,43 +174,32 @@ def migrate_credential(apps, schema_editor):
|
|||||||
Credential = apps.get_model('main', "Credential")
|
Credential = apps.get_model('main', "Credential")
|
||||||
JobTemplate = apps.get_model('main', 'JobTemplate')
|
JobTemplate = apps.get_model('main', 'JobTemplate')
|
||||||
Project = apps.get_model('main', 'Project')
|
Project = apps.get_model('main', 'Project')
|
||||||
Role = apps.get_model('main', 'Role')
|
|
||||||
User = apps.get_model('auth', 'User')
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
InventorySource = apps.get_model('main', 'InventorySource')
|
||||||
ContentType = apps.get_model('contenttypes', "ContentType")
|
|
||||||
user_content_type = ContentType.objects.get_for_model(User)
|
|
||||||
|
|
||||||
for cred in Credential.objects.iterator():
|
for cred in Credential.objects.iterator():
|
||||||
cred.save()
|
results = [x for x in JobTemplate.objects.filter(Q(credential=cred) | Q(cloud_credential=cred)).all()] + \
|
||||||
results = (JobTemplate.objects.filter(Q(credential=cred) | Q(cloud_credential=cred)).all() or
|
[x for x in InventorySource.objects.filter(credential=cred).all()]
|
||||||
InventorySource.objects.filter(credential=cred).all())
|
if cred.deprecated_team is not None and results:
|
||||||
if results:
|
|
||||||
if len(results) == 1:
|
if len(results) == 1:
|
||||||
_update_credential_parents(results[0].inventory.organization, cred)
|
_update_credential_parents(results[0].inventory.organization, cred)
|
||||||
else:
|
else:
|
||||||
_discover_credentials(results, cred, attrfunc('inventory.organization'))
|
_discover_credentials(results, cred, attrfunc('inventory.organization'))
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at organization level".format(cred.name, cred.kind, cred.host)))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at organization level".format(cred.name, cred.kind, cred.host)))
|
||||||
continue
|
|
||||||
|
|
||||||
projs = Project.objects.filter(credential=cred).all()
|
projs = Project.objects.filter(credential=cred).all()
|
||||||
if projs:
|
if cred.deprecated_team is not None and projs:
|
||||||
if len(projs) == 1:
|
if len(projs) == 1:
|
||||||
_update_credential_parents(projs[0].organization, cred)
|
_update_credential_parents(projs[0].organization, cred)
|
||||||
else:
|
else:
|
||||||
_discover_credentials(projs, cred, attrfunc('organization'))
|
_discover_credentials(projs, cred, attrfunc('organization'))
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at organization level".format(cred.name, cred.kind, cred.host)))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at organization level".format(cred.name, cred.kind, cred.host)))
|
||||||
continue
|
|
||||||
|
|
||||||
if cred.deprecated_team is not None:
|
if cred.deprecated_team is not None:
|
||||||
cred.deprecated_team.admin_role.children.add(cred.owner_role)
|
cred.deprecated_team.member_role.children.add(cred.owner_role)
|
||||||
cred.deprecated_team.member_role.children.add(cred.use_role)
|
|
||||||
cred.deprecated_user, cred.deprecated_team = None, None
|
|
||||||
cred.save()
|
cred.save()
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host)))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host)))
|
||||||
elif cred.deprecated_user is not None:
|
elif cred.deprecated_user is not None:
|
||||||
user_admin_role = Role.objects.get(content_type=user_content_type, object_id=cred.deprecated_user.id)
|
cred.owner_role.members.add(cred.deprecated_user)
|
||||||
user_admin_role.children.add(cred.owner_role)
|
|
||||||
cred.deprecated_user, cred.deprecated_team = None, None
|
|
||||||
cred.save()
|
cred.save()
|
||||||
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host, )))
|
logger.info(smart_text(u"added Credential(name={}, kind={}, host={}) at user level".format(cred.name, cred.kind, cred.host, )))
|
||||||
else:
|
else:
|
||||||
@@ -205,14 +218,13 @@ def migrate_inventory(apps, schema_editor):
|
|||||||
return inventory.auditor_role
|
return inventory.auditor_role
|
||||||
elif perm.permission_type == 'write':
|
elif perm.permission_type == 'write':
|
||||||
return inventory.update_role
|
return inventory.update_role
|
||||||
elif perm.permission_type == 'check' or perm.permission_type == 'run':
|
elif perm.permission_type == 'check' or perm.permission_type == 'run' or perm.permission_type == 'create':
|
||||||
# These permission types are handled differntly in RBAC now, nothing to migrate.
|
# These permission types are handled differntly in RBAC now, nothing to migrate.
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for inventory in Inventory.objects.iterator():
|
for inventory in Inventory.objects.iterator():
|
||||||
inventory.save()
|
|
||||||
for perm in Permission.objects.filter(inventory=inventory):
|
for perm in Permission.objects.filter(inventory=inventory):
|
||||||
role = None
|
role = None
|
||||||
execrole = None
|
execrole = None
|
||||||
@@ -260,7 +272,6 @@ def migrate_projects(apps, schema_editor):
|
|||||||
|
|
||||||
# Migrate projects to single organizations, duplicating as necessary
|
# Migrate projects to single organizations, duplicating as necessary
|
||||||
for project in Project.objects.iterator():
|
for project in Project.objects.iterator():
|
||||||
project.save()
|
|
||||||
original_project_name = project.name
|
original_project_name = project.name
|
||||||
project_orgs = project.deprecated_organizations.distinct().all()
|
project_orgs = project.deprecated_organizations.distinct().all()
|
||||||
|
|
||||||
@@ -373,7 +384,6 @@ def migrate_job_templates(apps, schema_editor):
|
|||||||
Permission = apps.get_model('main', 'Permission')
|
Permission = apps.get_model('main', 'Permission')
|
||||||
|
|
||||||
for jt in JobTemplate.objects.iterator():
|
for jt in JobTemplate.objects.iterator():
|
||||||
jt.save()
|
|
||||||
permission = Permission.objects.filter(
|
permission = Permission.objects.filter(
|
||||||
inventory=jt.inventory,
|
inventory=jt.inventory,
|
||||||
project=jt.project,
|
project=jt.project,
|
||||||
@@ -390,7 +400,7 @@ def migrate_job_templates(apps, schema_editor):
|
|||||||
jt.execute_role.members.add(user)
|
jt.execute_role.members.add(user)
|
||||||
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
|
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
|
||||||
|
|
||||||
if user in jt.execute_role:
|
if jt.execute_role.ancestors.filter(members=user).exists(): # aka "user in jt.execute_role"
|
||||||
# If the job template is already accessible by the user, because they
|
# If the job template is already accessible by the user, because they
|
||||||
# are a sytem, organization, or project admin, then don't add an explicit
|
# are a sytem, organization, or project admin, then don't add an explicit
|
||||||
# role entry for them
|
# role entry for them
|
||||||
@@ -399,3 +409,22 @@ def migrate_job_templates(apps, schema_editor):
|
|||||||
if old_access.check_user_access(user, jt.__class__, 'start', jt, False):
|
if old_access.check_user_access(user, jt.__class__, 'start', jt, False):
|
||||||
jt.execute_role.members.add(user)
|
jt.execute_role.members.add(user)
|
||||||
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
|
logger.info(smart_text(u'adding User({}) access to JobTemplate({})'.format(user.username, jt.name)))
|
||||||
|
|
||||||
|
@log_migration
|
||||||
|
def rebuild_role_hierarchy(apps, schema_editor):
|
||||||
|
logger.info('Computing role roots..')
|
||||||
|
start = time()
|
||||||
|
roots = Role.objects \
|
||||||
|
.all() \
|
||||||
|
.exclude(pk__in=Role.parents.through.objects.all()
|
||||||
|
.values_list('from_role_id', flat=True).distinct()) \
|
||||||
|
.values_list('id', flat=True)
|
||||||
|
stop = time()
|
||||||
|
logger.info('Found %d roots in %f seconds, rebuilding ancestry map' % (len(roots), stop - start))
|
||||||
|
start = time()
|
||||||
|
Role.rebuild_role_ancestor_list(roots, [])
|
||||||
|
stop = time()
|
||||||
|
logger.info('Rebuild completed in %f seconds' % (stop - start))
|
||||||
|
logger.info('Done.')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class ActivityStream(models.Model):
|
|||||||
notifier = models.ManyToManyField("Notifier", blank=True)
|
notifier = models.ManyToManyField("Notifier", blank=True)
|
||||||
notification = models.ManyToManyField("Notification", blank=True)
|
notification = models.ManyToManyField("Notification", blank=True)
|
||||||
label = models.ManyToManyField("Label", blank=True)
|
label = models.ManyToManyField("Label", blank=True)
|
||||||
|
role = models.ManyToManyField("Role", blank=True)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:activity_stream_detail', args=(self.pk,))
|
return reverse('api:activity_stream_detail', args=(self.pk,))
|
||||||
|
|||||||
@@ -84,11 +84,17 @@ class AdHocCommand(UnifiedJob):
|
|||||||
editable=False,
|
editable=False,
|
||||||
through='AdHocCommandEvent',
|
through='AdHocCommandEvent',
|
||||||
)
|
)
|
||||||
|
extra_vars = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
|
)
|
||||||
|
|
||||||
|
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
||||||
|
|
||||||
def clean_inventory(self):
|
def clean_inventory(self):
|
||||||
inv = self.inventory
|
inv = self.inventory
|
||||||
if not inv:
|
if not inv:
|
||||||
raise ValidationError('Inventory is no longer available.')
|
raise ValidationError('No valid inventory.')
|
||||||
return inv
|
return inv
|
||||||
|
|
||||||
def clean_credential(self):
|
def clean_credential(self):
|
||||||
|
|||||||
@@ -204,27 +204,19 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
help_text=_('Tenant identifier for this credential'),
|
help_text=_('Tenant identifier for this credential'),
|
||||||
)
|
)
|
||||||
owner_role = ImplicitRoleField(
|
owner_role = ImplicitRoleField(
|
||||||
role_name='Credential Owner',
|
|
||||||
role_description='Owner of the credential',
|
|
||||||
parent_role=[
|
parent_role=[
|
||||||
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Credential Auditor',
|
|
||||||
role_description='Auditor of the credential',
|
|
||||||
parent_role=[
|
parent_role=[
|
||||||
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
use_role = ImplicitRoleField(
|
use_role = ImplicitRoleField(
|
||||||
role_name='Credential User',
|
|
||||||
role_description='May use this credential, but not read sensitive portions or modify it',
|
|
||||||
parent_role=['owner_role']
|
parent_role=['owner_role']
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Credential REad',
|
|
||||||
role_description='May read this credential',
|
|
||||||
parent_role=[
|
parent_role=[
|
||||||
'use_role', 'auditor_role', 'owner_role'
|
'use_role', 'auditor_role', 'owner_role'
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -97,39 +97,25 @@ class Inventory(CommonModel, ResourceMixin):
|
|||||||
help_text=_('Number of external inventory sources in this inventory with failures.'),
|
help_text=_('Number of external inventory sources in this inventory with failures.'),
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Administrator',
|
|
||||||
role_description='May manage this inventory',
|
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Inventory Auditor',
|
|
||||||
role_description='May view but not modify this inventory',
|
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organization.auditor_role',
|
||||||
)
|
)
|
||||||
update_role = ImplicitRoleField(
|
update_role = ImplicitRoleField(
|
||||||
role_name='Inventory Updater',
|
|
||||||
role_description='May update the inventory',
|
|
||||||
parent_role=['admin_role'],
|
parent_role=['admin_role'],
|
||||||
)
|
)
|
||||||
use_role = ImplicitRoleField(
|
use_role = ImplicitRoleField(
|
||||||
role_name='Inventory User',
|
|
||||||
role_description='May use this inventory, but not read sensitive portions or modify it',
|
|
||||||
parent_role=['admin_role'],
|
parent_role=['admin_role'],
|
||||||
)
|
)
|
||||||
adhoc_role = ImplicitRoleField(
|
adhoc_role = ImplicitRoleField(
|
||||||
role_name='Inventory Ad Hoc',
|
|
||||||
role_description='May execute ad hoc commands against this inventory',
|
|
||||||
parent_role=['admin_role'],
|
parent_role=['admin_role'],
|
||||||
)
|
)
|
||||||
execute_role = ImplicitRoleField(
|
execute_role = ImplicitRoleField(
|
||||||
role_name='Inventory Executor',
|
|
||||||
role_description='May execute jobs against this inventory',
|
|
||||||
parent_role='adhoc_role',
|
parent_role='adhoc_role',
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Read',
|
|
||||||
parent_role=['auditor_role', 'execute_role', 'update_role', 'use_role', 'admin_role'],
|
parent_role=['auditor_role', 'execute_role', 'update_role', 'use_role', 'admin_role'],
|
||||||
role_description='May view this inventory',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
@@ -335,7 +321,7 @@ class Inventory(CommonModel, ResourceMixin):
|
|||||||
return self.groups.exclude(parents__pk__in=group_pks).distinct()
|
return self.groups.exclude(parents__pk__in=group_pks).distinct()
|
||||||
|
|
||||||
|
|
||||||
class Host(CommonModelNameNotUnique, ResourceMixin):
|
class Host(CommonModelNameNotUnique):
|
||||||
'''
|
'''
|
||||||
A managed node
|
A managed node
|
||||||
'''
|
'''
|
||||||
@@ -531,28 +517,21 @@ class Group(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
help_text=_('Inventory source(s) that created or modified this group.'),
|
help_text=_('Inventory source(s) that created or modified this group.'),
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Administrator',
|
|
||||||
parent_role=['inventory.admin_role', 'parents.admin_role'],
|
parent_role=['inventory.admin_role', 'parents.admin_role'],
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Auditor',
|
|
||||||
parent_role=['inventory.auditor_role', 'parents.auditor_role'],
|
parent_role=['inventory.auditor_role', 'parents.auditor_role'],
|
||||||
)
|
)
|
||||||
update_role = ImplicitRoleField(
|
update_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Updater',
|
|
||||||
parent_role=['inventory.update_role', 'parents.update_role', 'admin_role'],
|
parent_role=['inventory.update_role', 'parents.update_role', 'admin_role'],
|
||||||
)
|
)
|
||||||
adhoc_role = ImplicitRoleField(
|
adhoc_role = ImplicitRoleField(
|
||||||
role_name='Inventory Ad Hoc',
|
|
||||||
parent_role=['inventory.adhoc_role', 'parents.adhoc_role', 'admin_role'],
|
parent_role=['inventory.adhoc_role', 'parents.adhoc_role', 'admin_role'],
|
||||||
role_description='May execute ad hoc commands against this inventory',
|
|
||||||
)
|
)
|
||||||
execute_role = ImplicitRoleField(
|
execute_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Executor',
|
|
||||||
parent_role=['inventory.execute_role', 'parents.execute_role', 'adhoc_role'],
|
parent_role=['inventory.execute_role', 'parents.execute_role', 'adhoc_role'],
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Inventory Group Executor',
|
|
||||||
parent_role=['execute_role', 'update_role', 'auditor_role', 'admin_role'],
|
parent_role=['execute_role', 'update_role', 'auditor_role', 'admin_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1321,25 +1300,15 @@ class CustomInventoryScript(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='CustomInventory Administrator',
|
|
||||||
role_description='May manage this inventory',
|
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
)
|
)
|
||||||
|
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='CustomInventory Member',
|
|
||||||
role_description='May view but not modify this inventory',
|
|
||||||
parent_role='organization.member_role',
|
parent_role='organization.member_role',
|
||||||
)
|
)
|
||||||
|
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='CustomInventory Auditor',
|
|
||||||
role_description='May view but not modify this inventory',
|
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organization.auditor_role',
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='CustomInventory Read',
|
|
||||||
role_description='May view but not modify this inventory',
|
|
||||||
parent_role=['auditor_role', 'member_role', 'admin_role'],
|
parent_role=['auditor_role', 'member_role', 'admin_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -135,11 +135,6 @@ class JobOptions(BaseModel):
|
|||||||
become_enabled = models.BooleanField(
|
become_enabled = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
labels = models.ManyToManyField(
|
|
||||||
"Label",
|
|
||||||
blank=True,
|
|
||||||
related_name='%(class)s_labels'
|
|
||||||
)
|
|
||||||
|
|
||||||
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
||||||
|
|
||||||
@@ -226,23 +221,15 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
|||||||
default={},
|
default={},
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Job Template Administrator',
|
|
||||||
role_description='Full access to all settings',
|
|
||||||
parent_role=[('project.admin_role', 'inventory.admin_role')]
|
parent_role=[('project.admin_role', 'inventory.admin_role')]
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Job Template Auditor',
|
|
||||||
role_description='Read-only access to all settings',
|
|
||||||
parent_role=[('project.auditor_role', 'inventory.auditor_role')]
|
parent_role=[('project.auditor_role', 'inventory.auditor_role')]
|
||||||
)
|
)
|
||||||
execute_role = ImplicitRoleField(
|
execute_role = ImplicitRoleField(
|
||||||
role_name='Job Template Runner',
|
|
||||||
role_description='May run the job template',
|
|
||||||
parent_role=['admin_role'],
|
parent_role=['admin_role'],
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Job Template Runner',
|
|
||||||
role_description='May run the job template',
|
|
||||||
parent_role=['execute_role', 'auditor_role', 'admin_role'],
|
parent_role=['execute_role', 'auditor_role', 'admin_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -523,6 +510,36 @@ class Job(UnifiedJob, JobOptions):
|
|||||||
return self.job_template.ask_variables_on_launch
|
return self.job_template.ask_variables_on_launch
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ask_limit_on_launch(self):
|
||||||
|
if self.job_template is not None:
|
||||||
|
return self.job_template.ask_limit_on_launch
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ask_tags_on_launch(self):
|
||||||
|
if self.job_template is not None:
|
||||||
|
return self.job_template.ask_tags_on_launch
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ask_job_type_on_launch(self):
|
||||||
|
if self.job_template is not None:
|
||||||
|
return self.job_template.ask_job_type_on_launch
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ask_inventory_on_launch(self):
|
||||||
|
if self.job_template is not None:
|
||||||
|
return self.job_template.ask_inventory_on_launch
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ask_credential_on_launch(self):
|
||||||
|
if self.job_template is not None:
|
||||||
|
return self.job_template.ask_credential_on_launch
|
||||||
|
return False
|
||||||
|
|
||||||
def get_passwords_needed_to_start(self):
|
def get_passwords_needed_to_start(self):
|
||||||
return self.passwords_needed_to_start
|
return self.passwords_needed_to_start
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import CommonModelNameNotUnique
|
from awx.main.models.base import CommonModelNameNotUnique
|
||||||
|
from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob
|
||||||
|
|
||||||
__all__ = ('Label', )
|
__all__ = ('Label', )
|
||||||
|
|
||||||
@@ -39,3 +40,19 @@ class Label(CommonModelNameNotUnique):
|
|||||||
jobtemplate_labels__isnull=True
|
jobtemplate_labels__isnull=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_detached(self):
|
||||||
|
return bool(
|
||||||
|
Label.objects.filter(
|
||||||
|
id=self.id,
|
||||||
|
unifiedjob_labels__isnull=True,
|
||||||
|
unifiedjobtemplate_labels__isnull=True
|
||||||
|
).count())
|
||||||
|
|
||||||
|
def is_candidate_for_detach(self):
|
||||||
|
c1 = UnifiedJob.objects.filter(labels__in=[self.id]).count()
|
||||||
|
c2 = UnifiedJobTemplate.objects.filter(labels__in=[self.id]).count()
|
||||||
|
if (c1 + c2 - 1) == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -53,23 +53,15 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
|
|||||||
related_name='deprecated_organizations',
|
related_name='deprecated_organizations',
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Organization Administrator',
|
|
||||||
role_description='May manage all aspects of this organization',
|
|
||||||
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Organization Auditor',
|
|
||||||
role_description='May read all settings associated with this organization',
|
|
||||||
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
parent_role='singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='Organization Member',
|
|
||||||
role_description='A member of this organization',
|
|
||||||
parent_role='admin_role',
|
parent_role='admin_role',
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Organization Read Access',
|
|
||||||
role_description='Read an organization',
|
|
||||||
parent_role=['member_role', 'auditor_role'],
|
parent_role=['member_role', 'auditor_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,22 +102,13 @@ class Team(CommonModelNameNotUnique, ResourceMixin):
|
|||||||
related_name='deprecated_teams',
|
related_name='deprecated_teams',
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Team Administrator',
|
|
||||||
role_description='May manage this team',
|
|
||||||
parent_role='organization.admin_role',
|
parent_role='organization.admin_role',
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Team Auditor',
|
|
||||||
role_description='May read all settings associated with this team',
|
|
||||||
parent_role='organization.auditor_role',
|
parent_role='organization.auditor_role',
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField()
|
||||||
role_name='Team Member',
|
|
||||||
role_description='A member of this team',
|
|
||||||
)
|
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Read',
|
|
||||||
role_description='Can view this team',
|
|
||||||
parent_role=['admin_role', 'auditor_role', 'member_role'],
|
parent_role=['admin_role', 'auditor_role', 'member_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -221,34 +221,24 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
admin_role = ImplicitRoleField(
|
admin_role = ImplicitRoleField(
|
||||||
role_name='Project Administrator',
|
|
||||||
role_description='May manage this project',
|
|
||||||
parent_role=[
|
parent_role=[
|
||||||
'organization.admin_role',
|
'organization.admin_role',
|
||||||
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
auditor_role = ImplicitRoleField(
|
auditor_role = ImplicitRoleField(
|
||||||
role_name='Project Auditor',
|
|
||||||
role_description='May read all settings associated with this project',
|
|
||||||
parent_role=[
|
parent_role=[
|
||||||
'organization.auditor_role',
|
'organization.auditor_role',
|
||||||
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
member_role = ImplicitRoleField(
|
member_role = ImplicitRoleField(
|
||||||
role_name='Project Member',
|
|
||||||
role_description='Implies membership within this project',
|
|
||||||
parent_role='admin_role',
|
parent_role='admin_role',
|
||||||
)
|
)
|
||||||
scm_update_role = ImplicitRoleField(
|
scm_update_role = ImplicitRoleField(
|
||||||
role_name='Project Updater',
|
|
||||||
role_description='May update this project from the source control management system',
|
|
||||||
parent_role='admin_role',
|
parent_role='admin_role',
|
||||||
)
|
)
|
||||||
read_role = ImplicitRoleField(
|
read_role = ImplicitRoleField(
|
||||||
role_name='Project Read Access',
|
|
||||||
role_description='Read access to this project',
|
|
||||||
parent_role=['member_role', 'auditor_role', 'scm_update_role'],
|
parent_role=['member_role', 'auditor_role', 'scm_update_role'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import contextlib
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import models, transaction, connection
|
from django.db import models, transaction, connection
|
||||||
from django.db.models import Q
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@@ -29,8 +28,39 @@ __all__ = [
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.rbac')
|
logger = logging.getLogger('awx.main.models.rbac')
|
||||||
|
|
||||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='System Administrator'
|
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR='system_administrator'
|
||||||
ROLE_SINGLETON_SYSTEM_AUDITOR='System Auditor'
|
ROLE_SINGLETON_SYSTEM_AUDITOR='system_auditor'
|
||||||
|
|
||||||
|
role_names = {
|
||||||
|
'system_administrator' : 'System Administrator',
|
||||||
|
'system_auditor' : 'System Auditor',
|
||||||
|
'adhoc_role' : 'Ad Hoc',
|
||||||
|
'admin_role' : 'Admin',
|
||||||
|
'auditor_role' : 'Auditor',
|
||||||
|
'execute_role' : 'Execute',
|
||||||
|
'member_role' : 'Member',
|
||||||
|
'owner_role' : 'Owner',
|
||||||
|
'read_role' : 'Read',
|
||||||
|
'scm_update_role' : 'SCM Update',
|
||||||
|
'update_role' : 'Update',
|
||||||
|
'use_role' : 'Use',
|
||||||
|
}
|
||||||
|
|
||||||
|
role_descriptions = {
|
||||||
|
'system_administrator' : '[TODO] System Administrator',
|
||||||
|
'system_auditor' : '[TODO] System Auditor',
|
||||||
|
'adhoc_role' : '[TODO] Ad Hoc',
|
||||||
|
'admin_role' : '[TODO] Admin',
|
||||||
|
'auditor_role' : '[TODO] Auditor',
|
||||||
|
'execute_role' : '[TODO] Execute',
|
||||||
|
'member_role' : '[TODO] Member',
|
||||||
|
'owner_role' : '[TODO] Owner',
|
||||||
|
'read_role' : '[TODO] Read',
|
||||||
|
'scm_update_role' : '[TODO] SCM Update',
|
||||||
|
'update_role' : '[TODO] Update',
|
||||||
|
'use_role' : '[TODO] Use',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
tls = threading.local() # thread local storage
|
tls = threading.local() # thread local storage
|
||||||
|
|
||||||
@@ -51,23 +81,22 @@ def batch_role_ancestor_rebuilding(allow_nesting=False):
|
|||||||
try:
|
try:
|
||||||
setattr(tls, 'batch_role_rebuilding', True)
|
setattr(tls, 'batch_role_rebuilding', True)
|
||||||
if not batch_role_rebuilding:
|
if not batch_role_rebuilding:
|
||||||
setattr(tls, 'roles_needing_rebuilding', set())
|
setattr(tls, 'additions', set())
|
||||||
|
setattr(tls, 'removals', set())
|
||||||
yield
|
yield
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
setattr(tls, 'batch_role_rebuilding', batch_role_rebuilding)
|
setattr(tls, 'batch_role_rebuilding', batch_role_rebuilding)
|
||||||
if not batch_role_rebuilding:
|
if not batch_role_rebuilding:
|
||||||
rebuild_set = getattr(tls, 'roles_needing_rebuilding')
|
additions = getattr(tls, 'additions')
|
||||||
|
removals = getattr(tls, 'removals')
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
Role._simultaneous_ancestry_rebuild(list(rebuild_set))
|
Role.rebuild_role_ancestor_list(list(additions), list(removals))
|
||||||
|
delattr(tls, 'additions')
|
||||||
#for role in Role.objects.filter(id__in=list(rebuild_set)).all():
|
delattr(tls, 'removals')
|
||||||
# # TODO: We can reduce this to one rebuild call with our new upcoming rebuild method.. do this
|
|
||||||
# role.rebuild_role_ancestor_list()
|
|
||||||
delattr(tls, 'roles_needing_rebuilding')
|
|
||||||
|
|
||||||
|
|
||||||
class Role(CommonModelNameNotUnique):
|
class Role(models.Model):
|
||||||
'''
|
'''
|
||||||
Role model
|
Role model
|
||||||
'''
|
'''
|
||||||
@@ -76,9 +105,12 @@ class Role(CommonModelNameNotUnique):
|
|||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
verbose_name_plural = _('roles')
|
verbose_name_plural = _('roles')
|
||||||
db_table = 'main_rbac_roles'
|
db_table = 'main_rbac_roles'
|
||||||
|
index_together = [
|
||||||
|
("content_type", "object_id")
|
||||||
|
]
|
||||||
|
|
||||||
|
role_field = models.TextField(null=False)
|
||||||
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
|
singleton_name = models.TextField(null=True, default=None, db_index=True, unique=True)
|
||||||
role_field = models.TextField(null=False, default='')
|
|
||||||
parents = models.ManyToManyField('Role', related_name='children')
|
parents = models.ManyToManyField('Role', related_name='children')
|
||||||
implicit_parents = models.TextField(null=False, default='[]')
|
implicit_parents = models.TextField(null=False, default='[]')
|
||||||
ancestors = models.ManyToManyField(
|
ancestors = models.ManyToManyField(
|
||||||
@@ -94,7 +126,7 @@ class Role(CommonModelNameNotUnique):
|
|||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super(Role, self).save(*args, **kwargs)
|
super(Role, self).save(*args, **kwargs)
|
||||||
self.rebuild_role_ancestor_list()
|
self.rebuild_role_ancestor_list([self.id], [])
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:role_detail', args=(self.pk,))
|
return reverse('api:role_detail', args=(self.pk,))
|
||||||
@@ -112,20 +144,36 @@ class Role(CommonModelNameNotUnique):
|
|||||||
object_id=accessor.id)
|
object_id=accessor.id)
|
||||||
return self.ancestors.filter(pk__in=roles).exists()
|
return self.ancestors.filter(pk__in=roles).exists()
|
||||||
|
|
||||||
def rebuild_role_ancestor_list(self):
|
@property
|
||||||
|
def name(self):
|
||||||
|
global role_names
|
||||||
|
return role_names[self.role_field]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
global role_descriptions
|
||||||
|
return role_descriptions[self.role_field]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def rebuild_role_ancestor_list(additions, removals):
|
||||||
'''
|
'''
|
||||||
Updates our `ancestors` map to accurately reflect all of the ancestors for a role
|
Updates our `ancestors` map to accurately reflect all of the ancestors for a role
|
||||||
|
|
||||||
You should never need to call this. Signal handlers should be calling
|
You should never need to call this. Signal handlers should be calling
|
||||||
this method when the role hierachy changes automatically.
|
this method when the role hierachy changes automatically.
|
||||||
|
|
||||||
Note that this method relies on any parents' ancestor list being correct.
|
|
||||||
'''
|
'''
|
||||||
Role._simultaneous_ancestry_rebuild([self.id])
|
# The ancestry table
|
||||||
|
# =================================================
|
||||||
|
#
|
||||||
@staticmethod
|
# The role ancestors table denormalizes the parental relations
|
||||||
def _simultaneous_ancestry_rebuild(role_ids_to_rebuild):
|
# between all roles in the system. If you have role A which is a
|
||||||
|
# parent of B which is a parent of C, then the ancestors table will
|
||||||
|
# contain a row noting that B is a descendent of A, and two rows for
|
||||||
|
# denoting that C is a descendent of both A and B. In addition to
|
||||||
|
# storing entries for each descendent relationship, we also store an
|
||||||
|
# entry that states that C is a 'descendent' of itself, C. This makes
|
||||||
|
# usage of this table simple in our queries as it enables us to do
|
||||||
|
# straight joins where we would have to do unions otherwise.
|
||||||
#
|
#
|
||||||
# The simple version of what this function is doing
|
# The simple version of what this function is doing
|
||||||
# =================================================
|
# =================================================
|
||||||
@@ -163,37 +211,18 @@ class Role(CommonModelNameNotUnique):
|
|||||||
#
|
#
|
||||||
# SQL Breakdown
|
# SQL Breakdown
|
||||||
# =============
|
# =============
|
||||||
# The Role ancestors has three columns, (id, from_role_id, to_role_id)
|
|
||||||
#
|
|
||||||
# id: Unqiue row ID
|
|
||||||
# from_role_id: Descendent role ID
|
|
||||||
# to_role_id: Ancestor role ID
|
|
||||||
#
|
|
||||||
# *NOTE* In addition to mapping roles to parents, there also
|
|
||||||
# always exists must exist an entry where
|
|
||||||
#
|
|
||||||
# from_role_id == role_id == to_role_id
|
|
||||||
#
|
|
||||||
# this makes our joins simple when we go to derive permissions or
|
|
||||||
# accessible objects.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# We operate under the assumption that our parent's ancestor list is
|
# We operate under the assumption that our parent's ancestor list is
|
||||||
# correct, thus we can always compute what our ancestor list should
|
# correct, thus we can always compute what our ancestor list should
|
||||||
# be by taking the union of our parent's ancestor lists and adding
|
# be by taking the union of our parent's ancestor lists and adding
|
||||||
# our self reference entry from_role_id == role_id == to_role_id
|
# our self reference entry where ancestor_id = descendent_id
|
||||||
#
|
#
|
||||||
# The inner query for the two SQL statements compute this union,
|
# The DELETE query deletes all entries in the ancestor table that
|
||||||
# the union of the parent's ancestors and the self referncing entry,
|
# should no longer be there (as determined by the NOT EXISTS query,
|
||||||
# for all roles in the current set of roles to rebuild.
|
# which checks to see if the ancestor is still an ancestor of one
|
||||||
|
# or more of our parents)
|
||||||
#
|
#
|
||||||
# The DELETE query uses this to select all entries on disk for the
|
# The INSERT query computes the list of what our ancestor maps should
|
||||||
# roles we're dealing with, and removes the entries that are not in
|
# be, and inserts any missing entries.
|
||||||
# this list.
|
|
||||||
#
|
|
||||||
# The INSERT query uses this to select all entries in the list that
|
|
||||||
# are not in the database yet, and inserts all of the missing
|
|
||||||
# records.
|
|
||||||
#
|
#
|
||||||
# Once complete, we select all of the children for the roles we are
|
# Once complete, we select all of the children for the roles we are
|
||||||
# working with, this list becomes the new role list we are working
|
# working with, this list becomes the new role list we are working
|
||||||
@@ -205,18 +234,17 @@ class Role(CommonModelNameNotUnique):
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
if len(role_ids_to_rebuild) == 0:
|
if len(additions) == 0 and len(removals) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
global tls
|
global tls
|
||||||
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
batch_role_rebuilding = getattr(tls, 'batch_role_rebuilding', False)
|
||||||
|
|
||||||
if batch_role_rebuilding:
|
if batch_role_rebuilding:
|
||||||
roles_needing_rebuilding = getattr(tls, 'roles_needing_rebuilding')
|
getattr(tls, 'additions').update(set(additions))
|
||||||
roles_needing_rebuilding.update(set(role_ids_to_rebuild))
|
getattr(tls, 'removals').update(set(removals))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
loop_ct = 0
|
loop_ct = 0
|
||||||
|
|
||||||
@@ -226,94 +254,143 @@ class Role(CommonModelNameNotUnique):
|
|||||||
'roles_table': Role._meta.db_table,
|
'roles_table': Role._meta.db_table,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# SQLlite has a 1M sql statement limit.. since the django sqllite
|
||||||
|
# driver isn't letting us pass in the ids through the preferred
|
||||||
|
# parameter binding system, this function exists to obey this.
|
||||||
|
# est max 12 bytes per number, used up to 2 times in a query,
|
||||||
|
# minus 4k of padding for the other parts of the query, leads us
|
||||||
|
# to the magic number of 41496, or 40000 for a nice round number
|
||||||
def split_ids_for_sqlite(role_ids):
|
def split_ids_for_sqlite(role_ids):
|
||||||
for i in xrange(0, len(role_ids), 999):
|
for i in xrange(0, len(role_ids), 40000):
|
||||||
yield role_ids[i:i + 999]
|
yield role_ids[i:i + 40000]
|
||||||
|
|
||||||
while role_ids_to_rebuild:
|
|
||||||
if loop_ct > 1000:
|
|
||||||
raise Exception('Ancestry role rebuilding error: infinite loop detected')
|
|
||||||
loop_ct += 1
|
|
||||||
|
|
||||||
delete_ct = 0
|
|
||||||
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
|
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
|
||||||
cursor.execute('''
|
|
||||||
DELETE FROM %(ancestors_table)s
|
|
||||||
WHERE descendent_id IN (%(ids)s)
|
|
||||||
AND
|
|
||||||
id NOT IN (
|
|
||||||
SELECT %(ancestors_table)s.id FROM (
|
|
||||||
SELECT parents.from_role_id from_id, ancestors.ancestor_id to_id
|
|
||||||
FROM %(parents_table)s as parents
|
|
||||||
LEFT JOIN %(ancestors_table)s as ancestors
|
|
||||||
ON (parents.to_role_id = ancestors.descendent_id)
|
|
||||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
SELECT id from_id, id to_id from %(roles_table)s WHERE id IN (%(ids)s)
|
|
||||||
) new_ancestry_list
|
|
||||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
|
|
||||||
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
|
|
||||||
WHERE %(ancestors_table)s.id IS NOT NULL
|
|
||||||
)
|
|
||||||
''' % sql_params)
|
|
||||||
delete_ct += cursor.rowcount
|
|
||||||
|
|
||||||
insert_ct = 0
|
|
||||||
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
|
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
|
||||||
cursor.execute('''
|
|
||||||
INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id)
|
|
||||||
SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM (
|
|
||||||
SELECT parents.from_role_id from_id,
|
|
||||||
ancestors.ancestor_id to_id,
|
|
||||||
roles.role_field,
|
|
||||||
COALESCE(roles.content_type_id, 0) content_type_id,
|
|
||||||
COALESCE(roles.object_id, 0) object_id
|
|
||||||
FROM %(parents_table)s as parents
|
|
||||||
INNER JOIN %(roles_table)s as roles ON (parents.from_role_id = roles.id)
|
|
||||||
LEFT OUTER JOIN %(ancestors_table)s as ancestors
|
|
||||||
ON (parents.to_role_id = ancestors.descendent_id)
|
|
||||||
WHERE parents.from_role_id IN (%(ids)s) AND ancestors.ancestor_id IS NOT NULL
|
|
||||||
|
|
||||||
UNION
|
|
||||||
|
|
||||||
SELECT id from_id,
|
|
||||||
id to_id,
|
|
||||||
role_field,
|
|
||||||
COALESCE(content_type_id, 0) content_type_id,
|
|
||||||
COALESCE(object_id, 0) object_id
|
|
||||||
from %(roles_table)s WHERE id IN (%(ids)s)
|
|
||||||
) new_ancestry_list
|
|
||||||
LEFT JOIN %(ancestors_table)s ON (new_ancestry_list.from_id = %(ancestors_table)s.descendent_id
|
|
||||||
AND new_ancestry_list.to_id = %(ancestors_table)s.ancestor_id)
|
|
||||||
WHERE %(ancestors_table)s.id IS NULL
|
|
||||||
''' % sql_params)
|
|
||||||
insert_ct += cursor.rowcount
|
|
||||||
|
|
||||||
if insert_ct == 0 and delete_ct == 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
new_role_ids_to_rebuild = set()
|
|
||||||
for ids in split_ids_for_sqlite(role_ids_to_rebuild):
|
|
||||||
sql_params['ids'] = ','.join(str(x) for x in ids)
|
|
||||||
new_role_ids_to_rebuild.update(set(Role.objects.distinct()
|
|
||||||
.filter(id__in=ids, children__id__isnull=False)
|
|
||||||
.values_list('children__id', flat=True)))
|
|
||||||
role_ids_to_rebuild = list(new_role_ids_to_rebuild)
|
|
||||||
|
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
while len(additions) > 0 or len(removals) > 0:
|
||||||
|
if loop_ct > 100:
|
||||||
|
raise Exception('Role ancestry rebuilding error: infinite loop detected')
|
||||||
|
loop_ct += 1
|
||||||
|
|
||||||
|
delete_ct = 0
|
||||||
|
if len(removals) > 0:
|
||||||
|
for ids in split_ids_for_sqlite(removals):
|
||||||
|
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||||
|
cursor.execute('''
|
||||||
|
DELETE FROM %(ancestors_table)s
|
||||||
|
WHERE descendent_id IN (%(ids)s)
|
||||||
|
AND descendent_id != ancestor_id
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM %(parents_table)s as parents
|
||||||
|
INNER JOIN %(ancestors_table)s as inner_ancestors
|
||||||
|
ON (parents.to_role_id = inner_ancestors.descendent_id)
|
||||||
|
WHERE parents.from_role_id = %(ancestors_table)s.descendent_id
|
||||||
|
AND %(ancestors_table)s.ancestor_id = inner_ancestors.ancestor_id
|
||||||
|
)
|
||||||
|
''' % sql_params)
|
||||||
|
|
||||||
|
delete_ct += cursor.rowcount
|
||||||
|
|
||||||
|
insert_ct = 0
|
||||||
|
if len(additions) > 0:
|
||||||
|
for ids in split_ids_for_sqlite(additions):
|
||||||
|
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO %(ancestors_table)s (descendent_id, ancestor_id, role_field, content_type_id, object_id)
|
||||||
|
SELECT from_id, to_id, new_ancestry_list.role_field, new_ancestry_list.content_type_id, new_ancestry_list.object_id FROM (
|
||||||
|
SELECT roles.id from_id,
|
||||||
|
ancestors.ancestor_id to_id,
|
||||||
|
roles.role_field,
|
||||||
|
COALESCE(roles.content_type_id, 0) content_type_id,
|
||||||
|
COALESCE(roles.object_id, 0) object_id
|
||||||
|
FROM %(roles_table)s as roles
|
||||||
|
INNER JOIN %(parents_table)s as parents
|
||||||
|
ON (parents.from_role_id = roles.id)
|
||||||
|
INNER JOIN %(ancestors_table)s as ancestors
|
||||||
|
ON (parents.to_role_id = ancestors.descendent_id)
|
||||||
|
WHERE roles.id IN (%(ids)s)
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT id from_id,
|
||||||
|
id to_id,
|
||||||
|
role_field,
|
||||||
|
COALESCE(content_type_id, 0) content_type_id,
|
||||||
|
COALESCE(object_id, 0) object_id
|
||||||
|
from %(roles_table)s WHERE id IN (%(ids)s)
|
||||||
|
) new_ancestry_list
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM %(ancestors_table)s
|
||||||
|
WHERE %(ancestors_table)s.descendent_id = new_ancestry_list.from_id
|
||||||
|
AND %(ancestors_table)s.ancestor_id = new_ancestry_list.to_id
|
||||||
|
)
|
||||||
|
|
||||||
|
''' % sql_params)
|
||||||
|
insert_ct += cursor.rowcount
|
||||||
|
|
||||||
|
if insert_ct == 0 and delete_ct == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
new_additions = set()
|
||||||
|
for ids in split_ids_for_sqlite(additions):
|
||||||
|
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||||
|
# get all children for the roles we're operating on
|
||||||
|
cursor.execute('SELECT DISTINCT from_role_id FROM %(parents_table)s WHERE to_role_id IN (%(ids)s)' % sql_params)
|
||||||
|
new_additions.update([row[0] for row in cursor.fetchall()])
|
||||||
|
additions = list(new_additions)
|
||||||
|
|
||||||
|
new_removals = set()
|
||||||
|
for ids in split_ids_for_sqlite(removals):
|
||||||
|
sql_params['ids'] = ','.join(str(x) for x in ids)
|
||||||
|
# get all children for the roles we're operating on
|
||||||
|
cursor.execute('SELECT DISTINCT from_role_id FROM %(parents_table)s WHERE to_role_id IN (%(ids)s)' % sql_params)
|
||||||
|
new_removals.update([row[0] for row in cursor.fetchall()])
|
||||||
|
removals = list(new_removals)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visible_roles(user):
|
def visible_roles(user):
|
||||||
return Role.objects.filter(Q(descendents__in=user.roles.filter()) | Q(ancestors__in=user.roles.filter()))
|
sql_params = {
|
||||||
|
'ancestors_table': Role.ancestors.through._meta.db_table,
|
||||||
|
'parents_table': Role.parents.through._meta.db_table,
|
||||||
|
'roles_table': Role._meta.db_table,
|
||||||
|
'ids': ','.join(str(x) for x in user.roles.values_list('id', flat=True))
|
||||||
|
}
|
||||||
|
|
||||||
|
qs = Role.objects.extra(
|
||||||
|
where = ['''
|
||||||
|
%(roles_table)s.id IN (
|
||||||
|
SELECT descendent_id FROM %(ancestors_table)s WHERE ancestor_id IN (%(ids)s)
|
||||||
|
UNION
|
||||||
|
SELECT ancestor_id FROM %(ancestors_table)s WHERE descendent_id IN (%(ids)s)
|
||||||
|
)
|
||||||
|
''' % sql_params]
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def filter_visible_roles(user, roles_qs):
|
||||||
|
sql_params = {
|
||||||
|
'ancestors_table': Role.ancestors.through._meta.db_table,
|
||||||
|
'parents_table': Role.parents.through._meta.db_table,
|
||||||
|
'roles_table': Role._meta.db_table,
|
||||||
|
'ids': ','.join(str(x) for x in user.roles.all().values_list('id', flat=True))
|
||||||
|
}
|
||||||
|
|
||||||
|
qs = roles_qs.extra(
|
||||||
|
where = ['''
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM
|
||||||
|
%(ancestors_table)s
|
||||||
|
WHERE (descendent_id = %(roles_table)s.id AND ancestor_id IN (%(ids)s))
|
||||||
|
OR (ancestor_id = %(roles_table)s.id AND descendent_id IN (%(ids)s))
|
||||||
|
) ''' % sql_params]
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def singleton(name):
|
def singleton(name):
|
||||||
role, _ = Role.objects.get_or_create(singleton_name=name, name=name)
|
role, _ = Role.objects.get_or_create(singleton_name=name, role_field=name)
|
||||||
return role
|
return role
|
||||||
|
|
||||||
def is_ancestor_of(self, role):
|
def is_ancestor_of(self, role):
|
||||||
@@ -328,6 +405,7 @@ class RoleAncestorEntry(models.Model):
|
|||||||
index_together = [
|
index_together = [
|
||||||
("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource
|
("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource
|
||||||
("ancestor", "content_type_id", "role_field"), # used by accessible_objects
|
("ancestor", "content_type_id", "role_field"), # used by accessible_objects
|
||||||
|
("ancestor", "descendent"), # used by rebuild_role_ancestor_list in the NOT EXISTS clauses.
|
||||||
]
|
]
|
||||||
|
|
||||||
descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
|
descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
|
||||||
@@ -359,5 +437,5 @@ def get_roles_on_resource(resource, accessor):
|
|||||||
ancestor__in=roles,
|
ancestor__in=roles,
|
||||||
content_type_id=ContentType.objects.get_for_model(resource).id,
|
content_type_id=ContentType.objects.get_for_model(resource).id,
|
||||||
object_id=resource.id
|
object_id=resource.id
|
||||||
).values_list('role_field', flat=True)
|
).values_list('role_field', flat=True).distinct()
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
|||||||
default='ok',
|
default='ok',
|
||||||
editable=False,
|
editable=False,
|
||||||
)
|
)
|
||||||
|
labels = models.ManyToManyField(
|
||||||
|
"Label",
|
||||||
|
blank=True,
|
||||||
|
related_name='%(class)s_labels'
|
||||||
|
)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
real_instance = self.get_real_instance()
|
real_instance = self.get_real_instance()
|
||||||
@@ -476,6 +481,12 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
default='',
|
default='',
|
||||||
editable=False,
|
editable=False,
|
||||||
)
|
)
|
||||||
|
labels = models.ManyToManyField(
|
||||||
|
"Label",
|
||||||
|
blank=True,
|
||||||
|
related_name='%(class)s_labels'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
real_instance = self.get_real_instance()
|
real_instance = self.get_real_instance()
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ class SlackBackend(TowerBaseEmailBackend):
|
|||||||
for m in messages:
|
for m in messages:
|
||||||
try:
|
try:
|
||||||
for r in m.recipients():
|
for r in m.recipients():
|
||||||
|
if r.startswith('#'):
|
||||||
|
r = r[1:]
|
||||||
self.connection.rtm_send_message(r, m.subject)
|
self.connection.rtm_send_message(r, m.subject)
|
||||||
sent_messages += 1
|
sent_messages += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.db.models.signals import pre_save, post_save, post_delete, m2m_changed
|
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.registrar')
|
logger = logging.getLogger('awx.main.registrar')
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ class ActivityStreamRegistrar(object):
|
|||||||
self.models.append(model)
|
self.models.append(model)
|
||||||
post_save.connect(activity_stream_create, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_create")
|
post_save.connect(activity_stream_create, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_create")
|
||||||
pre_save.connect(activity_stream_update, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_update")
|
pre_save.connect(activity_stream_update, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_update")
|
||||||
post_delete.connect(activity_stream_delete, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_delete")
|
pre_delete.connect(activity_stream_delete, sender=model, dispatch_uid=str(self.__class__) + str(model) + "_delete")
|
||||||
|
|
||||||
for m2mfield in model._meta.many_to_many:
|
for m2mfield in model._meta.many_to_many:
|
||||||
try:
|
try:
|
||||||
@@ -36,7 +36,7 @@ class ActivityStreamRegistrar(object):
|
|||||||
if model in self.models:
|
if model in self.models:
|
||||||
post_save.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_create")
|
post_save.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_create")
|
||||||
pre_save.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_update")
|
pre_save.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_update")
|
||||||
post_delete.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_delete")
|
pre_delete.disconnect(dispatch_uid=str(self.__class__) + str(model) + "_delete")
|
||||||
self.models.pop(model)
|
self.models.pop(model)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -108,12 +108,17 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
|||||||
|
|
||||||
def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs):
|
def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs):
|
||||||
'When a role parent is added or removed, update our role hierarchy list'
|
'When a role parent is added or removed, update our role hierarchy list'
|
||||||
if action in ['post_add', 'post_remove', 'post_clear']:
|
if action == 'post_add':
|
||||||
if reverse:
|
if reverse:
|
||||||
for id in pk_set:
|
model.rebuild_role_ancestor_list(list(pk_set), [])
|
||||||
model.objects.get(id=id).rebuild_role_ancestor_list()
|
|
||||||
else:
|
else:
|
||||||
instance.rebuild_role_ancestor_list()
|
model.rebuild_role_ancestor_list([instance.id], [])
|
||||||
|
|
||||||
|
if action in ['post_remove', 'post_clear']:
|
||||||
|
if reverse:
|
||||||
|
model.rebuild_role_ancestor_list([], list(pk_set))
|
||||||
|
else:
|
||||||
|
model.rebuild_role_ancestor_list([], [instance.id])
|
||||||
|
|
||||||
def sync_superuser_status_to_rbac(instance, **kwargs):
|
def sync_superuser_status_to_rbac(instance, **kwargs):
|
||||||
'When the is_superuser flag is changed on a user, reflect that in the membership of the System Admnistrator role'
|
'When the is_superuser flag is changed on a user, reflect that in the membership of the System Admnistrator role'
|
||||||
@@ -127,11 +132,10 @@ def create_user_role(instance, **kwargs):
|
|||||||
Role.objects.get(
|
Role.objects.get(
|
||||||
content_type=ContentType.objects.get_for_model(instance),
|
content_type=ContentType.objects.get_for_model(instance),
|
||||||
object_id=instance.id,
|
object_id=instance.id,
|
||||||
name = 'User Admin'
|
role_field='admin_role'
|
||||||
)
|
)
|
||||||
except Role.DoesNotExist:
|
except Role.DoesNotExist:
|
||||||
role = Role.objects.create(
|
role = Role.objects.create(
|
||||||
name = 'User Admin',
|
|
||||||
role_field='admin_role',
|
role_field='admin_role',
|
||||||
content_object = instance,
|
content_object = instance,
|
||||||
)
|
)
|
||||||
@@ -152,6 +156,24 @@ def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
|
|||||||
if action == 'pre_remove':
|
if action == 'pre_remove':
|
||||||
instance.content_object.admin_role.children.remove(user.admin_role)
|
instance.content_object.admin_role.children.remove(user.admin_role)
|
||||||
|
|
||||||
|
def rbac_activity_stream(instance, sender, **kwargs):
|
||||||
|
user_type = ContentType.objects.get_for_model(User)
|
||||||
|
# Only if we are associating/disassociating
|
||||||
|
if kwargs['action'] in ['pre_add', 'pre_remove']:
|
||||||
|
# Only if this isn't for the User.admin_role
|
||||||
|
if hasattr(instance, 'content_type'):
|
||||||
|
if instance.content_type in [None, user_type]:
|
||||||
|
return
|
||||||
|
role = instance
|
||||||
|
instance = instance.content_object
|
||||||
|
else:
|
||||||
|
role = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']).first()
|
||||||
|
activity_stream_associate(sender, instance, role=role, **kwargs)
|
||||||
|
|
||||||
|
def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs):
|
||||||
|
for l in instance.labels.all():
|
||||||
|
if l.is_candidate_for_detach():
|
||||||
|
l.delete()
|
||||||
|
|
||||||
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
|
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
|
||||||
post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
|
post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
|
||||||
@@ -169,9 +191,11 @@ post_save.connect(emit_job_event_detail, sender=JobEvent)
|
|||||||
post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent)
|
post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent)
|
||||||
m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through)
|
m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through)
|
||||||
m2m_changed.connect(org_admin_edit_members, Role.members.through)
|
m2m_changed.connect(org_admin_edit_members, Role.members.through)
|
||||||
|
m2m_changed.connect(rbac_activity_stream, Role.members.through)
|
||||||
post_save.connect(sync_superuser_status_to_rbac, sender=User)
|
post_save.connect(sync_superuser_status_to_rbac, sender=User)
|
||||||
post_save.connect(create_user_role, sender=User)
|
post_save.connect(create_user_role, sender=User)
|
||||||
|
pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJob)
|
||||||
|
pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJobTemplate)
|
||||||
|
|
||||||
# Migrate hosts, groups to parent group(s) whenever a group is deleted
|
# Migrate hosts, groups to parent group(s) whenever a group is deleted
|
||||||
|
|
||||||
@@ -331,14 +355,10 @@ def activity_stream_update(sender, instance, **kwargs):
|
|||||||
def activity_stream_delete(sender, instance, **kwargs):
|
def activity_stream_delete(sender, instance, **kwargs):
|
||||||
if not activity_stream_enabled:
|
if not activity_stream_enabled:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
old = sender.objects.get(id=instance.id)
|
|
||||||
except sender.DoesNotExist:
|
|
||||||
return
|
|
||||||
# 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.group:
|
if isinstance(instance, InventorySource) and instance.group:
|
||||||
return
|
return
|
||||||
changes = model_instance_diff(old, 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(
|
||||||
operation='delete',
|
operation='delete',
|
||||||
@@ -349,7 +369,7 @@ def activity_stream_delete(sender, instance, **kwargs):
|
|||||||
def activity_stream_associate(sender, instance, **kwargs):
|
def activity_stream_associate(sender, instance, **kwargs):
|
||||||
if not activity_stream_enabled:
|
if not activity_stream_enabled:
|
||||||
return
|
return
|
||||||
if 'pre_add' in kwargs['action'] or 'pre_remove' in kwargs['action']:
|
if kwargs['action'] in ['pre_add', 'pre_remove']:
|
||||||
if kwargs['action'] == 'pre_add':
|
if kwargs['action'] == 'pre_add':
|
||||||
action = 'associate'
|
action = 'associate'
|
||||||
elif kwargs['action'] == 'pre_remove':
|
elif kwargs['action'] == 'pre_remove':
|
||||||
@@ -378,6 +398,23 @@ def activity_stream_associate(sender, instance, **kwargs):
|
|||||||
getattr(activity_entry, object1).add(obj1)
|
getattr(activity_entry, object1).add(obj1)
|
||||||
getattr(activity_entry, object2).add(obj2_actual)
|
getattr(activity_entry, object2).add(obj2_actual)
|
||||||
|
|
||||||
|
# Record the role for RBAC changes
|
||||||
|
if 'role' in kwargs:
|
||||||
|
role = kwargs['role']
|
||||||
|
if role.content_object is not None:
|
||||||
|
obj_rel = '.'.join([role.content_object.__module__,
|
||||||
|
role.content_object.__class__.__name__,
|
||||||
|
role.role_field])
|
||||||
|
|
||||||
|
# If the m2m is from the User side we need to
|
||||||
|
# set the content_object of the Role for our entry.
|
||||||
|
if type(instance) == User and role.content_object is not None:
|
||||||
|
getattr(activity_entry, role.content_type.name).add(role.content_object)
|
||||||
|
|
||||||
|
activity_entry.role.add(role)
|
||||||
|
activity_entry.object_relationship_type = obj_rel
|
||||||
|
activity_entry.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(current_user_getter)
|
@receiver(current_user_getter)
|
||||||
def get_current_user_from_drf_request(sender, **kwargs):
|
def get_current_user_from_drf_request(sender, **kwargs):
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ def handle_work_success(self, result, task_actual):
|
|||||||
notification_body = instance.notification_data()
|
notification_body = instance.notification_data()
|
||||||
notification_subject = "{} #{} '{}' succeeded on Ansible Tower: {}".format(friendly_name,
|
notification_subject = "{} #{} '{}' succeeded on Ansible Tower: {}".format(friendly_name,
|
||||||
task_actual['id'],
|
task_actual['id'],
|
||||||
instance_name,
|
smart_text(instance_name),
|
||||||
notification_body['url'])
|
notification_body['url'])
|
||||||
notification_body['friendly_name'] = friendly_name
|
notification_body['friendly_name'] = friendly_name
|
||||||
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
||||||
@@ -246,8 +246,8 @@ def handle_work_error(self, task_id, subtasks=None):
|
|||||||
instance_name = instance.module_name
|
instance_name = instance.module_name
|
||||||
notifiers = []
|
notifiers = []
|
||||||
friendly_name = "AdHoc Command"
|
friendly_name = "AdHoc Command"
|
||||||
elif task_actual['type'] == 'system_job':
|
elif each_task['type'] == 'system_job':
|
||||||
instance = SystemJob.objects.get(id=task_actual['id'])
|
instance = SystemJob.objects.get(id=each_task['id'])
|
||||||
instance_name = instance.system_job_template.name
|
instance_name = instance.system_job_template.name
|
||||||
notifiers = instance.system_job_template.notifiers
|
notifiers = instance.system_job_template.notifiers
|
||||||
friendly_name = "System Job"
|
friendly_name = "System Job"
|
||||||
@@ -270,7 +270,7 @@ def handle_work_error(self, task_id, subtasks=None):
|
|||||||
notification_body = first_task.notification_data()
|
notification_body = first_task.notification_data()
|
||||||
notification_subject = "{} #{} '{}' failed on Ansible Tower: {}".format(first_task_friendly_name,
|
notification_subject = "{} #{} '{}' failed on Ansible Tower: {}".format(first_task_friendly_name,
|
||||||
first_task_id,
|
first_task_id,
|
||||||
first_task_name,
|
smart_text(first_task_name),
|
||||||
notification_body['url'])
|
notification_body['url'])
|
||||||
notification_body['friendly_name'] = first_task_friendly_name
|
notification_body['friendly_name'] = first_task_friendly_name
|
||||||
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
|
||||||
@@ -558,7 +558,7 @@ class BaseTask(Task):
|
|||||||
instance = self.update_model(instance.pk)
|
instance = self.update_model(instance.pk)
|
||||||
if instance.cancel_flag:
|
if instance.cancel_flag:
|
||||||
try:
|
try:
|
||||||
if tower_settings.AWX_PROOT_ENABLED:
|
if tower_settings.AWX_PROOT_ENABLED and self.should_use_proot(instance):
|
||||||
# NOTE: Refactor this once we get a newer psutil across the board
|
# NOTE: Refactor this once we get a newer psutil across the board
|
||||||
if not psutil:
|
if not psutil:
|
||||||
os.kill(child.pid, signal.SIGKILL)
|
os.kill(child.pid, signal.SIGKILL)
|
||||||
@@ -1615,6 +1615,9 @@ class RunAdHocCommand(BaseTask):
|
|||||||
if ad_hoc_command.verbosity:
|
if ad_hoc_command.verbosity:
|
||||||
args.append('-%s' % ('v' * min(5, ad_hoc_command.verbosity)))
|
args.append('-%s' % ('v' * min(5, ad_hoc_command.verbosity)))
|
||||||
|
|
||||||
|
if ad_hoc_command.extra_vars_dict:
|
||||||
|
args.extend(['-e', json.dumps(ad_hoc_command.extra_vars_dict)])
|
||||||
|
|
||||||
args.extend(['-m', ad_hoc_command.module_name])
|
args.extend(['-m', ad_hoc_command.module_name])
|
||||||
args.extend(['-a', ad_hoc_command.module_args])
|
args.extend(['-a', ad_hoc_command.module_args])
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,29 @@ def test_middleware_actor_added(monkeypatch, post, get, user):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data['summary_fields']['actor']['username'] == 'admin-poster'
|
assert response.data['summary_fields']['actor']['username'] == 'admin-poster'
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True), reason="Activity stream not enabled")
|
||||||
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_rbac_stream_resource_roles(mocker, organization, user):
|
||||||
|
member = user('test', False)
|
||||||
|
organization.admin_role.members.add(member)
|
||||||
|
|
||||||
|
activity_stream = ActivityStream.objects.filter(organization__pk=organization.pk, operation='associate').first()
|
||||||
|
assert activity_stream.user.first() == member
|
||||||
|
assert activity_stream.organization.first() == organization
|
||||||
|
assert activity_stream.role.first() == organization.admin_role
|
||||||
|
assert activity_stream.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not getattr(settings, 'ACTIVITY_STREAM_ENABLED', True), reason="Activity stream not enabled")
|
||||||
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_rbac_stream_user_roles(mocker, organization, user):
|
||||||
|
member = user('test', False)
|
||||||
|
member.roles.add(organization.admin_role)
|
||||||
|
|
||||||
|
activity_stream = ActivityStream.objects.filter(organization__pk=organization.pk, operation='associate').first()
|
||||||
|
assert activity_stream.user.first() == member
|
||||||
|
assert activity_stream.organization.first() == organization
|
||||||
|
assert activity_stream.role.first() == organization.admin_role
|
||||||
|
assert activity_stream.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ def test_create_user_credential_via_credentials_list(post, get, alice):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_user_credential_via_user_credentials_list(post, get, alice):
|
def test_create_user_credential_via_user_credentials_list(post, get, alice):
|
||||||
response = post(reverse('api:user_credentials_list', args=(alice.pk,)), {
|
response = post(reverse('api:user_credentials_list', args=(alice.pk,)), {
|
||||||
|
'user': alice.pk,
|
||||||
'name': 'Some name',
|
'name': 'Some name',
|
||||||
'username': 'someusername',
|
'username': 'someusername',
|
||||||
}, alice)
|
}, alice)
|
||||||
@@ -45,6 +46,7 @@ def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob):
|
def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob):
|
||||||
response = post(reverse('api:user_credentials_list', args=(bob.pk,)), {
|
response = post(reverse('api:user_credentials_list', args=(bob.pk,)), {
|
||||||
|
'user': bob.pk,
|
||||||
'name': 'Some name',
|
'name': 'Some name',
|
||||||
'username': 'someusername'
|
'username': 'someusername'
|
||||||
}, alice)
|
}, alice)
|
||||||
@@ -71,6 +73,7 @@ def test_create_team_credential(post, get, team, org_admin, team_member):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_team_credential_via_team_credentials_list(post, get, team, org_admin, team_member):
|
def test_create_team_credential_via_team_credentials_list(post, get, team, org_admin, team_member):
|
||||||
response = post(reverse('api:team_credentials_list', args=(team.pk,)), {
|
response = post(reverse('api:team_credentials_list', args=(team.pk,)), {
|
||||||
|
'team': team.pk,
|
||||||
'name': 'Some name',
|
'name': 'Some name',
|
||||||
'username': 'someusername',
|
'username': 'someusername',
|
||||||
}, org_admin)
|
}, org_admin)
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ def runtime_data(organization):
|
|||||||
credential=cred_obj.pk,
|
credential=cred_obj.pk,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def job_with_links(machine_credential, inventory):
|
||||||
|
return Job.objects.create(name='existing-job', credential=machine_credential, inventory=inventory)
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_prompts(project, inventory, machine_credential):
|
def job_template_prompts(project, inventory, machine_credential):
|
||||||
def rf(on_off):
|
def rf(on_off):
|
||||||
@@ -155,7 +159,7 @@ def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_promp
|
|||||||
dict(extra_vars='{"unbalanced brackets":'), user('admin', True))
|
dict(extra_vars='{"unbalanced brackets":'), user('admin', True))
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert response.data['extra_vars'] == ['Must be valid JSON or YAML']
|
assert response.data['extra_vars'] == ['Must be a valid JSON or YAML dictionary']
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
@@ -167,51 +171,72 @@ def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, user):
|
|||||||
args=[deploy_jobtemplate.pk]), {}, user('admin', True))
|
args=[deploy_jobtemplate.pk]), {}, user('admin', True))
|
||||||
|
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert response.data['inventory'] == ['Job Template Inventory is missing or undefined']
|
assert response.data['inventory'] == ['Job Template Inventory is missing or undefined.']
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime_data, machine_credential, post, user, mocker):
|
def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime_data, post, user):
|
||||||
job_template = job_template_prompts(True)
|
job_template = job_template_prompts(True)
|
||||||
common_user = user('test-user', False)
|
common_user = user('test-user', False)
|
||||||
job_template.execute_role.members.add(common_user)
|
job_template.execute_role.members.add(common_user)
|
||||||
|
|
||||||
# Assure that the base job template can be launched to begin with
|
|
||||||
mock_job = mocker.MagicMock(spec=Job, id=968, **runtime_data)
|
|
||||||
with mocker.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job', return_value=mock_job):
|
|
||||||
with mocker.patch('awx.api.serializers.JobSerializer.to_representation'):
|
|
||||||
response = post(reverse('api:job_template_launch',
|
|
||||||
args=[job_template.pk]), {}, common_user)
|
|
||||||
|
|
||||||
assert response.status_code == 201
|
|
||||||
|
|
||||||
# Assure that giving an inventory without access to the inventory blocks the launch
|
# Assure that giving an inventory without access to the inventory blocks the launch
|
||||||
new_inv = job_template.project.organization.inventories.create(name="user-can-not-use")
|
runtime_inventory = Inventory.objects.get(pk=runtime_data['inventory'])
|
||||||
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
|
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
|
||||||
dict(inventory=new_inv.pk), common_user)
|
dict(inventory=runtime_inventory.pk), common_user)
|
||||||
|
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
assert response.data['detail'] == u'You do not have permission to perform this action.'
|
assert response.data['detail'] == u'You do not have permission to perform this action.'
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_relaunch_copy_vars(runtime_data, job_template_prompts, project, post, mocker):
|
def test_job_launch_fails_without_credential_access(job_template_prompts, runtime_data, post, user):
|
||||||
job_template = job_template_prompts(True)
|
job_template = job_template_prompts(True)
|
||||||
|
common_user = user('test-user', False)
|
||||||
|
job_template.execute_role.members.add(common_user)
|
||||||
|
|
||||||
# Create a job with the given data that will be relaunched
|
# Assure that giving a credential without access blocks the launch
|
||||||
job_create_kwargs = runtime_data
|
runtime_credential = Credential.objects.get(pk=runtime_data['credential'])
|
||||||
inv_obj = Inventory.objects.get(pk=job_create_kwargs.pop('inventory'))
|
response = post(reverse('api:job_template_launch', args=[job_template.pk]),
|
||||||
cred_obj = Credential.objects.get(pk=job_create_kwargs.pop('credential'))
|
dict(credential=runtime_credential.pk), common_user)
|
||||||
original_job = Job.objects.create(inventory=inv_obj, credential=cred_obj, job_template=job_template, **job_create_kwargs)
|
|
||||||
with mocker.patch('awx.main.models.unified_jobs.UnifiedJobTemplate._get_unified_job_field_names', return_value=runtime_data.keys()):
|
assert response.status_code == 403
|
||||||
second_job = original_job.copy()
|
assert response.data['detail'] == u'You do not have permission to perform this action.'
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.job_runtime_vars
|
||||||
|
def test_job_relaunch_copy_vars(job_with_links, machine_credential, inventory,
|
||||||
|
deploy_jobtemplate, post, mocker):
|
||||||
|
job_with_links.job_template = deploy_jobtemplate
|
||||||
|
job_with_links.limit = "my_server"
|
||||||
|
with mocker.patch('awx.main.models.unified_jobs.UnifiedJobTemplate._get_unified_job_field_names',
|
||||||
|
return_value=['inventory', 'credential', 'limit']):
|
||||||
|
second_job = job_with_links.copy()
|
||||||
|
|
||||||
# Check that job data matches the original variables
|
# Check that job data matches the original variables
|
||||||
assert 'job_launch_var' in yaml.load(second_job.extra_vars)
|
assert second_job.credential == job_with_links.credential
|
||||||
assert original_job.limit == second_job.limit
|
assert second_job.inventory == job_with_links.inventory
|
||||||
assert original_job.job_type == second_job.job_type
|
assert second_job.limit == 'my_server'
|
||||||
assert original_job.inventory.pk == second_job.inventory.pk
|
|
||||||
assert original_job.job_tags == second_job.job_tags
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.job_runtime_vars
|
||||||
|
def test_job_relaunch_resource_access(job_with_links, user):
|
||||||
|
inventory_user = user('user1', False)
|
||||||
|
credential_user = user('user2', False)
|
||||||
|
both_user = user('user3', False)
|
||||||
|
|
||||||
|
# Confirm that a user with inventory & credential access can launch
|
||||||
|
job_with_links.credential.use_role.members.add(both_user)
|
||||||
|
job_with_links.inventory.use_role.members.add(both_user)
|
||||||
|
assert both_user.can_access(Job, 'start', job_with_links)
|
||||||
|
|
||||||
|
# Confirm that a user with credential access alone can not launch
|
||||||
|
job_with_links.credential.use_role.members.add(credential_user)
|
||||||
|
assert not credential_user.can_access(Job, 'start', job_with_links)
|
||||||
|
|
||||||
|
# Confirm that a user with inventory access alone can not launch
|
||||||
|
job_with_links.inventory.use_role.members.add(inventory_user)
|
||||||
|
assert not inventory_user.can_access(Job, 'start', job_with_links)
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_job_launch_JT_with_validation(machine_credential, deploy_jobtemplate):
|
def test_job_launch_JT_with_validation(machine_credential, deploy_jobtemplate):
|
||||||
|
|||||||
@@ -150,6 +150,26 @@ def test_two_organizations(resourced_organization, organizations, user, get):
|
|||||||
assert counts[org_id_full] == COUNTS_PRIMES
|
assert counts[org_id_full] == COUNTS_PRIMES
|
||||||
assert counts[org_id_zero] == COUNTS_ZEROS
|
assert counts[org_id_zero] == COUNTS_ZEROS
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_scan_JT_counted(resourced_organization, user, get):
|
||||||
|
admin_user = user('admin', True)
|
||||||
|
# Add a scan job template to the org
|
||||||
|
resourced_organization.projects.all()[0].jobtemplates.create(
|
||||||
|
job_type='scan', inventory=resourced_organization.inventories.all()[0],
|
||||||
|
name='scan-job-template')
|
||||||
|
counts_dict = COUNTS_PRIMES
|
||||||
|
counts_dict['job_templates'] += 1
|
||||||
|
|
||||||
|
# Test list view
|
||||||
|
list_response = get(reverse('api:organization_list', args=[]), admin_user)
|
||||||
|
assert list_response.status_code == 200
|
||||||
|
assert list_response.data['results'][0]['summary_fields']['related_field_counts'] == counts_dict
|
||||||
|
|
||||||
|
# Test detail view
|
||||||
|
detail_response = get(reverse('api:organization_detail', args=[resourced_organization.pk]), admin_user)
|
||||||
|
assert detail_response.status_code == 200
|
||||||
|
assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_JT_associated_with_project(organizations, project, user, get):
|
def test_JT_associated_with_project(organizations, project, user, get):
|
||||||
# Check that adding a project to an organization gets the project's JT
|
# Check that adding a project to an organization gets the project's JT
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from awx.main.models import Role
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_indirect_access_list(get, organization, project, team_factory, user, admin):
|
def test_indirect_access_list(get, organization, project, team_factory, user, admin):
|
||||||
@@ -53,5 +54,5 @@ def test_indirect_access_list(get, organization, project, team_factory, user, ad
|
|||||||
assert org_admin_team_member_entry['team_name'] == org_admin_team.name
|
assert org_admin_team_member_entry['team_name'] == org_admin_team.name
|
||||||
|
|
||||||
admin_entry = admin_res['summary_fields']['indirect_access'][0]['role']
|
admin_entry = admin_res['summary_fields']['indirect_access'][0]['role']
|
||||||
assert admin_entry['name'] == 'System Administrator'
|
assert admin_entry['name'] == Role.singleton('system_administrator').name
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ from awx.main.models.organization import (
|
|||||||
Team,
|
Team,
|
||||||
)
|
)
|
||||||
|
|
||||||
from awx.main.models.rbac import Role
|
|
||||||
from awx.main.models.notifications import Notifier
|
from awx.main.models.notifications import Notifier
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -193,11 +192,6 @@ def notifier(organization):
|
|||||||
notification_type="webhook",
|
notification_type="webhook",
|
||||||
notification_configuration=dict(url="http://localhost",
|
notification_configuration=dict(url="http://localhost",
|
||||||
headers={"Test": "Header"}))
|
headers={"Test": "Header"}))
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def role():
|
|
||||||
return Role.objects.create(name='role')
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def admin(user):
|
def admin(user):
|
||||||
return user('admin', True)
|
return user('admin', True)
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ def mock_feature_enabled(feature, bypass_database=None):
|
|||||||
|
|
||||||
#@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
#@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def role():
|
||||||
|
return Role.objects.create()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# /roles
|
# /roles
|
||||||
@@ -85,13 +89,14 @@ def test_get_user_roles_list(get, admin):
|
|||||||
response = get(url, admin)
|
response = get(url, admin)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
roles = response.data
|
roles = response.data
|
||||||
assert roles['count'] > 0 # 'System Administrator' role if nothing else
|
assert roles['count'] > 0 # 'system_administrator' role if nothing else
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):
|
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):
|
||||||
'Users can see roles for other users, but only the roles that that user has access to see as well'
|
'Users can see roles for other users, but only the roles that that user has access to see as well'
|
||||||
organization.member_role.members.add(alice)
|
organization.member_role.members.add(alice)
|
||||||
organization.admin_role.members.add(bob)
|
organization.admin_role.members.add(bob)
|
||||||
|
organization.member_role.members.add(bob)
|
||||||
custom_role = Role.objects.create(name='custom_role-test_user_view_admin_roles_list')
|
custom_role = Role.objects.create(name='custom_role-test_user_view_admin_roles_list')
|
||||||
organization.member_role.children.add(custom_role)
|
organization.member_role.children.add(custom_role)
|
||||||
team.member_role.members.add(bob)
|
team.member_role.members.add(bob)
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ from awx.main.models import (
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_auto_inheritance_by_children(organization, alice):
|
def test_auto_inheritance_by_children(organization, alice):
|
||||||
A = Role.objects.create(name='A', role_field='')
|
A = Role.objects.create()
|
||||||
B = Role.objects.create(name='B', role_field='')
|
B = Role.objects.create()
|
||||||
A.members.add(alice)
|
A.members.add(alice)
|
||||||
|
|
||||||
assert alice not in organization.admin_role
|
assert alice not in organization.admin_role
|
||||||
@@ -38,8 +38,8 @@ def test_auto_inheritance_by_children(organization, alice):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_auto_inheritance_by_parents(organization, alice):
|
def test_auto_inheritance_by_parents(organization, alice):
|
||||||
A = Role.objects.create(name='A')
|
A = Role.objects.create()
|
||||||
B = Role.objects.create(name='B')
|
B = Role.objects.create()
|
||||||
A.members.add(alice)
|
A.members.add(alice)
|
||||||
|
|
||||||
assert alice not in organization.admin_role
|
assert alice not in organization.admin_role
|
||||||
@@ -58,9 +58,9 @@ def test_auto_inheritance_by_parents(organization, alice):
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_accessible_objects(organization, alice, bob):
|
def test_accessible_objects(organization, alice, bob):
|
||||||
A = Role.objects.create(name='A')
|
A = Role.objects.create()
|
||||||
A.members.add(alice)
|
A.members.add(alice)
|
||||||
B = Role.objects.create(name='B')
|
B = Role.objects.create()
|
||||||
B.members.add(alice)
|
B.members.add(alice)
|
||||||
B.members.add(bob)
|
B.members.add(bob)
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ def test_auto_field_adjustments(organization, inventory, team, alice):
|
|||||||
def test_implicit_deletes(alice):
|
def test_implicit_deletes(alice):
|
||||||
'Ensures implicit resources and roles delete themselves'
|
'Ensures implicit resources and roles delete themselves'
|
||||||
delorg = Organization.objects.create(name='test-org')
|
delorg = Organization.objects.create(name='test-org')
|
||||||
child = Role.objects.create(name='child-role')
|
child = Role.objects.create()
|
||||||
child.parents.add(delorg.admin_role)
|
child.parents.add(delorg.admin_role)
|
||||||
delorg.admin_role.members.add(alice)
|
delorg.admin_role.members.add(alice)
|
||||||
|
|
||||||
@@ -129,14 +129,14 @@ def test_implicit_deletes(alice):
|
|||||||
assert Role.objects.filter(id=admin_role_id).count() == 1
|
assert Role.objects.filter(id=admin_role_id).count() == 1
|
||||||
assert Role.objects.filter(id=auditor_role_id).count() == 1
|
assert Role.objects.filter(id=auditor_role_id).count() == 1
|
||||||
n_alice_roles = alice.roles.count()
|
n_alice_roles = alice.roles.count()
|
||||||
n_system_admin_children = Role.singleton('System Administrator').children.count()
|
n_system_admin_children = Role.singleton('system_administrator').children.count()
|
||||||
|
|
||||||
delorg.delete()
|
delorg.delete()
|
||||||
|
|
||||||
assert Role.objects.filter(id=admin_role_id).count() == 0
|
assert Role.objects.filter(id=admin_role_id).count() == 0
|
||||||
assert Role.objects.filter(id=auditor_role_id).count() == 0
|
assert Role.objects.filter(id=auditor_role_id).count() == 0
|
||||||
assert alice.roles.count() == (n_alice_roles - 1)
|
assert alice.roles.count() == (n_alice_roles - 1)
|
||||||
assert Role.singleton('System Administrator').children.count() == (n_system_admin_children - 1)
|
assert Role.singleton('system_administrator').children.count() == (n_system_admin_children - 1)
|
||||||
assert child.ancestors.count() == 1
|
assert child.ancestors.count() == 1
|
||||||
assert child.ancestors.all()[0] == child
|
assert child.ancestors.all()[0] == child
|
||||||
|
|
||||||
@@ -152,11 +152,11 @@ def test_content_object(user):
|
|||||||
def test_hierarchy_rebuilding_multi_path():
|
def test_hierarchy_rebuilding_multi_path():
|
||||||
'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
|
'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
|
||||||
|
|
||||||
X = Role.objects.create(name='X')
|
X = Role.objects.create()
|
||||||
A = Role.objects.create(name='A')
|
A = Role.objects.create()
|
||||||
B = Role.objects.create(name='B')
|
B = Role.objects.create()
|
||||||
C = Role.objects.create(name='C')
|
C = Role.objects.create()
|
||||||
D = Role.objects.create(name='D')
|
D = Role.objects.create()
|
||||||
|
|
||||||
A.children.add(B)
|
A.children.add(B)
|
||||||
A.children.add(D)
|
A.children.add(D)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def test_credential_use_role(credential, user, permissions):
|
|||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_credential_migration_team_member(credential, team, user, permissions):
|
def test_credential_migration_team_member(credential, team, user, permissions):
|
||||||
u = user('user', False)
|
u = user('user', False)
|
||||||
team.admin_role.members.add(u)
|
team.member_role.members.add(u)
|
||||||
credential.deprecated_team = team
|
credential.deprecated_team = team
|
||||||
credential.save()
|
credential.save()
|
||||||
|
|
||||||
@@ -91,7 +91,8 @@ def test_credential_access_admin(user, team, credential):
|
|||||||
assert access.can_change(credential, {'user': u.pk})
|
assert access.can_change(credential, {'user': u.pk})
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cred_job_template(user, deploy_jobtemplate):
|
def test_cred_job_template_xfail(user, deploy_jobtemplate):
|
||||||
|
' Personal credential migration '
|
||||||
a = user('admin', False)
|
a = user('admin', False)
|
||||||
org = deploy_jobtemplate.project.organization
|
org = deploy_jobtemplate.project.organization
|
||||||
org.admin_role.members.add(a)
|
org.admin_role.members.add(a)
|
||||||
@@ -102,19 +103,17 @@ def test_cred_job_template(user, deploy_jobtemplate):
|
|||||||
|
|
||||||
access = CredentialAccess(a)
|
access = CredentialAccess(a)
|
||||||
rbac.migrate_credential(apps, None)
|
rbac.migrate_credential(apps, None)
|
||||||
assert access.can_change(cred, {'organization': org.pk})
|
|
||||||
|
|
||||||
org.admin_role.members.remove(a)
|
|
||||||
assert not access.can_change(cred, {'organization': org.pk})
|
assert not access.can_change(cred, {'organization': org.pk})
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cred_multi_job_template_single_org(user, deploy_jobtemplate):
|
def test_cred_job_template(user, team, deploy_jobtemplate):
|
||||||
|
' Team credential migration => org credential '
|
||||||
a = user('admin', False)
|
a = user('admin', False)
|
||||||
org = deploy_jobtemplate.project.organization
|
org = deploy_jobtemplate.project.organization
|
||||||
org.admin_role.members.add(a)
|
org.admin_role.members.add(a)
|
||||||
|
|
||||||
cred = deploy_jobtemplate.credential
|
cred = deploy_jobtemplate.credential
|
||||||
cred.deprecated_user = user('john', False)
|
cred.deprecated_team = team
|
||||||
cred.save()
|
cred.save()
|
||||||
|
|
||||||
access = CredentialAccess(a)
|
access = CredentialAccess(a)
|
||||||
@@ -125,8 +124,42 @@ def test_cred_multi_job_template_single_org(user, deploy_jobtemplate):
|
|||||||
assert not access.can_change(cred, {'organization': org.pk})
|
assert not access.can_change(cred, {'organization': org.pk})
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_single_cred_multi_job_template_multi_org(user, organizations, credential):
|
def test_cred_multi_job_template_single_org_xfail(user, deploy_jobtemplate):
|
||||||
|
a = user('admin', False)
|
||||||
|
org = deploy_jobtemplate.project.organization
|
||||||
|
org.admin_role.members.add(a)
|
||||||
|
|
||||||
|
cred = deploy_jobtemplate.credential
|
||||||
|
cred.deprecated_user = user('john', False)
|
||||||
|
cred.save()
|
||||||
|
|
||||||
|
access = CredentialAccess(a)
|
||||||
|
rbac.migrate_credential(apps, None)
|
||||||
|
assert not access.can_change(cred, {'organization': org.pk})
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_cred_multi_job_template_single_org(user, team, deploy_jobtemplate):
|
||||||
|
a = user('admin', False)
|
||||||
|
org = deploy_jobtemplate.project.organization
|
||||||
|
org.admin_role.members.add(a)
|
||||||
|
|
||||||
|
cred = deploy_jobtemplate.credential
|
||||||
|
cred.deprecated_team = team
|
||||||
|
cred.save()
|
||||||
|
|
||||||
|
access = CredentialAccess(a)
|
||||||
|
rbac.migrate_credential(apps, None)
|
||||||
|
assert access.can_change(cred, {'organization': org.pk})
|
||||||
|
|
||||||
|
org.admin_role.members.remove(a)
|
||||||
|
assert not access.can_change(cred, {'organization': org.pk})
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_single_cred_multi_job_template_multi_org(user, organizations, credential, team):
|
||||||
orgs = organizations(2)
|
orgs = organizations(2)
|
||||||
|
credential.deprecated_team = team
|
||||||
|
credential.save()
|
||||||
|
|
||||||
jts = []
|
jts = []
|
||||||
for org in orgs:
|
for org in orgs:
|
||||||
inv = org.inventories.create(name="inv-%d" % org.pk)
|
inv = org.inventories.create(name="inv-%d" % org.pk)
|
||||||
@@ -169,7 +202,7 @@ def test_cred_inventory_source(user, inventory, credential):
|
|||||||
assert u not in credential.use_role
|
assert u not in credential.use_role
|
||||||
|
|
||||||
rbac.migrate_credential(apps, None)
|
rbac.migrate_credential(apps, None)
|
||||||
assert u in credential.use_role
|
assert u not in credential.use_role
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cred_project(user, credential, project):
|
def test_cred_project(user, credential, project):
|
||||||
@@ -181,7 +214,7 @@ def test_cred_project(user, credential, project):
|
|||||||
assert u not in credential.use_role
|
assert u not in credential.use_role
|
||||||
|
|
||||||
rbac.migrate_credential(apps, None)
|
rbac.migrate_credential(apps, None)
|
||||||
assert u in credential.use_role
|
assert u not in credential.use_role
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cred_no_org(user, credential):
|
def test_cred_no_org(user, credential):
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ def test_job_template_team_migration_check(deploy_jobtemplate, check_jobtemplate
|
|||||||
rbac.migrate_projects(apps, None)
|
rbac.migrate_projects(apps, None)
|
||||||
rbac.migrate_inventory(apps, None)
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
assert joe in check_jobtemplate.read_role
|
assert joe not in check_jobtemplate.read_role
|
||||||
assert admin in check_jobtemplate.execute_role
|
assert admin in check_jobtemplate.execute_role
|
||||||
assert joe not in check_jobtemplate.execute_role
|
assert joe not in check_jobtemplate.execute_role
|
||||||
|
|
||||||
@@ -120,12 +120,13 @@ def test_job_template_team_deploy_migration(deploy_jobtemplate, check_jobtemplat
|
|||||||
rbac.migrate_projects(apps, None)
|
rbac.migrate_projects(apps, None)
|
||||||
rbac.migrate_inventory(apps, None)
|
rbac.migrate_inventory(apps, None)
|
||||||
|
|
||||||
assert joe in deploy_jobtemplate.read_role
|
assert joe not in deploy_jobtemplate.read_role
|
||||||
assert admin in deploy_jobtemplate.execute_role
|
assert admin in deploy_jobtemplate.execute_role
|
||||||
assert joe not in deploy_jobtemplate.execute_role
|
assert joe not in deploy_jobtemplate.execute_role
|
||||||
|
|
||||||
rbac.migrate_job_templates(apps, None)
|
rbac.migrate_job_templates(apps, None)
|
||||||
|
|
||||||
|
assert joe in deploy_jobtemplate.read_role
|
||||||
assert admin in deploy_jobtemplate.execute_role
|
assert admin in deploy_jobtemplate.execute_role
|
||||||
assert joe in deploy_jobtemplate.execute_role
|
assert joe in deploy_jobtemplate.execute_role
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ def test_project_user_project(user_project, project, user):
|
|||||||
def test_project_accessible_by_sa(user, project):
|
def test_project_accessible_by_sa(user, project):
|
||||||
u = user('systemadmin', is_superuser=True)
|
u = user('systemadmin', is_superuser=True)
|
||||||
# This gets setup by a signal, but we want to test the migration which will set this up too, so remove it
|
# This gets setup by a signal, but we want to test the migration which will set this up too, so remove it
|
||||||
Role.singleton('System Administrator').members.remove(u)
|
Role.singleton('system_administrator').members.remove(u)
|
||||||
|
|
||||||
assert u not in project.read_role
|
assert u not in project.read_role
|
||||||
rbac.migrate_organization(apps, None)
|
rbac.migrate_organization(apps, None)
|
||||||
|
|||||||
@@ -3,6 +3,25 @@ import pytest
|
|||||||
from awx.main.access import TeamAccess
|
from awx.main.access import TeamAccess
|
||||||
from awx.main.models import Project
|
from awx.main.models import Project
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_team_attach_unattach(team, user):
|
||||||
|
u = user('member', False)
|
||||||
|
access = TeamAccess(u)
|
||||||
|
|
||||||
|
team.member_role.members.add(u)
|
||||||
|
assert not access.can_attach(team, u.admin_role, 'member_role.children', None)
|
||||||
|
assert not access.can_unattach(team, u.admin_role, 'member_role.children')
|
||||||
|
|
||||||
|
team.admin_role.members.add(u)
|
||||||
|
assert access.can_attach(team, u.admin_role, 'member_role.children', None)
|
||||||
|
assert access.can_unattach(team, u.admin_role, 'member_role.children')
|
||||||
|
|
||||||
|
u2 = user('non-member', False)
|
||||||
|
access = TeamAccess(u2)
|
||||||
|
assert not access.can_attach(team, u2.admin_role, 'member_role.children', None)
|
||||||
|
assert not access.can_unattach(team, u2.admin_role, 'member_role.chidlren')
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_team_access_superuser(team, user):
|
def test_team_access_superuser(team, user):
|
||||||
team.member_role.members.add(user('member', False))
|
team.member_role.members.add(user('member', False))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ def test_user_admin(user_project, project, user):
|
|||||||
|
|
||||||
joe = user(username, is_superuser = False)
|
joe = user(username, is_superuser = False)
|
||||||
admin = user('admin', is_superuser = True)
|
admin = user('admin', is_superuser = True)
|
||||||
sa = Role.singleton('System Administrator')
|
sa = Role.singleton('system_administrator')
|
||||||
|
|
||||||
# this should happen automatically with our signal
|
# this should happen automatically with our signal
|
||||||
assert sa.members.filter(id=admin.id).exists() is True
|
assert sa.members.filter(id=admin.id).exists() is True
|
||||||
|
|||||||
17
awx/main/tests/unit/api/test_filters.py
Normal file
17
awx/main/tests/unit/api/test_filters.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from awx.api.filters import FieldLookupBackend
|
||||||
|
from awx.main.models import JobTemplate
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(u"empty_value", [u'', ''])
|
||||||
|
def test_empty_in(empty_value):
|
||||||
|
field_lookup = FieldLookupBackend()
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
field_lookup.value_to_python(JobTemplate, 'project__in', empty_value)
|
||||||
|
assert 'empty value for __in' in str(excinfo.value)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(u"valid_value", [u'foo', u'foo,'])
|
||||||
|
def test_valid_in(valid_value):
|
||||||
|
field_lookup = FieldLookupBackend()
|
||||||
|
value, new_lookup = field_lookup.value_to_python(JobTemplate, 'project__in', valid_value)
|
||||||
|
assert 'foo' in value
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
|
|
||||||
# Python
|
# Python
|
||||||
import pytest
|
import pytest
|
||||||
|
import mock
|
||||||
|
|
||||||
# DRF
|
# DRF
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.generics import ParentMixin, SubListCreateAttachDetachAPIView
|
from awx.api.generics import ParentMixin, SubListCreateAttachDetachAPIView, DeleteLastUnattachLabelMixin
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def get_object_or_404(mocker):
|
def get_object_or_404(mocker):
|
||||||
@@ -37,7 +38,7 @@ def parent_relationship_factory(mocker):
|
|||||||
return (serializer, mock_parent_relationship)
|
return (serializer, mock_parent_relationship)
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
# TODO: Test create and associate failure (i.e. id doesn't exist or record already exists)
|
# TODO: Test create and associate failure (i.e. id doesn't exist, record already exists, permission denied)
|
||||||
# TODO: Mock and check return (Response)
|
# TODO: Mock and check return (Response)
|
||||||
class TestSubListCreateAttachDetachAPIView:
|
class TestSubListCreateAttachDetachAPIView:
|
||||||
def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_relationship_factory, mock_response_new):
|
def test_attach_create_and_associate(self, mocker, get_object_or_400, parent_relationship_factory, mock_response_new):
|
||||||
@@ -65,6 +66,97 @@ class TestSubListCreateAttachDetachAPIView:
|
|||||||
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
mock_parent_relationship.wife.add.assert_called_with(get_object_or_400.return_value)
|
||||||
mock_response_new.assert_called_with(Response, status=status.HTTP_204_NO_CONTENT)
|
mock_response_new.assert_called_with(Response, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def test_unattach_validate_ok(self, mocker):
|
||||||
|
mock_request = mocker.MagicMock(data=dict(id=1))
|
||||||
|
serializer = SubListCreateAttachDetachAPIView()
|
||||||
|
|
||||||
|
(sub_id, res) = serializer.unattach_validate(mock_request)
|
||||||
|
|
||||||
|
assert sub_id == 1
|
||||||
|
assert res is None
|
||||||
|
|
||||||
|
def test_unattach_validate_missing_id(self, mocker):
|
||||||
|
mock_request = mocker.MagicMock(data=dict())
|
||||||
|
serializer = SubListCreateAttachDetachAPIView()
|
||||||
|
|
||||||
|
(sub_id, res) = serializer.unattach_validate(mock_request)
|
||||||
|
|
||||||
|
assert sub_id is None
|
||||||
|
assert type(res) is Response
|
||||||
|
|
||||||
|
def test_unattach_by_id_ok(self, mocker, parent_relationship_factory, get_object_or_400):
|
||||||
|
(serializer, mock_parent_relationship) = parent_relationship_factory(SubListCreateAttachDetachAPIView, 'wife')
|
||||||
|
mock_request = mocker.MagicMock()
|
||||||
|
mock_sub = mocker.MagicMock(name="object to unattach")
|
||||||
|
get_object_or_400.return_value = mock_sub
|
||||||
|
|
||||||
|
res = serializer.unattach_by_id(mock_request, 1)
|
||||||
|
|
||||||
|
assert type(res) is Response
|
||||||
|
assert res.status_code == status.HTTP_204_NO_CONTENT
|
||||||
|
mock_parent_relationship.wife.remove.assert_called_with(mock_sub)
|
||||||
|
|
||||||
|
def test_unattach_ok(self, mocker):
|
||||||
|
mock_request = mocker.MagicMock()
|
||||||
|
mock_sub_id = mocker.MagicMock()
|
||||||
|
view = SubListCreateAttachDetachAPIView()
|
||||||
|
view.unattach_validate = mocker.MagicMock()
|
||||||
|
view.unattach_by_id = mocker.MagicMock()
|
||||||
|
view.unattach_validate.return_value = (mock_sub_id, None)
|
||||||
|
|
||||||
|
view.unattach(mock_request)
|
||||||
|
|
||||||
|
view.unattach_validate.assert_called_with(mock_request)
|
||||||
|
view.unattach_by_id.assert_called_with(mock_request, mock_sub_id)
|
||||||
|
|
||||||
|
def test_unattach_invalid(self, mocker):
|
||||||
|
mock_request = mocker.MagicMock()
|
||||||
|
mock_res = mocker.MagicMock()
|
||||||
|
view = SubListCreateAttachDetachAPIView()
|
||||||
|
view.unattach_validate = mocker.MagicMock()
|
||||||
|
view.unattach_by_id = mocker.MagicMock()
|
||||||
|
view.unattach_validate.return_value = (None, mock_res)
|
||||||
|
|
||||||
|
view.unattach(mock_request)
|
||||||
|
|
||||||
|
view.unattach_validate.assert_called_with(mock_request)
|
||||||
|
view.unattach_by_id.assert_not_called()
|
||||||
|
|
||||||
|
class TestDeleteLastUnattachLabelMixin:
|
||||||
|
@mock.patch('awx.api.generics.super')
|
||||||
|
def test_unattach_ok(self, super, mocker):
|
||||||
|
mock_request = mocker.MagicMock()
|
||||||
|
mock_sub_id = mocker.MagicMock()
|
||||||
|
super.return_value = super
|
||||||
|
super.unattach_validate = mocker.MagicMock(return_value=(mock_sub_id, None))
|
||||||
|
super.unattach_by_id = mocker.MagicMock()
|
||||||
|
mock_label = mocker.patch('awx.api.generics.Label')
|
||||||
|
mock_label.objects.get.return_value = mock_label
|
||||||
|
mock_label.is_detached.return_value = True
|
||||||
|
|
||||||
|
view = DeleteLastUnattachLabelMixin()
|
||||||
|
|
||||||
|
view.unattach(mock_request, None, None)
|
||||||
|
|
||||||
|
super.unattach_validate.assert_called_with(mock_request, None, None)
|
||||||
|
super.unattach_by_id.assert_called_with(mock_request, mock_sub_id)
|
||||||
|
mock_label.is_detached.assert_called_with()
|
||||||
|
mock_label.objects.get.assert_called_with(id=mock_sub_id)
|
||||||
|
mock_label.delete.assert_called_with()
|
||||||
|
|
||||||
|
@mock.patch('awx.api.generics.super')
|
||||||
|
def test_unattach_fail(self, super, mocker):
|
||||||
|
mock_request = mocker.MagicMock()
|
||||||
|
mock_response = mocker.MagicMock()
|
||||||
|
super.return_value = super
|
||||||
|
super.unattach_validate = mocker.MagicMock(return_value=(None, mock_response))
|
||||||
|
view = DeleteLastUnattachLabelMixin()
|
||||||
|
|
||||||
|
res = view.unattach(mock_request, None, None)
|
||||||
|
|
||||||
|
super.unattach_validate.assert_called_with(mock_request, None, None)
|
||||||
|
assert mock_response == res
|
||||||
|
|
||||||
class TestParentMixin:
|
class TestParentMixin:
|
||||||
def test_get_parent_object(self, mocker, get_object_or_404):
|
def test_get_parent_object(self, mocker, get_object_or_404):
|
||||||
parent_mixin = ParentMixin()
|
parent_mixin = ParentMixin()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Python
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# AWX
|
from awx.api.views import (
|
||||||
from awx.api.views import ApiV1RootView
|
ApiV1RootView,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_response_new(mocker):
|
def mock_response_new(mocker):
|
||||||
@@ -10,6 +11,7 @@ def mock_response_new(mocker):
|
|||||||
m.return_value = m
|
m.return_value = m
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
class TestApiV1RootView:
|
class TestApiV1RootView:
|
||||||
def test_get_endpoints(self, mocker, mock_response_new):
|
def test_get_endpoints(self, mocker, mock_response_new):
|
||||||
endpoints = [
|
endpoints = [
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
from awx.main.models.label import Label
|
from awx.main.models.label import Label
|
||||||
|
from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob
|
||||||
|
|
||||||
|
|
||||||
def test_get_orphaned_labels(mocker):
|
def test_get_orphaned_labels(mocker):
|
||||||
mock_query_set = mocker.MagicMock()
|
mock_query_set = mocker.MagicMock()
|
||||||
@@ -8,3 +12,54 @@ def test_get_orphaned_labels(mocker):
|
|||||||
|
|
||||||
assert mock_query_set == ret
|
assert mock_query_set == ret
|
||||||
Label.objects.filter.assert_called_with(organization=None, jobtemplate_labels__isnull=True)
|
Label.objects.filter.assert_called_with(organization=None, jobtemplate_labels__isnull=True)
|
||||||
|
|
||||||
|
def test_is_detached(mocker):
|
||||||
|
mock_query_set = mocker.MagicMock()
|
||||||
|
Label.objects.filter = mocker.MagicMock(return_value=mock_query_set)
|
||||||
|
mock_query_set.count.return_value = 1
|
||||||
|
|
||||||
|
label = Label(id=37)
|
||||||
|
ret = label.is_detached()
|
||||||
|
|
||||||
|
assert ret is True
|
||||||
|
Label.objects.filter.assert_called_with(id=37, unifiedjob_labels__isnull=True, unifiedjobtemplate_labels__isnull=True)
|
||||||
|
mock_query_set.count.assert_called_with()
|
||||||
|
|
||||||
|
def test_is_detached_not(mocker):
|
||||||
|
mock_query_set = mocker.MagicMock()
|
||||||
|
Label.objects.filter = mocker.MagicMock(return_value=mock_query_set)
|
||||||
|
mock_query_set.count.return_value = 0
|
||||||
|
|
||||||
|
label = Label(id=37)
|
||||||
|
ret = label.is_detached()
|
||||||
|
|
||||||
|
assert ret is False
|
||||||
|
Label.objects.filter.assert_called_with(id=37, unifiedjob_labels__isnull=True, unifiedjobtemplate_labels__isnull=True)
|
||||||
|
mock_query_set.count.assert_called_with()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("jt_count,j_count,expected", [
|
||||||
|
(1, 0, True),
|
||||||
|
(0, 1, True),
|
||||||
|
(1, 1, False),
|
||||||
|
])
|
||||||
|
def test_is_candidate_for_detach(mocker, jt_count, j_count, expected):
|
||||||
|
mock_job_qs = mocker.MagicMock()
|
||||||
|
mock_job_qs.count = mocker.MagicMock(return_value=j_count)
|
||||||
|
UnifiedJob.objects = mocker.MagicMock()
|
||||||
|
UnifiedJob.objects.filter = mocker.MagicMock(return_value=mock_job_qs)
|
||||||
|
|
||||||
|
mock_jt_qs = mocker.MagicMock()
|
||||||
|
mock_jt_qs.count = mocker.MagicMock(return_value=jt_count)
|
||||||
|
UnifiedJobTemplate.objects = mocker.MagicMock()
|
||||||
|
UnifiedJobTemplate.objects.filter = mocker.MagicMock(return_value=mock_jt_qs)
|
||||||
|
|
||||||
|
label = Label(id=37)
|
||||||
|
ret = label.is_candidate_for_detach()
|
||||||
|
|
||||||
|
UnifiedJob.objects.filter.assert_called_with(labels__in=[label.id])
|
||||||
|
UnifiedJobTemplate.objects.filter.assert_called_with(labels__in=[label.id])
|
||||||
|
mock_job_qs.count.assert_called_with()
|
||||||
|
mock_jt_qs.count.assert_called_with()
|
||||||
|
|
||||||
|
assert ret is expected
|
||||||
|
|
||||||
|
|||||||
21
awx/main/tests/unit/test_access.py
Normal file
21
awx/main/tests/unit/test_access.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from awx.main.access import (
|
||||||
|
BaseAccess,
|
||||||
|
check_superuser,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_superuser(mocker):
|
||||||
|
user = mocker.MagicMock(spec=User, id=1, is_superuser=True)
|
||||||
|
access = BaseAccess(user)
|
||||||
|
|
||||||
|
can_add = check_superuser(BaseAccess.can_add)
|
||||||
|
assert can_add(access, None) is True
|
||||||
|
|
||||||
|
def test_not_superuser(mocker):
|
||||||
|
user = mocker.MagicMock(spec=User, id=1, is_superuser=False)
|
||||||
|
access = BaseAccess(user)
|
||||||
|
|
||||||
|
can_add = check_superuser(BaseAccess.can_add)
|
||||||
|
assert can_add(access, None) is False
|
||||||
|
|
||||||
17
awx/main/tests/unit/test_signals.py
Normal file
17
awx/main/tests/unit/test_signals.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from awx.main import signals
|
||||||
|
|
||||||
|
class TestCleanupDetachedLabels:
|
||||||
|
def test_cleanup_detached_labels_on_deleted_parent(self, mocker):
|
||||||
|
mock_labels = [mocker.MagicMock(), mocker.MagicMock()]
|
||||||
|
mock_instance = mocker.MagicMock()
|
||||||
|
mock_instance.labels.all = mocker.MagicMock()
|
||||||
|
mock_instance.labels.all.return_value = mock_labels
|
||||||
|
mock_labels[0].is_candidate_for_detach.return_value = True
|
||||||
|
mock_labels[1].is_candidate_for_detach.return_value = False
|
||||||
|
|
||||||
|
signals.cleanup_detached_labels_on_deleted_parent(None, mock_instance)
|
||||||
|
|
||||||
|
mock_labels[0].is_candidate_for_detach.assert_called_with()
|
||||||
|
mock_labels[1].is_candidate_for_detach.assert_called_with()
|
||||||
|
mock_labels[0].delete.assert_called_with()
|
||||||
|
mock_labels[1].delete.assert_not_called()
|
||||||
@@ -70,7 +70,7 @@ import psutil
|
|||||||
# pass
|
# pass
|
||||||
# statsd = NoStatsClient()
|
# statsd = NoStatsClient()
|
||||||
|
|
||||||
CENSOR_FIELD_WHITELIST=[
|
CENSOR_FIELD_WHITELIST = [
|
||||||
'msg',
|
'msg',
|
||||||
'failed',
|
'failed',
|
||||||
'changed',
|
'changed',
|
||||||
@@ -80,7 +80,6 @@ CENSOR_FIELD_WHITELIST=[
|
|||||||
'delta',
|
'delta',
|
||||||
'cmd',
|
'cmd',
|
||||||
'_ansible_no_log',
|
'_ansible_no_log',
|
||||||
'cmd',
|
|
||||||
'rc',
|
'rc',
|
||||||
'failed_when_result',
|
'failed_when_result',
|
||||||
'skipped',
|
'skipped',
|
||||||
@@ -114,6 +113,7 @@ def censor(obj, no_log=False):
|
|||||||
obj['results'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
|
obj['results'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class TokenAuth(requests.auth.AuthBase):
|
class TokenAuth(requests.auth.AuthBase):
|
||||||
|
|
||||||
def __init__(self, token):
|
def __init__(self, token):
|
||||||
@@ -194,31 +194,7 @@ class BaseCallbackModule(object):
|
|||||||
self._init_connection()
|
self._init_connection()
|
||||||
if self.context is None:
|
if self.context is None:
|
||||||
self._start_connection()
|
self._start_connection()
|
||||||
if 'res' in event_data and hasattr(event_data['res'], 'get') \
|
|
||||||
and event_data['res'].get('_ansible_no_log', False):
|
|
||||||
res = event_data['res']
|
|
||||||
if 'stdout' in res and res['stdout']:
|
|
||||||
res['stdout'] = '<censored>'
|
|
||||||
if 'stdout_lines' in res and res['stdout_lines']:
|
|
||||||
res['stdout_lines'] = ['<censored>']
|
|
||||||
if 'stderr' in res and res['stderr']:
|
|
||||||
res['stderr'] = '<censored>'
|
|
||||||
if 'stderr_lines' in res and res['stderr_lines']:
|
|
||||||
res['stderr_lines'] = ['<censored>']
|
|
||||||
if res.get('cmd', None) and re.search(r'\s', res['cmd']):
|
|
||||||
res['cmd'] = re.sub(r'^(([^\s\\]|\\\s)+).*$',
|
|
||||||
r'\1 <censored>',
|
|
||||||
res['cmd'])
|
|
||||||
if 'invocation' in res \
|
|
||||||
and 'module_args' in res['invocation'] \
|
|
||||||
and '_raw_params' in res['invocation']['module_args'] \
|
|
||||||
and re.search(r'\s',
|
|
||||||
res['invocation']['module_args']['_raw_params']):
|
|
||||||
res['invocation']['module_args']['_raw_params'] = \
|
|
||||||
re.sub(r'^(([^\s\\]|\\\s)+).*$',
|
|
||||||
r'\1 <censored>',
|
|
||||||
res['invocation']['module_args']['_raw_params'])
|
|
||||||
msg['event_data']['res'] = res
|
|
||||||
self.socket.send_json(msg)
|
self.socket.send_json(msg)
|
||||||
self.socket.recv()
|
self.socket.recv()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from copy import deepcopy
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
try:
|
try:
|
||||||
from ansible.cache.base import BaseCacheModule
|
from ansible.cache.base import BaseCacheModule
|
||||||
@@ -50,7 +49,7 @@ class CacheModule(BaseCacheModule):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Basic in-memory caching for typical runs
|
# Basic in-memory caching for typical runs
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._cache_prev = {}
|
self._all_keys = {}
|
||||||
|
|
||||||
# This is the local tower zmq connection
|
# This is the local tower zmq connection
|
||||||
self._tower_connection = C.CACHE_PLUGIN_CONNECTION
|
self._tower_connection = C.CACHE_PLUGIN_CONNECTION
|
||||||
@@ -72,12 +71,10 @@ class CacheModule(BaseCacheModule):
|
|||||||
def identify_new_module(self, key, value):
|
def identify_new_module(self, key, value):
|
||||||
# Return the first key found that doesn't exist in the
|
# Return the first key found that doesn't exist in the
|
||||||
# previous set of facts
|
# previous set of facts
|
||||||
if key in self._cache_prev:
|
if key in self._all_keys:
|
||||||
value_old = self._cache_prev[key]
|
for k in value.iterkeys():
|
||||||
for k,v in value.iteritems():
|
if k not in self._all_keys[key] and not k.startswith('ansible_'):
|
||||||
if k not in value_old:
|
return k
|
||||||
if not k.startswith('ansible_'):
|
|
||||||
return k
|
|
||||||
# First time we have seen facts from this host
|
# First time we have seen facts from this host
|
||||||
# it's either ansible facts or a module facts (including module_setup)
|
# it's either ansible facts or a module facts (including module_setup)
|
||||||
elif len(value) == 1:
|
elif len(value) == 1:
|
||||||
@@ -110,7 +107,7 @@ class CacheModule(BaseCacheModule):
|
|||||||
# Assume ansible fact triggered the set if no new module found
|
# Assume ansible fact triggered the set if no new module found
|
||||||
facts = self.filter_ansible_facts(value) if not module else dict({ module : value[module]})
|
facts = self.filter_ansible_facts(value) if not module else dict({ module : value[module]})
|
||||||
self._cache[key] = value
|
self._cache[key] = value
|
||||||
self._cache_prev = deepcopy(self._cache)
|
self._all_keys[key] = value.keys()
|
||||||
packet = {
|
packet = {
|
||||||
'host': key,
|
'host': key,
|
||||||
'inventory_id': os.environ['INVENTORY_ID'],
|
'inventory_id': os.environ['INVENTORY_ID'],
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class ServiceScanService(BaseService):
|
|||||||
initctl_path = self.module.get_bin_path("initctl")
|
initctl_path = self.module.get_bin_path("initctl")
|
||||||
chkconfig_path = self.module.get_bin_path("chkconfig")
|
chkconfig_path = self.module.get_bin_path("chkconfig")
|
||||||
|
|
||||||
# Upstart and sysvinit
|
# sysvinit
|
||||||
if initctl_path is not None and chkconfig_path is None:
|
if service_path is not None and chkconfig_path is None:
|
||||||
rc, stdout, stderr = self.module.run_command("%s --status-all 2>&1 | grep -E \"\\[ (\\+|\\-) \\]\"" % service_path, use_unsafe_shell=True)
|
rc, stdout, stderr = self.module.run_command("%s --status-all 2>&1 | grep -E \"\\[ (\\+|\\-) \\]\"" % service_path, use_unsafe_shell=True)
|
||||||
for line in stdout.split("\n"):
|
for line in stdout.split("\n"):
|
||||||
line_data = line.split()
|
line_data = line.split()
|
||||||
@@ -72,6 +72,9 @@ class ServiceScanService(BaseService):
|
|||||||
else:
|
else:
|
||||||
service_state = "stopped"
|
service_state = "stopped"
|
||||||
services.append({"name": service_name, "state": service_state, "source": "sysv"})
|
services.append({"name": service_name, "state": service_state, "source": "sysv"})
|
||||||
|
|
||||||
|
# Upstart
|
||||||
|
if initctl_path is not None and chkconfig_path is None:
|
||||||
p = re.compile('^\s?(?P<name>.*)\s(?P<goal>\w+)\/(?P<state>\w+)(\,\sprocess\s(?P<pid>[0-9]+))?\s*$')
|
p = re.compile('^\s?(?P<name>.*)\s(?P<goal>\w+)\/(?P<state>\w+)(\,\sprocess\s(?P<pid>[0-9]+))?\s*$')
|
||||||
rc, stdout, stderr = self.module.run_command("%s list" % initctl_path)
|
rc, stdout, stderr = self.module.run_command("%s list" % initctl_path)
|
||||||
real_stdout = stdout.replace("\r","")
|
real_stdout = stdout.replace("\r","")
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ body .navbar .navbar-brand:hover {
|
|||||||
}
|
}
|
||||||
body .navbar .navbar-brand img {
|
body .navbar .navbar-brand img {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 150px;
|
width: 93px;
|
||||||
max-height: 50px;
|
height: 30px;
|
||||||
|
margin-right:14px;
|
||||||
}
|
}
|
||||||
body .navbar .navbar-brand > span {
|
body .navbar .navbar-brand > span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="{% url 'api:api_root_view' %}">
|
<a class="navbar-brand" href="{% url 'api:api_root_view' %}">
|
||||||
<img class="logo" src="{% static 'assets/main_menu_logo.png' %}">
|
<img class="logo" src="{% static 'assets/tower-logo-header.svg' %}">
|
||||||
<span>{% trans 'REST API' %}</span>
|
<span>{% trans 'REST API' %}</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-title" href="{{ request.get_full_path }}">
|
<a class="navbar-title" href="{{ request.get_full_path }}">
|
||||||
@@ -49,10 +49,7 @@
|
|||||||
<div id="footer">
|
<div id="footer">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 footer-logo">
|
<div class="col-sm-6">
|
||||||
<a href="http://www.ansible.com" target="_blank">
|
|
||||||
<img alt="Red Hat, Inc. | Ansible, Inc." src="{% static 'assets/footer-logo.png' %}" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 footer-copyright">
|
<div class="col-sm-6 footer-copyright">
|
||||||
Copyright © 2016 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
Copyright © 2016 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
||||||
|
|||||||
1
awx/ui/client/assets/tower-logo-header.svg
Normal file
1
awx/ui/client/assets/tower-logo-header.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92.42 30"><defs><style>.cls-1{fill:#707070;}.cls-2{fill:#fff;}</style></defs><title>tower-logo-header2</title><path class="cls-1" d="M40.6,9.8V20.9H39.2V9.8H35.6V8.5h8.6V9.7l-3.6.1h0Z"/><path class="cls-1" d="M50.8,21.1c-3.1,0-5.1-2.6-5.1-6.4s2.1-6.4,5.2-6.4,5.2,2.6,5.2,6.4S53.9,21.1,50.8,21.1Zm0-11.5c-2.1,0-3.7,2-3.7,5.1s1.6,5.2,3.8,5.2,3.7-2,3.7-5.1S53,9.6,50.8,9.6h0Z"/><path class="cls-1" d="M68.4,20.9H66.8L64.9,13c-0.2-.8-0.5-2.1-0.6-2.8-0.1.8-.4,1.9-0.6,2.8l-1.9,7.9H60.3L57.7,8.5h1.4l1.4,7.9c0.1,0.8.4,2.2,0.5,2.8,0.1-.6.4-1.9,0.6-2.7l2-8H65l2,8c0.2,0.8.5,2.1,0.6,2.7,0.1-.6.3-1.9,0.5-2.8l1.4-7.9h1.3Z"/><path class="cls-1" d="M73.4,20.9V8.5h7.5V9.7H74.8v3.9h3.5v1.3H74.8v4.7h6.4v1.2C81.2,20.9,73.4,20.9,73.4,20.9Z"/><path class="cls-1" d="M89.6,15.5l2.7,5.4H90.7L88,15.6H85.1v5.3H83.7V8.5h4.9c2.2,0,3.8,1.1,3.8,3.5A3.09,3.09,0,0,1,89.6,15.5Zm-1-5.7H85.2v4.6h3.3c1.8,0,2.7-.8,2.7-2.3s-0.9-2.3-2.6-2.3h0Z"/><circle class="cls-1" cx="15" cy="15" r="15"/><path class="cls-2" d="M22.1,21l-6-14.4a1,1,0,0,0-1.8,0L7.7,22.3H10l2.6-6.5,7.7,6.3a2,2,0,0,0,.8.4,1,1,0,0,0,1.1-1V21.4A0.6,0.6,0,0,0,22.1,21ZM15.2,9.2l3.9,9.6-5.9-4.6Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
1
awx/ui/client/assets/tower-logo-login.svg
Normal file
1
awx/ui/client/assets/tower-logo-login.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.6 KiB |
@@ -896,10 +896,6 @@ select.field-mini-height {
|
|||||||
font-size: 10.5px;
|
font-size: 10.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ask-checkbox {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-padding {
|
.no-padding {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -15,12 +15,25 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Form-textArea{
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form-header--fields{
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form-header-field{
|
||||||
|
margin-left: 10px;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.Form-header{
|
.Form-header{
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Form-title{
|
.Form-title{
|
||||||
flex: 1 0 auto;
|
flex: 0 1 auto;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: @list-header-txt;
|
color: @list-header-txt;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -144,7 +157,7 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
width: 33%;
|
width: 33%;
|
||||||
padding-right: 50px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Form-subForm {
|
.Form-subForm {
|
||||||
@@ -455,6 +468,12 @@ input[type='radio']:checked:before {
|
|||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Form-subCheckbox {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: small;
|
||||||
|
color: @default-interface-txt;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 650px) {
|
@media only screen and (max-width: 650px) {
|
||||||
.Form-formGroup {
|
.Form-formGroup {
|
||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ table, tbody {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.List-titleBadge--selected {
|
||||||
|
background-color: @default-link;
|
||||||
|
}
|
||||||
|
|
||||||
.List-titleText {
|
.List-titleText {
|
||||||
color: @list-title-txt;
|
color: @list-title-txt;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -23,10 +23,8 @@
|
|||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
}
|
}
|
||||||
.About-brand--redhat{
|
.About-brand--redhat{
|
||||||
max-width: 420px;
|
float: left;
|
||||||
margin: 0 auto;
|
width: 112px;
|
||||||
margin-top: -50px;
|
|
||||||
margin-bottom: -30px;
|
|
||||||
}
|
}
|
||||||
.About-brand--ansible{
|
.About-brand--ansible{
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
@@ -36,7 +34,14 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
right: 15px;
|
right: 15px;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.About p{
|
.About p{
|
||||||
color: @default-interface-txt;
|
color: @default-interface-txt;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
.About-modal--footer {
|
||||||
|
clear: both;
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ export default
|
|||||||
var processVersion = function(version){
|
var processVersion = function(version){
|
||||||
// prettify version & calculate padding
|
// prettify version & calculate padding
|
||||||
// e,g 3.0.0-0.git201602191743/ -> 3.0.0
|
// e,g 3.0.0-0.git201602191743/ -> 3.0.0
|
||||||
var split = version.split('-')[0]
|
var split = version.split('-')[0];
|
||||||
var spaces = Math.floor((16-split.length)/2),
|
var spaces = Math.floor((16-split.length)/2),
|
||||||
paddedStr = "";
|
paddedStr = "";
|
||||||
for(var i=0; i<=spaces; i++){
|
for(var i=0; i<=spaces; i++){
|
||||||
@@ -13,7 +13,7 @@ export default
|
|||||||
for(var j = paddedStr.length; j<16; j++){
|
for(var j = paddedStr.length; j<16; j++){
|
||||||
paddedStr = paddedStr + " ";
|
paddedStr = paddedStr + " ";
|
||||||
}
|
}
|
||||||
return paddedStr
|
return paddedStr;
|
||||||
};
|
};
|
||||||
var init = function(){
|
var init = function(){
|
||||||
CheckLicense.get()
|
CheckLicense.get()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<img class="About-brand--ansible img-responsive" src="/static/assets/ansible_tower_logo_minimalc.png" />
|
|
||||||
<button data-dismiss="modal" type="button" class="close About-close">
|
<button data-dismiss="modal" type="button" class="close About-close">
|
||||||
<span class="fa fa-times-circle"></span>
|
<span class="fa fa-times-circle"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -22,11 +21,11 @@
|
|||||||
|| ||
|
|| ||
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
<img class="About-brand--redhat img-responsive" src="/static/assets/redhat_ansible_lockup.png" />
|
<div class="About-modal--footer">
|
||||||
<p class="text-center">Copyright 2016. All rights reserved.<br>
|
<img class="About-brand--redhat img-responsive" src="/static/assets/tower-logo-login.svg" />
|
||||||
Ansible and Ansible Tower are registered trademarks of <a href="http://www.redhat.com/" target="_blank">Red Hat, Inc</a>.<br>
|
<p class="text-right">Copyright © 2016 Red Hat, Inc. <br>
|
||||||
Visit <a href="http://www.ansible.com/" target="_blank">Ansible.com</a> for more information.<br>
|
Visit <a href="http://www.ansible.com/" target="_blank">Ansible.com</a> for more information.<br>
|
||||||
{{subscription}}</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default ['templateUrl', function(templateUrl) {
|
|||||||
|
|
||||||
$scope.changeStreamTarget = function(){
|
$scope.changeStreamTarget = function(){
|
||||||
|
|
||||||
if($scope.streamTarget && $scope.streamTarget == 'dashboard') {
|
if($scope.streamTarget && $scope.streamTarget === 'dashboard') {
|
||||||
// Just navigate to the base activity stream
|
// Just navigate to the base activity stream
|
||||||
$state.go('activityStream', {}, {inherit: false});
|
$state.go('activityStream', {}, {inherit: false});
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export default ['templateUrl', function(templateUrl) {
|
|||||||
$state.go('activityStream', {target: $scope.streamTarget}, {inherit: false});
|
$state.go('activityStream', {target: $scope.streamTarget}, {inherit: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
};
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
|
|||||||
var privateFn = {};
|
var privateFn = {};
|
||||||
this.privateFn = privateFn;
|
this.privateFn = privateFn;
|
||||||
|
|
||||||
|
var id = $stateParams.inventory_id,
|
||||||
|
urls = privateFn.setAvailableUrls(),
|
||||||
|
hostPattern = $rootScope.hostPatterns || "all";
|
||||||
|
|
||||||
// note: put any urls that the controller will use in here!!!!
|
// note: put any urls that the controller will use in here!!!!
|
||||||
privateFn.setAvailableUrls = function() {
|
privateFn.setAvailableUrls = function() {
|
||||||
return {
|
return {
|
||||||
@@ -31,10 +35,6 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var id = $stateParams.inventory_id,
|
|
||||||
urls = privateFn.setAvailableUrls(),
|
|
||||||
hostPattern = $rootScope.hostPatterns || "all";
|
|
||||||
|
|
||||||
// set the default options for the selects of the adhoc form
|
// set the default options for the selects of the adhoc form
|
||||||
privateFn.setFieldDefaults = function(verbosity_options, forks_default) {
|
privateFn.setFieldDefaults = function(verbosity_options, forks_default) {
|
||||||
var verbosity;
|
var verbosity;
|
||||||
@@ -164,7 +164,7 @@ function adhocController($q, $scope, $rootScope, $location, $stateParams,
|
|||||||
|
|
||||||
$scope.formCancel = function(){
|
$scope.formCancel = function(){
|
||||||
$state.go('inventoryManage');
|
$state.go('inventoryManage');
|
||||||
}
|
};
|
||||||
|
|
||||||
// remove all data input into the form and reset the form back to defaults
|
// remove all data input into the form and reset the form back to defaults
|
||||||
$scope.formReset = function () {
|
$scope.formReset = function () {
|
||||||
|
|||||||
@@ -10,10 +10,5 @@ export default {
|
|||||||
route: '/adhoc',
|
route: '/adhoc',
|
||||||
name: 'inventoryManage.adhoc',
|
name: 'inventoryManage.adhoc',
|
||||||
templateUrl: templateUrl('adhoc/adhoc'),
|
templateUrl: templateUrl('adhoc/adhoc'),
|
||||||
controller: 'adhocController',
|
controller: 'adhocController'
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
*************************************************/
|
*************************************************/
|
||||||
|
|
||||||
var urlPrefix;
|
var urlPrefix;
|
||||||
|
var $basePath;
|
||||||
|
|
||||||
if ($basePath) {
|
if ($basePath) {
|
||||||
urlPrefix = $basePath;
|
urlPrefix = $basePath;
|
||||||
} else {
|
} else {
|
||||||
// required to make tests work
|
// required to make tests work
|
||||||
var $basePath = '/static/';
|
$basePath = '/static/';
|
||||||
urlPrefix = $basePath;
|
urlPrefix = $basePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ import './lists';
|
|||||||
import './widgets';
|
import './widgets';
|
||||||
import './help';
|
import './help';
|
||||||
import './filters';
|
import './filters';
|
||||||
import {Home, HomeGroups, HomeHosts} from './controllers/Home';
|
import {Home, HomeGroups} from './controllers/Home';
|
||||||
import {SocketsController} from './controllers/Sockets';
|
import {SocketsController} from './controllers/Sockets';
|
||||||
import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Credentials';
|
import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Credentials';
|
||||||
import {JobsListController} from './controllers/Jobs';
|
import {JobsListController} from './controllers/Jobs';
|
||||||
@@ -49,20 +50,17 @@ import adhoc from './adhoc/main';
|
|||||||
import login from './login/main';
|
import login from './login/main';
|
||||||
import activityStream from './activity-stream/main';
|
import activityStream from './activity-stream/main';
|
||||||
import standardOut from './standard-out/main';
|
import standardOut from './standard-out/main';
|
||||||
import lookUpHelper from './lookup/main';
|
|
||||||
import JobTemplates from './job-templates/main';
|
import JobTemplates from './job-templates/main';
|
||||||
import search from './search/main';
|
import search from './search/main';
|
||||||
import {ScheduleEditController} from './controllers/Schedules';
|
|
||||||
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
|
import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
|
||||||
import OrganizationsList from './organizations/list/organizations-list.controller';
|
import OrganizationsList from './organizations/list/organizations-list.controller';
|
||||||
import OrganizationsAdd from './organizations/add/organizations-add.controller';
|
import OrganizationsAdd from './organizations/add/organizations-add.controller';
|
||||||
import OrganizationsEdit from './organizations/edit/organizations-edit.controller';
|
|
||||||
import {InventoriesAdd, InventoriesEdit, InventoriesList, InventoriesManage} from './inventories/main';
|
|
||||||
import {AdminsList} from './controllers/Admins';
|
import {AdminsList} from './controllers/Admins';
|
||||||
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
|
||||||
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
|
||||||
|
|
||||||
import RestServices from './rest/main';
|
import RestServices from './rest/main';
|
||||||
|
import './lookup/main';
|
||||||
import './shared/api-loader';
|
import './shared/api-loader';
|
||||||
import './shared/form-generator';
|
import './shared/form-generator';
|
||||||
import './shared/Modal';
|
import './shared/Modal';
|
||||||
@@ -175,7 +173,6 @@ var tower = angular.module('Tower', [
|
|||||||
'CredentialsHelper',
|
'CredentialsHelper',
|
||||||
'StreamListDefinition',
|
'StreamListDefinition',
|
||||||
'HomeGroupListDefinition',
|
'HomeGroupListDefinition',
|
||||||
'HomeHostListDefinition',
|
|
||||||
'ActivityDetailDefinition',
|
'ActivityDetailDefinition',
|
||||||
'VariablesHelper',
|
'VariablesHelper',
|
||||||
'SchedulesListDefinition',
|
'SchedulesListDefinition',
|
||||||
@@ -198,7 +195,7 @@ var tower = angular.module('Tower', [
|
|||||||
'pendolytics',
|
'pendolytics',
|
||||||
'ui.router',
|
'ui.router',
|
||||||
'ncy-angular-breadcrumb',
|
'ncy-angular-breadcrumb',
|
||||||
'scheduler',
|
scheduler.name,
|
||||||
'ApiModelHelper',
|
'ApiModelHelper',
|
||||||
'ActivityStreamHelper',
|
'ActivityStreamHelper',
|
||||||
'dndLists'
|
'dndLists'
|
||||||
@@ -261,30 +258,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: 'dashboard',
|
parent: 'dashboard',
|
||||||
label: "GROUPS"
|
label: "GROUPS"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('dashboardHosts', {
|
|
||||||
url: '/home/hosts?has_active_failures&name&id',
|
|
||||||
templateUrl: urlPrefix + 'partials/subhome.html',
|
|
||||||
controller: HomeHosts,
|
|
||||||
data: {
|
|
||||||
activityStream: true,
|
|
||||||
activityStreamTarget: 'host'
|
|
||||||
},
|
|
||||||
ncyBreadcrumb: {
|
|
||||||
parent: 'dashboard',
|
|
||||||
label: "HOSTS"
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -294,11 +267,6 @@ var tower = angular.module('Tower', [
|
|||||||
controller: JobsListController,
|
controller: JobsListController,
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
label: "JOBS"
|
label: "JOBS"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -312,11 +280,6 @@ var tower = angular.module('Tower', [
|
|||||||
},
|
},
|
||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
label: "PROJECTS"
|
label: "PROJECTS"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -327,11 +290,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "projects",
|
parent: "projects",
|
||||||
label: "CREATE PROJECT"
|
label: "CREATE PROJECT"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -341,79 +299,19 @@ var tower = angular.module('Tower', [
|
|||||||
controller: ProjectsEdit,
|
controller: ProjectsEdit,
|
||||||
data: {
|
data: {
|
||||||
activityStreamId: 'id'
|
activityStreamId: 'id'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
state('projectOrganizations', {
|
state('projectOrganizations', {
|
||||||
url: '/projects/:project_id/organizations',
|
url: '/projects/:project_id/organizations',
|
||||||
templateUrl: urlPrefix + 'partials/projects.html',
|
templateUrl: urlPrefix + 'partials/projects.html',
|
||||||
controller: OrganizationsList,
|
controller: OrganizationsList
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('projectOrganizationAdd', {
|
state('projectOrganizationAdd', {
|
||||||
url: '/projects/:project_id/organizations/add',
|
url: '/projects/:project_id/organizations/add',
|
||||||
templateUrl: urlPrefix + 'partials/projects.html',
|
templateUrl: urlPrefix + 'partials/projects.html',
|
||||||
controller: OrganizationsAdd,
|
controller: OrganizationsAdd
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('organizationAdmins', {
|
|
||||||
url: '/organizations/:organization_id/admins',
|
|
||||||
templateUrl: urlPrefix + 'partials/organizations.html',
|
|
||||||
controller: AdminsList,
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('organizationUsers', {
|
|
||||||
url:'/organizations/:organization_id/users',
|
|
||||||
templateUrl: urlPrefix + 'partials/users.html',
|
|
||||||
controller: UsersList,
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('organizationUserAdd', {
|
|
||||||
url: '/organizations/:organization_id/users/add',
|
|
||||||
templateUrl: urlPrefix + 'partials/users.html',
|
|
||||||
controller: UsersAdd,
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('organizationUserEdit', {
|
|
||||||
url: '/organizations/:organization_id/users/:user_id',
|
|
||||||
templateUrl: urlPrefix + 'partials/users.html',
|
|
||||||
controller: UsersEdit,
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
|
||||||
|
|
||||||
state('teams', {
|
state('teams', {
|
||||||
url: '/teams',
|
url: '/teams',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
@@ -425,11 +323,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: 'setup',
|
parent: 'setup',
|
||||||
label: 'TEAMS'
|
label: 'TEAMS'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -440,11 +333,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "teams",
|
parent: "teams",
|
||||||
label: "CREATE TEAM"
|
label: "CREATE TEAM"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -454,100 +342,55 @@ var tower = angular.module('Tower', [
|
|||||||
controller: TeamsEdit,
|
controller: TeamsEdit,
|
||||||
data: {
|
data: {
|
||||||
activityStreamId: 'team_id'
|
activityStreamId: 'team_id'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamUsers', {
|
state('teamUsers', {
|
||||||
url: '/teams/:team_id/users',
|
url: '/teams/:team_id/users',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: UsersList,
|
controller: UsersList
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamUserEdit', {
|
state('teamUserEdit', {
|
||||||
url: '/teams/:team_id/users/:user_id',
|
url: '/teams/:team_id/users/:user_id',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: UsersEdit,
|
controller: UsersEdit
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamProjects', {
|
state('teamProjects', {
|
||||||
url: '/teams/:team_id/projects',
|
url: '/teams/:team_id/projects',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: ProjectsList,
|
controller: ProjectsList
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamProjectAdd', {
|
state('teamProjectAdd', {
|
||||||
url: '/teams/:team_id/projects/add',
|
url: '/teams/:team_id/projects/add',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: ProjectsAdd,
|
controller: ProjectsAdd
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamProjectEdit', {
|
state('teamProjectEdit', {
|
||||||
url: '/teams/:team_id/projects/:project_id',
|
url: '/teams/:team_id/projects/:project_id',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: ProjectsEdit,
|
controller: ProjectsEdit
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamCredentials', {
|
state('teamCredentials', {
|
||||||
url: '/teams/:team_id/credentials',
|
url: '/teams/:team_id/credentials',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: CredentialsList,
|
controller: CredentialsList
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamCredentialAdd', {
|
state('teamCredentialAdd', {
|
||||||
url: '/teams/:team_id/credentials/add',
|
url: '/teams/:team_id/credentials/add',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: CredentialsAdd,
|
controller: CredentialsAdd
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamCredentialEdit', {
|
state('teamCredentialEdit', {
|
||||||
url: '/teams/:team_id/credentials/:credential_id',
|
url: '/teams/:team_id/credentials/:credential_id',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: CredentialsEdit,
|
controller: CredentialsEdit
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('credentials', {
|
state('credentials', {
|
||||||
@@ -561,11 +404,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: 'setup',
|
parent: 'setup',
|
||||||
label: 'CREDENTIALS'
|
label: 'CREDENTIALS'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -576,11 +414,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "credentials",
|
parent: "credentials",
|
||||||
label: "CREATE CREDENTIAL"
|
label: "CREATE CREDENTIAL"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -590,11 +423,6 @@ var tower = angular.module('Tower', [
|
|||||||
controller: CredentialsEdit,
|
controller: CredentialsEdit,
|
||||||
data: {
|
data: {
|
||||||
activityStreamId: 'credential_id'
|
activityStreamId: 'credential_id'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -609,11 +437,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: 'setup',
|
parent: 'setup',
|
||||||
label: 'USERS'
|
label: 'USERS'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -624,11 +447,6 @@ var tower = angular.module('Tower', [
|
|||||||
ncyBreadcrumb: {
|
ncyBreadcrumb: {
|
||||||
parent: "users",
|
parent: "users",
|
||||||
label: "CREATE USER"
|
label: "CREATE USER"
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
@@ -638,45 +456,25 @@ var tower = angular.module('Tower', [
|
|||||||
controller: UsersEdit,
|
controller: UsersEdit,
|
||||||
data: {
|
data: {
|
||||||
activityStreamId: 'user_id'
|
activityStreamId: 'user_id'
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('userCredentials', {
|
state('userCredentials', {
|
||||||
url: '/users/:user_id/credentials',
|
url: '/users/:user_id/credentials',
|
||||||
templateUrl: urlPrefix + 'partials/users.html',
|
templateUrl: urlPrefix + 'partials/users.html',
|
||||||
controller: CredentialsList,
|
controller: CredentialsList
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('userCredentialAdd', {
|
state('userCredentialAdd', {
|
||||||
url: '/users/:user_id/credentials/add',
|
url: '/users/:user_id/credentials/add',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: CredentialsAdd,
|
controller: CredentialsAdd
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('teamUserCredentialEdit', {
|
state('teamUserCredentialEdit', {
|
||||||
url: '/teams/:user_id/credentials/:credential_id',
|
url: '/teams/:user_id/credentials/:credential_id',
|
||||||
templateUrl: urlPrefix + 'partials/teams.html',
|
templateUrl: urlPrefix + 'partials/teams.html',
|
||||||
controller: CredentialsEdit,
|
controller: CredentialsEdit
|
||||||
resolve: {
|
|
||||||
features: ['FeaturesService', function(FeaturesService) {
|
|
||||||
return FeaturesService.get();
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
state('sockets', {
|
state('sockets', {
|
||||||
@@ -710,7 +508,7 @@ var tower = angular.module('Tower', [
|
|||||||
var sock;
|
var sock;
|
||||||
$rootScope.addPermission = function (scope) {
|
$rootScope.addPermission = function (scope) {
|
||||||
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
|
$compile("<add-permissions class='AddPermissions'></add-permissions>")(scope);
|
||||||
}
|
};
|
||||||
|
|
||||||
$rootScope.deletePermission = function (user, role, userName,
|
$rootScope.deletePermission = function (user, role, userName,
|
||||||
roleName, resourceName) {
|
roleName, resourceName) {
|
||||||
@@ -738,6 +536,66 @@ var tower = angular.module('Tower', [
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$rootScope.deletePermissionFromUser = function (userId, userName, roleName, roleType, url) {
|
||||||
|
var action = function () {
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
Wait('start');
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.post({"disassociate": true, "id": userId})
|
||||||
|
.success(function () {
|
||||||
|
Wait('stop');
|
||||||
|
$rootScope.$broadcast("refreshList", "permission");
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Could not disassociate user from role. Call to ' + url + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Prompt({
|
||||||
|
hdr: `Remove role`,
|
||||||
|
body: `
|
||||||
|
<div class="Prompt-bodyQuery">
|
||||||
|
Confirm the removal of the ${roleType}
|
||||||
|
<span class="Prompt-emphasis"> ${roleName} </span>
|
||||||
|
role associated with ${userName}.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
action: action,
|
||||||
|
actionText: 'REMOVE'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$rootScope.deletePermissionFromTeam = function (teamId, teamName, roleName, roleType, url) {
|
||||||
|
var action = function () {
|
||||||
|
$('#prompt-modal').modal('hide');
|
||||||
|
Wait('start');
|
||||||
|
Rest.setUrl(url);
|
||||||
|
Rest.post({"disassociate": true, "id": teamId})
|
||||||
|
.success(function () {
|
||||||
|
Wait('stop');
|
||||||
|
$rootScope.$broadcast("refreshList", "role");
|
||||||
|
})
|
||||||
|
.error(function (data, status) {
|
||||||
|
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Could not disassociate team from role. Call to ' + url + ' failed. DELETE returned status: ' + status });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Prompt({
|
||||||
|
hdr: `Remove role`,
|
||||||
|
body: `
|
||||||
|
<div class="Prompt-bodyQuery">
|
||||||
|
Confirm the removal of the ${roleType}
|
||||||
|
<span class="Prompt-emphasis"> ${roleName} </span>
|
||||||
|
role associated with the ${teamName} team.
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
action: action,
|
||||||
|
actionText: 'REMOVE'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
function activateTab() {
|
function activateTab() {
|
||||||
// Make the correct tab active
|
// Make the correct tab active
|
||||||
var base = $location.path().replace(/^\//, '').split('/')[0];
|
var base = $location.path().replace(/^\//, '').split('/')[0];
|
||||||
@@ -774,16 +632,17 @@ var tower = angular.module('Tower', [
|
|||||||
$rootScope.removeConfigReady();
|
$rootScope.removeConfigReady();
|
||||||
}
|
}
|
||||||
$rootScope.removeConfigReady = $rootScope.$on('ConfigReady', function() {
|
$rootScope.removeConfigReady = $rootScope.$on('ConfigReady', function() {
|
||||||
|
var list, id;
|
||||||
// initially set row edit indicator for crud pages
|
// initially set row edit indicator for crud pages
|
||||||
if ($location.$$path && $location.$$path.split("/")[3] && $location.$$path.split("/")[3] === "schedules") {
|
if ($location.$$path && $location.$$path.split("/")[3] && $location.$$path.split("/")[3] === "schedules") {
|
||||||
var list = $location.$$path.split("/")[3];
|
list = $location.$$path.split("/")[3];
|
||||||
var id = $location.$$path.split("/")[4];
|
id = $location.$$path.split("/")[4];
|
||||||
$rootScope.listBeingEdited = list;
|
$rootScope.listBeingEdited = list;
|
||||||
$rootScope.rowBeingEdited = id;
|
$rootScope.rowBeingEdited = id;
|
||||||
$rootScope.initialIndicatorLoad = true;
|
$rootScope.initialIndicatorLoad = true;
|
||||||
} else if ($location.$$path.split("/")[2]) {
|
} else if ($location.$$path.split("/")[2]) {
|
||||||
var list = $location.$$path.split("/")[1];
|
list = $location.$$path.split("/")[1];
|
||||||
var id = $location.$$path.split("/")[2];
|
id = $location.$$path.split("/")[2];
|
||||||
$rootScope.listBeingEdited = list;
|
$rootScope.listBeingEdited = list;
|
||||||
$rootScope.rowBeingEdited = id;
|
$rootScope.rowBeingEdited = id;
|
||||||
}
|
}
|
||||||
@@ -871,6 +730,9 @@ var tower = angular.module('Tower', [
|
|||||||
|
|
||||||
|
|
||||||
$rootScope.$on("$stateChangeStart", function (event, next, nextParams, prev) {
|
$rootScope.$on("$stateChangeStart", function (event, next, nextParams, prev) {
|
||||||
|
if (next.name !== 'signOut'){
|
||||||
|
CheckLicense.notify();
|
||||||
|
}
|
||||||
$rootScope.$broadcast("closePermissionsModal");
|
$rootScope.$broadcast("closePermissionsModal");
|
||||||
// this line removes the query params attached to a route
|
// this line removes the query params attached to a route
|
||||||
if(prev && prev.$$route &&
|
if(prev && prev.$$route &&
|
||||||
@@ -915,15 +777,16 @@ var tower = angular.module('Tower', [
|
|||||||
activateTab();
|
activateTab();
|
||||||
});
|
});
|
||||||
|
|
||||||
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
|
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState) {
|
||||||
// catch license expiration notifications immediately after user logs in, redirect
|
// catch license expiration notifications immediately after user logs in, redirect
|
||||||
if (fromState.name == 'signIn'){
|
if (fromState.name === 'signIn'){
|
||||||
CheckLicense.notify();
|
CheckLicense.notify();
|
||||||
}
|
}
|
||||||
|
var list, id;
|
||||||
// broadcast event change if editing crud object
|
// broadcast event change if editing crud object
|
||||||
if ($location.$$path && $location.$$path.split("/")[3] && $location.$$path.split("/")[3] === "schedules") {
|
if ($location.$$path && $location.$$path.split("/")[3] && $location.$$path.split("/")[3] === "schedules") {
|
||||||
var list = $location.$$path.split("/")[3];
|
list = $location.$$path.split("/")[3];
|
||||||
var id = $location.$$path.split("/")[4];
|
id = $location.$$path.split("/")[4];
|
||||||
|
|
||||||
if (!$rootScope.initialIndicatorLoad) {
|
if (!$rootScope.initialIndicatorLoad) {
|
||||||
delete $rootScope.listBeingEdited;
|
delete $rootScope.listBeingEdited;
|
||||||
@@ -934,8 +797,8 @@ var tower = angular.module('Tower', [
|
|||||||
|
|
||||||
$rootScope.$broadcast("EditIndicatorChange", list, id);
|
$rootScope.$broadcast("EditIndicatorChange", list, id);
|
||||||
} else if ($location.$$path.split("/")[2]) {
|
} else if ($location.$$path.split("/")[2]) {
|
||||||
var list = $location.$$path.split("/")[1];
|
list = $location.$$path.split("/")[1];
|
||||||
var id = $location.$$path.split("/")[2];
|
id = $location.$$path.split("/")[2];
|
||||||
|
|
||||||
delete $rootScope.listBeingEdited;
|
delete $rootScope.listBeingEdited;
|
||||||
delete $rootScope.rowBeingEdited;
|
delete $rootScope.rowBeingEdited;
|
||||||
@@ -962,6 +825,7 @@ var tower = angular.module('Tower', [
|
|||||||
$rootScope.sessionTimer = timer;
|
$rootScope.sessionTimer = timer;
|
||||||
$rootScope.$emit('OpenSocket');
|
$rootScope.$emit('OpenSocket');
|
||||||
pendoService.issuePendoIdentity();
|
pendoService.issuePendoIdentity();
|
||||||
|
CheckLicense.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export default
|
|||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
templateUrl: templateUrl('bread-crumb/bread-crumb'),
|
templateUrl: templateUrl('bread-crumb/bread-crumb'),
|
||||||
link: function(scope, element, attrs) {
|
link: function(scope) {
|
||||||
|
|
||||||
var streamConfig = {};
|
var streamConfig = {};
|
||||||
|
|
||||||
@@ -15,15 +15,15 @@ export default
|
|||||||
|
|
||||||
if(streamConfig && streamConfig.activityStream) {
|
if(streamConfig && streamConfig.activityStream) {
|
||||||
if(streamConfig.activityStreamTarget) {
|
if(streamConfig.activityStreamTarget) {
|
||||||
stateGoParams['target'] = streamConfig.activityStreamTarget;
|
stateGoParams.target = streamConfig.activityStreamTarget;
|
||||||
}
|
}
|
||||||
if(streamConfig.activityStreamId) {
|
if(streamConfig.activityStreamId) {
|
||||||
stateGoParams['id'] = $state.params[streamConfig.activityStreamId];
|
stateGoParams.id = $state.params[streamConfig.activityStreamId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$state.go('activityStream', stateGoParams);
|
$state.go('activityStream', stateGoParams);
|
||||||
}
|
};
|
||||||
|
|
||||||
scope.$on("$stateChangeSuccess", function updateActivityStreamButton(event, toState) {
|
scope.$on("$stateChangeSuccess", function updateActivityStreamButton(event, toState) {
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export default
|
|||||||
// attached to the $rootScope.
|
// attached to the $rootScope.
|
||||||
|
|
||||||
FeaturesService.get()
|
FeaturesService.get()
|
||||||
.then(function(features) {
|
.then(function() {
|
||||||
if(FeaturesService.featureEnabled('activity_streams')) {
|
if(FeaturesService.featureEnabled('activity_streams')) {
|
||||||
scope.showActivityStreamButton = true;
|
scope.showActivityStreamButton = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div id="bread_crumb" class="BreadCrumb" ng-class="{'is-loggedOut' : !$root.current_user.username}">
|
<div id="bread_crumb" class="BreadCrumb" ng-class="{'is-loggedOut' : !$root.current_user.username}">
|
||||||
<div ncy-breadcrumb></div>
|
<div ng-if="!licenseMissing" ncy-breadcrumb></div>
|
||||||
<div class="BreadCrumb-menuLink"
|
<div class="BreadCrumb-menuLink"
|
||||||
id="bread_crumb_activity_stream"
|
id="bread_crumb_activity_stream"
|
||||||
aw-tool-tip="View Activity Stream"
|
aw-tool-tip="View Activity Stream"
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
data-container="body"
|
data-container="body"
|
||||||
ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}"
|
ng-class="{'BreadCrumb-menuLinkActive' : activityStreamActive}"
|
||||||
ng-if="showActivityStreamButton"
|
ng-if="showActivityStreamButton"
|
||||||
|
ng-hide= "licenseMissing"
|
||||||
ng-click="openActivityStream()">
|
ng-click="openActivityStream()">
|
||||||
<i class="BreadCrumb-menuLinkImage icon-activity-stream"
|
<i class="BreadCrumb-menuLinkImage icon-activity-stream"
|
||||||
alt="Activity Stream">
|
alt="Activity Stream">
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
data-placement="left"
|
data-placement="left"
|
||||||
data-trigger="hover"
|
data-trigger="hover"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
|
ng-hide="licenseMissing"
|
||||||
ng-if="!showActivityStreamButton">
|
ng-if="!showActivityStreamButton">
|
||||||
<i class="BreadCrumb-menuLinkImage fa fa-tachometer"
|
<i class="BreadCrumb-menuLinkImage fa fa-tachometer"
|
||||||
alt="Dashboard">
|
alt="Dashboard">
|
||||||
|
|||||||
@@ -519,8 +519,8 @@ export function CredentialsEdit($scope, $rootScope, $compile, $location, $log,
|
|||||||
$scope.project = data.project;
|
$scope.project = data.project;
|
||||||
break;
|
break;
|
||||||
case 'azure':
|
case 'azure':
|
||||||
$scope.subscription_id = data.username;
|
$scope.subscription = data.username;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
$scope.credential_obj = data;
|
$scope.credential_obj = data;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*************************************************
|
/*************************************************
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
*
|
*
|
||||||
* All Rights Reserved
|
* All Rights Reserved
|
||||||
*************************************************/
|
*************************************************/
|
||||||
@@ -155,12 +155,10 @@ export function HomeGroups($rootScope, $log, $scope, $filter, $compile, $locatio
|
|||||||
|
|
||||||
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
|
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
|
||||||
//scope.
|
//scope.
|
||||||
|
|
||||||
var generator = GenerateList,
|
var generator = GenerateList,
|
||||||
list = HomeGroupList,
|
list = HomeGroupList,
|
||||||
defaultUrl = GetBasePath('groups'),
|
defaultUrl = GetBasePath('groups'),
|
||||||
scope = $scope,
|
scope = $scope,
|
||||||
modal_scope = $scope.$new(),
|
|
||||||
opt, PreviousSearchParams;
|
opt, PreviousSearchParams;
|
||||||
|
|
||||||
generator.inject(list, { mode: 'edit', scope: scope });
|
generator.inject(list, { mode: 'edit', scope: scope });
|
||||||
@@ -517,112 +515,3 @@ HomeGroups.$inject = ['$rootScope', '$log', '$scope', '$filter', '$compile', '$l
|
|||||||
* @description This loads the page for 'home/hosts'
|
* @description This loads the page for 'home/hosts'
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function HomeHosts($scope, $location, $stateParams, HomeHostList, GenerateList, ProcessErrors, ReturnToCaller, ClearScope,
|
|
||||||
GetBasePath, SearchInit, PaginateInit, FormatDate, SetStatus, ToggleHostEnabled, HostsEdit, Find, ShowJobSummary) {
|
|
||||||
|
|
||||||
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
|
|
||||||
//scope.
|
|
||||||
|
|
||||||
var generator = GenerateList,
|
|
||||||
list = HomeHostList,
|
|
||||||
defaultUrl = GetBasePath('hosts');
|
|
||||||
|
|
||||||
if ($scope.removePostRefresh) {
|
|
||||||
$scope.removePostRefresh();
|
|
||||||
}
|
|
||||||
$scope.removePostRefresh = $scope.$on('PostRefresh', function () {
|
|
||||||
for (var i = 0; i < $scope.hosts.length; i++) {
|
|
||||||
$scope.hosts[i].inventory_name = $scope.hosts[i].summary_fields.inventory.name;
|
|
||||||
//SetHostStatus($scope['hosts'][i]);
|
|
||||||
SetStatus({
|
|
||||||
$scope: $scope,
|
|
||||||
host: $scope.hosts[i]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generator.inject(list, { mode: 'edit', scope: $scope });
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
SearchInit({
|
|
||||||
scope: $scope,
|
|
||||||
set: 'hosts',
|
|
||||||
list: list,
|
|
||||||
url: defaultUrl
|
|
||||||
});
|
|
||||||
|
|
||||||
PaginateInit({
|
|
||||||
scope: $scope,
|
|
||||||
list: list,
|
|
||||||
url: defaultUrl
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process search params
|
|
||||||
if ($stateParams.name) {
|
|
||||||
$scope[HomeHostList.iterator + 'InputDisable'] = false;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchValue'] = $stateParams.name;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchField'] = 'name';
|
|
||||||
$scope[HomeHostList.iterator + 'SearchFieldLabel'] = list.fields.name.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stateParams.id) {
|
|
||||||
$scope[HomeHostList.iterator + 'InputDisable'] = false;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchValue'] = $stateParams.id;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchField'] = 'id';
|
|
||||||
$scope[HomeHostList.iterator + 'SearchFieldLabel'] = list.fields.id.label;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchSelectValue'] = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stateParams.has_active_failures) {
|
|
||||||
$scope[HomeHostList.iterator + 'InputDisable'] = true;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchValue'] = $stateParams.has_active_failures;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchField'] = 'has_active_failures';
|
|
||||||
$scope[HomeHostList.iterator + 'SearchFieldLabel'] = HomeHostList.fields.has_active_failures.label;
|
|
||||||
$scope[HomeHostList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? { value: 1 } : { value: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.search(list.iterator);
|
|
||||||
|
|
||||||
$scope.refreshHosts = function() {
|
|
||||||
$scope.search(list.iterator);
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.toggleHostEnabled = function (id, sources) {
|
|
||||||
ToggleHostEnabled({
|
|
||||||
host_id: id,
|
|
||||||
external_source: sources,
|
|
||||||
host_scope: $scope
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.editHost = function (host_id) {
|
|
||||||
var host = Find({
|
|
||||||
list: $scope.hosts,
|
|
||||||
key: 'id',
|
|
||||||
val: host_id
|
|
||||||
});
|
|
||||||
if (host) {
|
|
||||||
HostsEdit({
|
|
||||||
host_scope: $scope,
|
|
||||||
host_id: host_id,
|
|
||||||
inventory_id: host.inventory,
|
|
||||||
group_id: null,
|
|
||||||
hostsReload: false,
|
|
||||||
mode: 'edit'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.showJobSummary = function (job_id) {
|
|
||||||
ShowJobSummary({
|
|
||||||
job_id: job_id
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
HomeHosts.$inject = ['$scope', '$location', '$stateParams', 'HomeHostList', 'generateList', 'ProcessErrors', 'ReturnToCaller',
|
|
||||||
'ClearScope', 'GetBasePath', 'SearchInit', 'PaginateInit', 'FormatDate', 'SetStatus', 'ToggleHostEnabled', 'HostsEdit',
|
|
||||||
'Find', 'ShowJobSummary'
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export function ProjectsList ($scope, $rootScope, $location, $log, $stateParams,
|
|||||||
mode = (base === 'projects') ? 'edit' : 'select',
|
mode = (base === 'projects') ? 'edit' : 'select',
|
||||||
url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl,
|
url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl,
|
||||||
choiceCount = 0;
|
choiceCount = 0;
|
||||||
|
|
||||||
view.inject(list, { mode: mode, scope: $scope });
|
view.inject(list, { mode: mode, scope: $scope });
|
||||||
|
|
||||||
$rootScope.flashMessage = null;
|
$rootScope.flashMessage = null;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ GetBasePath, Wait, Find, LoadDialogPartial, LoadSchedulesScope, GetChoices) {
|
|||||||
|
|
||||||
ClearScope();
|
ClearScope();
|
||||||
|
|
||||||
var base, e, id, url, parentObject;
|
var base, id, url, parentObject;
|
||||||
|
|
||||||
base = $location.path().replace(/^\//, '').split('/')[0];
|
base = $location.path().replace(/^\//, '').split('/')[0];
|
||||||
|
|
||||||
|
|||||||
@@ -198,16 +198,15 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
var defaultUrl = GetBasePath('teams'),
|
var defaultUrl = GetBasePath('teams'),
|
||||||
generator = GenerateForm,
|
generator = GenerateForm,
|
||||||
form = TeamForm,
|
form = TeamForm,
|
||||||
base = $location.path().replace(/^\//, '').split('/')[0],
|
|
||||||
master = {},
|
|
||||||
id = $stateParams.team_id,
|
id = $stateParams.team_id,
|
||||||
relatedSets = {};
|
relatedSets = {},
|
||||||
|
set;
|
||||||
|
|
||||||
$scope.team_id = id;
|
$scope.team_id = id;
|
||||||
|
|
||||||
|
|
||||||
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
||||||
generator.reset()
|
generator.reset();
|
||||||
|
|
||||||
var setScopeFields = function(data){
|
var setScopeFields = function(data){
|
||||||
_(data)
|
_(data)
|
||||||
@@ -218,7 +217,7 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
$scope[key] = value;
|
$scope[key] = value;
|
||||||
})
|
})
|
||||||
.value();
|
.value();
|
||||||
return
|
return;
|
||||||
};
|
};
|
||||||
var setScopeRelated = function(data, related){
|
var setScopeRelated = function(data, related){
|
||||||
_(related)
|
_(related)
|
||||||
@@ -242,7 +241,7 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
data[key] = $scope[key];
|
data[key] = $scope[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
var init = function(){
|
var init = function(){
|
||||||
@@ -251,7 +250,7 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
Wait('start');
|
Wait('start');
|
||||||
Rest.get(url).success(function(data){
|
Rest.get(url).success(function(data){
|
||||||
setScopeFields(data);
|
setScopeFields(data);
|
||||||
setScopeRelated(data, form.related)
|
setScopeRelated(data, form.related);
|
||||||
$scope.organization_name = data.summary_fields.organization.name;
|
$scope.organization_name = data.summary_fields.organization.name;
|
||||||
|
|
||||||
RelatedSearchInit({
|
RelatedSearchInit({
|
||||||
@@ -265,6 +264,12 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
relatedSets: relatedSets
|
relatedSets: relatedSets
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (set in relatedSets) {
|
||||||
|
$scope.search(relatedSets[set].iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.team_obj = data;
|
||||||
|
|
||||||
LookUpInit({
|
LookUpInit({
|
||||||
url: GetBasePath('organizations'),
|
url: GetBasePath('organizations'),
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
@@ -275,11 +280,11 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
input_type: 'radio'
|
input_type: 'radio'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
$scope.formCancel = function(){
|
$scope.formCancel = function(){
|
||||||
$state.go('teams', null, {reload: true});
|
$state.go('teams', null, {reload: true});
|
||||||
}
|
};
|
||||||
|
|
||||||
$scope.formSave = function(){
|
$scope.formSave = function(){
|
||||||
generator.clearApiErrors();
|
generator.clearApiErrors();
|
||||||
@@ -288,7 +293,7 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
if ($scope[form.name + '_form'].$valid){
|
if ($scope[form.name + '_form'].$valid){
|
||||||
Rest.setUrl(defaultUrl + id + '/');
|
Rest.setUrl(defaultUrl + id + '/');
|
||||||
var data = processNewData(form.fields);
|
var data = processNewData(form.fields);
|
||||||
Rest.put(data).success(function(res){
|
Rest.put(data).success(function(){
|
||||||
$state.go('teams', null, {reload: true});
|
$state.go('teams', null, {reload: true});
|
||||||
})
|
})
|
||||||
.error(function (data, status) {
|
.error(function (data, status) {
|
||||||
@@ -300,6 +305,14 @@ export function TeamsEdit($scope, $rootScope, $location,
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
$scope.convertApiUrl = function(str) {
|
||||||
|
if (str) {
|
||||||
|
return str.replace("api/v1", "#");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* Related Set implementation TDB */
|
/* Related Set implementation TDB */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,10 +229,10 @@ export function UsersEdit($scope, $rootScope, $location,
|
|||||||
var defaultUrl = GetBasePath('users'),
|
var defaultUrl = GetBasePath('users'),
|
||||||
generator = GenerateForm,
|
generator = GenerateForm,
|
||||||
form = UserForm,
|
form = UserForm,
|
||||||
base = $location.path().replace(/^\//, '').split('/')[0],
|
|
||||||
master = {},
|
master = {},
|
||||||
id = $stateParams.user_id,
|
id = $stateParams.user_id,
|
||||||
relatedSets = {};
|
relatedSets = {},
|
||||||
|
set;
|
||||||
|
|
||||||
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
generator.inject(form, { mode: 'edit', related: true, scope: $scope });
|
||||||
generator.reset();
|
generator.reset();
|
||||||
@@ -246,20 +246,28 @@ export function UsersEdit($scope, $rootScope, $location,
|
|||||||
$scope[key] = value;
|
$scope[key] = value;
|
||||||
})
|
})
|
||||||
.value();
|
.value();
|
||||||
return
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.convertApiUrl = function(str) {
|
||||||
|
if (str) {
|
||||||
|
return str.replace("api/v1", "#");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var setScopeRelated = function(data, related){
|
var setScopeRelated = function(data, related){
|
||||||
_(related)
|
_(related)
|
||||||
.pick(function(value, key){
|
.pick(function(value, key){
|
||||||
return data.related.hasOwnProperty(key) === true;
|
return data.related.hasOwnProperty(key) === true;
|
||||||
})
|
})
|
||||||
.forEach(function(value, key){
|
.forEach(function(value, key){
|
||||||
relatedSets[key] = {
|
relatedSets[key] = {
|
||||||
url: data.related[key],
|
url: data.related[key],
|
||||||
iterator: value.iterator
|
iterator: value.iterator
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.value();
|
.value();
|
||||||
};
|
};
|
||||||
// prepares a data payload for a PUT request to the API
|
// prepares a data payload for a PUT request to the API
|
||||||
@@ -270,7 +278,7 @@ export function UsersEdit($scope, $rootScope, $location,
|
|||||||
data[key] = $scope[key];
|
data[key] = $scope[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return data
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
var init = function(){
|
var init = function(){
|
||||||
@@ -296,6 +304,11 @@ export function UsersEdit($scope, $rootScope, $location,
|
|||||||
scope: $scope,
|
scope: $scope,
|
||||||
relatedSets: relatedSets
|
relatedSets: relatedSets
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (set in relatedSets) {
|
||||||
|
$scope.search(relatedSets[set].iterator);
|
||||||
|
}
|
||||||
|
|
||||||
Wait('stop');
|
Wait('stop');
|
||||||
})
|
})
|
||||||
.error(function (data, status) {
|
.error(function (data, status) {
|
||||||
@@ -315,8 +328,8 @@ export function UsersEdit($scope, $rootScope, $location,
|
|||||||
if ($scope[form.name + '_form'].$valid){
|
if ($scope[form.name + '_form'].$valid){
|
||||||
Rest.setUrl(defaultUrl + id + '/');
|
Rest.setUrl(defaultUrl + id + '/');
|
||||||
var data = processNewData(form.fields);
|
var data = processNewData(form.fields);
|
||||||
Rest.put(data).success(function(res){
|
Rest.put(data).success(function(){
|
||||||
$state.go('users', null, {reload: true})
|
$state.go('users', null, {reload: true});
|
||||||
})
|
})
|
||||||
.error(function (data, status) {
|
.error(function (data, status) {
|
||||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user: ' +
|
ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve user: ' +
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default
|
|||||||
label: "Hosts"
|
label: "Hosts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "/#/home/hosts?has_active_failures=true",
|
url: "/#/home/hosts?active-failures=true",
|
||||||
number: scope.data.hosts.failed,
|
number: scope.data.hosts.failed,
|
||||||
label: "Failed Hosts",
|
label: "Failed Hosts",
|
||||||
isFailureCount: true
|
isFailureCount: true
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
export default
|
||||||
|
['$scope', '$state', '$stateParams', 'DashboardHostsForm', 'GenerateForm', 'ParseTypeChange', 'DashboardHostService', 'host',
|
||||||
|
function($scope, $state, $stateParams, DashboardHostsForm, GenerateForm, ParseTypeChange, DashboardHostService, host){
|
||||||
|
var generator = GenerateForm,
|
||||||
|
form = DashboardHostsForm;
|
||||||
|
$scope.parseType = 'yaml';
|
||||||
|
$scope.formCancel = function(){
|
||||||
|
$state.go('^', null, {reload: true});
|
||||||
|
};
|
||||||
|
$scope.toggleHostEnabled = function(){
|
||||||
|
$scope.host.enabled = !$scope.host.enabled;
|
||||||
|
};
|
||||||
|
$scope.toggleEnabled = function(){
|
||||||
|
$scope.host.enabled = !$scope.host.enabled;
|
||||||
|
};
|
||||||
|
$scope.formSave = function(){
|
||||||
|
var host = {
|
||||||
|
id: $scope.host.id,
|
||||||
|
variables: $scope.extraVars === '---' || $scope.extraVars === '{}' ? null : $scope.extraVars,
|
||||||
|
name: $scope.name,
|
||||||
|
description: $scope.description,
|
||||||
|
enabled: $scope.host.enabled
|
||||||
|
};
|
||||||
|
DashboardHostService.putHost(host).then(function(){
|
||||||
|
$state.go('^', null, {reload: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
var init = function(){
|
||||||
|
$scope.host = host;
|
||||||
|
$scope.extraVars = host.variables === '' ? '---' : host.variables;
|
||||||
|
generator.inject(form, {mode: 'edit', related: false, scope: $scope});
|
||||||
|
$scope.extraVars = $scope.host.variables === '' ? '---' : $scope.host.variables;
|
||||||
|
$scope.name = host.name;
|
||||||
|
$scope.description = host.description;
|
||||||
|
ParseTypeChange({
|
||||||
|
scope: $scope,
|
||||||
|
field_id: 'host_variables',
|
||||||
|
variable: 'extraVars',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
}];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="tab-pane" id="organizations">
|
||||||
|
<div ui-view></div>
|
||||||
|
<div ng-cloak id="htmlTemplate" class="Panel"></div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
export default
|
||||||
|
['$scope', '$state', '$stateParams', 'PageRangeSetup', 'GetBasePath', 'DashboardHostsList',
|
||||||
|
'generateList', 'PaginateInit', 'SetStatus', 'DashboardHostService', 'hosts',
|
||||||
|
function($scope, $state, $stateParams, PageRangeSetup, GetBasePath, DashboardHostsList, GenerateList, PaginateInit, SetStatus, DashboardHostService, hosts){
|
||||||
|
var setJobStatus = function(){
|
||||||
|
_.forEach($scope.hosts, function(value){
|
||||||
|
SetStatus({
|
||||||
|
scope: $scope,
|
||||||
|
host: value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var generator = GenerateList,
|
||||||
|
list = DashboardHostsList,
|
||||||
|
defaultUrl = GetBasePath('hosts');
|
||||||
|
$scope.hostPageSize = 10;
|
||||||
|
$scope.editHost = function(id){
|
||||||
|
$state.go('dashboardHosts.edit', {id: id});
|
||||||
|
};
|
||||||
|
$scope.toggleHostEnabled = function(host){
|
||||||
|
DashboardHostService.setHostStatus(host, !host.enabled)
|
||||||
|
.then(function(res){
|
||||||
|
var index = _.findIndex($scope.hosts, function(o) {return o.id === res.data.id;});
|
||||||
|
$scope.hosts[index].enabled = res.data.enabled;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$scope.$on('PostRefresh', function(){
|
||||||
|
$scope.hosts = _.map($scope.hosts, function(value){
|
||||||
|
value.inventory_name = value.summary_fields.inventory.name;
|
||||||
|
value.inventory_id = value.summary_fields.inventory.id;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
setJobStatus();
|
||||||
|
});
|
||||||
|
var init = function(){
|
||||||
|
$scope.list = list;
|
||||||
|
$scope.host_active_search = false;
|
||||||
|
$scope.host_total_rows = hosts.results.length;
|
||||||
|
$scope.hosts = hosts.results;
|
||||||
|
setJobStatus();
|
||||||
|
generator.inject(list, {mode: 'edit', scope: $scope});
|
||||||
|
PaginateInit({
|
||||||
|
scope: $scope,
|
||||||
|
list: list,
|
||||||
|
url: defaultUrl,
|
||||||
|
pageSize: 10
|
||||||
|
});
|
||||||
|
PageRangeSetup({
|
||||||
|
scope: $scope,
|
||||||
|
count: hosts.count,
|
||||||
|
next: hosts.next,
|
||||||
|
previous: hosts.previous,
|
||||||
|
iterator: list.iterator
|
||||||
|
});
|
||||||
|
$scope.hostLoading = false;
|
||||||
|
};
|
||||||
|
init();
|
||||||
|
}];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="tab-pane" id="HomeHosts">
|
||||||
|
<div ui-view></div>
|
||||||
|
<div ng-cloak id="htmlTemplate" class="Panel"></div>
|
||||||
|
</div>
|
||||||
77
awx/ui/client/src/dashboard/hosts/dashboard-hosts.form.js
Normal file
77
awx/ui/client/src/dashboard/hosts/dashboard-hosts.form.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
export default function(){
|
||||||
|
return {
|
||||||
|
editTitle: '{{host.name}}',
|
||||||
|
name: 'host',
|
||||||
|
well: true,
|
||||||
|
formLabelSize: 'col-lg-3',
|
||||||
|
formFieldSize: 'col-lg-9',
|
||||||
|
iterator: 'host',
|
||||||
|
headerFields:{
|
||||||
|
enabled: {
|
||||||
|
//flag: 'host.enabled',
|
||||||
|
class: 'Form-header-field',
|
||||||
|
ngClick: 'toggleHostEnabled()',
|
||||||
|
type: 'toggle',
|
||||||
|
editRequired: false,
|
||||||
|
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
|
||||||
|
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
|
||||||
|
dataTitle: 'Host Enabled'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'Host Name',
|
||||||
|
type: 'text',
|
||||||
|
editRequired: true,
|
||||||
|
value: '{{name}}',
|
||||||
|
awPopOver: "<p>Provide a host name, ip address, or ip address:port. Examples include:</p>" +
|
||||||
|
"<blockquote>myserver.domain.com<br/>" +
|
||||||
|
"127.0.0.1<br />" +
|
||||||
|
"10.1.0.140:25<br />" +
|
||||||
|
"server.example.com:25" +
|
||||||
|
"</blockquote>",
|
||||||
|
dataTitle: 'Host Name',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body'
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
label: 'Description',
|
||||||
|
type: 'text',
|
||||||
|
editRequired: false
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
label: 'Variables',
|
||||||
|
type: 'textarea',
|
||||||
|
editRequired: false,
|
||||||
|
rows: 6,
|
||||||
|
class: 'modal-input-xlarge Form-textArea',
|
||||||
|
dataTitle: 'Host Variables',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: 'body',
|
||||||
|
default: '---',
|
||||||
|
awPopOver: "<p>Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.</p>" +
|
||||||
|
"JSON:<br />\n" +
|
||||||
|
"<blockquote>{<br />\"somevar\": \"somevalue\",<br />\"password\": \"magic\"<br /> }</blockquote>\n" +
|
||||||
|
"YAML:<br />\n" +
|
||||||
|
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n" +
|
||||||
|
'<p>View JSON examples at <a href="http://www.json.org" target="_blank">www.json.org</a></p>' +
|
||||||
|
'<p>View YAML examples at <a href="http://docs.ansible.com/YAMLSyntax.html" target="_blank">docs.ansible.com</a></p>',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
save: {
|
||||||
|
ngClick: 'formSave()', //$scope.function to call on click, optional
|
||||||
|
ngDisabled: "host_form.$invalid"//true //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
ngClick: 'formCancel()'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
/*************************************************
|
/*************************************************
|
||||||
* Copyright (c) 2015 Ansible, Inc.
|
* Copyright (c) 2015 Ansible, Inc.
|
||||||
*
|
*
|
||||||
* All Rights Reserved
|
* All Rights Reserved
|
||||||
*************************************************/
|
*************************************************/
|
||||||
|
|
||||||
|
|
||||||
export default
|
export default function(){
|
||||||
angular.module('HomeHostListDefinition', [])
|
return {
|
||||||
.value('HomeHostList', {
|
|
||||||
|
|
||||||
name: 'hosts',
|
name: 'hosts',
|
||||||
iterator: 'host',
|
iterator: 'host',
|
||||||
selectTitle: 'Add Existing Hosts',
|
selectTitle: 'Add Existing Hosts',
|
||||||
@@ -17,41 +15,48 @@ export default
|
|||||||
index: false,
|
index: false,
|
||||||
hover: true,
|
hover: true,
|
||||||
well: true,
|
well: true,
|
||||||
|
emptyListText: 'NO ACTIVE FAILURES FOUND',
|
||||||
fields: {
|
fields: {
|
||||||
status: {
|
status: {
|
||||||
label: "",
|
basePath: 'unified_jobs',
|
||||||
|
label: '',
|
||||||
iconOnly: true,
|
iconOnly: true,
|
||||||
icon: "{{ 'icon-job-' + host.active_failures }}",
|
searchable: true,
|
||||||
awToolTip: "{{ host.badgeToolTip }}",
|
searchType: 'select',
|
||||||
awTipPlacement: "right",
|
nosort: true,
|
||||||
dataPlacement: "right",
|
searchOptions: [],
|
||||||
awPopOver: "{{ host.job_status_html }}",
|
searchLabel: 'Job Status',
|
||||||
ngClick:"bob",
|
icon: 'icon-job-{{ host.active_failures }}',
|
||||||
columnClass: "List-staticColumn--smallStatus",
|
awToolTip: '{{ host.badgeToolTip }}',
|
||||||
searchable: false,
|
awTipPlacement: 'right',
|
||||||
nosort: true
|
dataPlacement: 'right',
|
||||||
|
awPopOver: '{{ host.job_status_html }}',
|
||||||
|
ngClick:'viewHost(host.id)',
|
||||||
|
columnClass: 'col-lg-1 col-md-1 col-sm-2 col-xs-2 List-staticColumn--smallStatus'
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
key: true,
|
key: true,
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
columnClass: 'col-lg-5 col-md-5 col-sm-5 col-xs-8 ellipsis List-staticColumnAdjacent',
|
columnClass: 'col-lg-5 col-md-5 col-sm-5 col-xs-8 ellipsis List-staticColumnAdjacent',
|
||||||
ngClass: "{ 'host-disabled-label': !host.enabled }",
|
ngClick: 'editHost(host.id)'
|
||||||
ngClick: "editHost(host.id)"
|
|
||||||
},
|
},
|
||||||
inventory_name: {
|
inventory_name: {
|
||||||
label: 'Inventory',
|
label: 'Inventory',
|
||||||
sourceModel: 'inventory',
|
sourceModel: 'inventory',
|
||||||
sourceField: 'name',
|
sourceField: 'name',
|
||||||
columnClass: 'col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis',
|
columnClass: 'col-lg-5 col-md-4 col-sm-4 hidden-xs elllipsis',
|
||||||
linkTo: "{{ '/#/inventories/' + host.inventory }}"
|
linkTo: "{{ '/#/inventories/' + host.inventory_id }}",
|
||||||
|
searchable: false
|
||||||
},
|
},
|
||||||
enabled: {
|
enabled: {
|
||||||
label: 'Disabled?',
|
label: 'Status',
|
||||||
searchSingleValue: true,
|
columnClass: 'List-staticColumn--toggle',
|
||||||
searchType: 'boolean',
|
type: 'toggle',
|
||||||
searchValue: 'false',
|
ngClick: 'toggleHostEnabled(host)',
|
||||||
searchOnly: true
|
searchable: false,
|
||||||
|
nosort: true,
|
||||||
|
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
|
||||||
|
dataTitle: 'Host Enabled',
|
||||||
},
|
},
|
||||||
has_active_failures: {
|
has_active_failures: {
|
||||||
label: 'Has failed jobs?',
|
label: 'Has failed jobs?',
|
||||||
@@ -66,30 +71,15 @@ export default
|
|||||||
searchType: 'boolean',
|
searchType: 'boolean',
|
||||||
searchValue: 'true',
|
searchValue: 'true',
|
||||||
searchOnly: true
|
searchOnly: true
|
||||||
},
|
|
||||||
id: {
|
|
||||||
label: 'ID',
|
|
||||||
searchOnly: true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fieldActions: {
|
fieldActions: {
|
||||||
|
|
||||||
columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-4',
|
columnClass: 'col-lg-2 col-md-3 col-sm-3 col-xs-4',
|
||||||
|
|
||||||
/*active_failures: {
|
|
||||||
//label: 'Job Status',
|
|
||||||
//ngHref: "\{\{'/#/hosts/' + host.id + '/job_host_summaries/?inventory=' + inventory_id \}\}",
|
|
||||||
awPopOver: "{{ host.job_status_html }}",
|
|
||||||
dataTitle: "{{ host.job_status_title }}",
|
|
||||||
awToolTip: "{{ host.badgeToolTip }}",
|
|
||||||
awTipPlacement: 'top',
|
|
||||||
dataPlacement: 'left',
|
|
||||||
iconClass: "{{ 'fa icon-failures-' + host.has_active_failures }}"
|
|
||||||
}*/
|
|
||||||
edit: {
|
edit: {
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
ngClick: "editHost(host.id)",
|
ngClick: 'editHost(host.id)',
|
||||||
icon: 'icon-edit',
|
icon: 'icon-edit',
|
||||||
awToolTip: 'Edit host',
|
awToolTip: 'Edit host',
|
||||||
dataPlacement: 'top'
|
dataPlacement: 'top'
|
||||||
@@ -99,5 +89,5 @@ export default
|
|||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
});
|
}
|
||||||
64
awx/ui/client/src/dashboard/hosts/dashboard-hosts.route.js
Normal file
64
awx/ui/client/src/dashboard/hosts/dashboard-hosts.route.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {templateUrl} from '../../shared/template-url/template-url.factory';
|
||||||
|
import listController from './dashboard-hosts-list.controller';
|
||||||
|
import editController from './dashboard-hosts-edit.controller';
|
||||||
|
|
||||||
|
var dashboardHostsList = {
|
||||||
|
name: 'dashboardHosts',
|
||||||
|
url: '/home/hosts?:active-failures',
|
||||||
|
controller: listController,
|
||||||
|
templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-list'),
|
||||||
|
data: {
|
||||||
|
activityStream: true,
|
||||||
|
activityStreamTarget: 'host'
|
||||||
|
},
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
parent: 'dashboard',
|
||||||
|
label: "HOSTS"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
features: ['FeaturesService', function(FeaturesService) {
|
||||||
|
return FeaturesService.get();
|
||||||
|
}],
|
||||||
|
hosts: ['Rest', 'GetBasePath', '$stateParams', function(Rest, GetBasePath, $stateParams){
|
||||||
|
var defaultUrl = GetBasePath('hosts') + '?page_size=10' + ($stateParams['active-failures'] ? '&has_active_failures=true' : '' );
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
return Rest.get().then(function(res){
|
||||||
|
var results = _.map(res.data.results, function(value){
|
||||||
|
value.inventory_name = value.summary_fields.inventory.name;
|
||||||
|
value.inventory_id = value.summary_fields.inventory.id;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
res.data.results = results;
|
||||||
|
return res.data;
|
||||||
|
});
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var dashboardHostsEdit = {
|
||||||
|
name: 'dashboardHosts.edit',
|
||||||
|
url: '/:id',
|
||||||
|
controller: editController,
|
||||||
|
templateUrl: templateUrl('dashboard/hosts/dashboard-hosts-edit'),
|
||||||
|
ncyBreadcrumb: {
|
||||||
|
parent: 'dashboardHosts',
|
||||||
|
label: "{{host.name}}"
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
host: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){
|
||||||
|
var defaultUrl = GetBasePath('hosts') + '?id=' + $stateParams.id;
|
||||||
|
Rest.setUrl(defaultUrl);
|
||||||
|
return Rest.get().then(function(res){
|
||||||
|
return res.data.results[0];
|
||||||
|
});
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {dashboardHostsList, dashboardHostsEdit};
|
||||||
30
awx/ui/client/src/dashboard/hosts/dashboard-hosts.service.js
Normal file
30
awx/ui/client/src/dashboard/hosts/dashboard-hosts.service.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export default
|
||||||
|
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
|
||||||
|
return {
|
||||||
|
|
||||||
|
setHostStatus: function(host, enabled){
|
||||||
|
var url = GetBasePath('hosts') + host.id;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
return Rest.put({enabled: enabled, name: host.name})
|
||||||
|
.success(function(data){
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + '. GET returned: ' + status });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
putHost: function(host){
|
||||||
|
var url = GetBasePath('hosts') + host.id;
|
||||||
|
Rest.setUrl(url);
|
||||||
|
return Rest.put(host)
|
||||||
|
.success(function(data){
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.error(function(data, status) {
|
||||||
|
ProcessErrors($rootScope, data, status, null, { hdr: 'Error!',
|
||||||
|
msg: 'Call to ' + url + '. GET returned: ' + status });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}];
|
||||||
20
awx/ui/client/src/dashboard/hosts/main.js
Normal file
20
awx/ui/client/src/dashboard/hosts/main.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/*************************************************
|
||||||
|
* Copyright (c) 2016 Ansible, Inc.
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*************************************************/
|
||||||
|
|
||||||
|
import {dashboardHostsList, dashboardHostsEdit} from './dashboard-hosts.route';
|
||||||
|
import list from './dashboard-hosts.list';
|
||||||
|
import form from './dashboard-hosts.form';
|
||||||
|
import service from './dashboard-hosts.service';
|
||||||
|
|
||||||
|
export default
|
||||||
|
angular.module('dashboardHosts', [])
|
||||||
|
.service('DashboardHostService', service)
|
||||||
|
.factory('DashboardHostsList', list)
|
||||||
|
.factory('DashboardHostsForm', form)
|
||||||
|
.run(['$stateExtender', function($stateExtender){
|
||||||
|
$stateExtender.addState(dashboardHostsList);
|
||||||
|
$stateExtender.addState(dashboardHostsEdit);
|
||||||
|
}]);
|
||||||
@@ -2,7 +2,8 @@ import dashboardCounts from './counts/main';
|
|||||||
import dashboardGraphs from './graphs/main';
|
import dashboardGraphs from './graphs/main';
|
||||||
import dashboardLists from './lists/main';
|
import dashboardLists from './lists/main';
|
||||||
import dashboardDirective from './dashboard.directive';
|
import dashboardDirective from './dashboard.directive';
|
||||||
|
import dashboardHosts from './hosts/main';
|
||||||
|
|
||||||
export default
|
export default
|
||||||
angular.module('dashboard', [dashboardCounts.name, dashboardGraphs.name, dashboardLists.name])
|
angular.module('dashboard', [dashboardHosts.name, dashboardCounts.name, dashboardGraphs.name, dashboardLists.name])
|
||||||
.directive('dashboard', dashboardDirective);
|
.directive('dashboard', dashboardDirective);
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
<footer class='Footer'>
|
<footer class='Footer'>
|
||||||
<a href="http://www.ansible.com" target="_blank">
|
|
||||||
<div class="Footer-logo" ng-class="{'is-loggedOut' : !$root.current_user.username}">
|
|
||||||
<img id="footer-logo" alt="Red Hat, Inc. | Ansible, Inc." class="Footer-logoImage" src="/static/assets/footer-logo.png">
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<div class="Footer-copyright" ng-class="{'is-loggedOut' : !$root.current_user.username}">Copyright © 2016 <a class="Footer-link" href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc.</div>
|
<div class="Footer-copyright" ng-class="{'is-loggedOut' : !$root.current_user.username}">Copyright © 2016 <a class="Footer-link" href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc.</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -114,12 +114,17 @@ export default
|
|||||||
label: 'Secret Key',
|
label: 'Secret Key',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'aws'",
|
ngShow: "kind.value == 'aws'",
|
||||||
|
ngDisabled: "secret_key_ask",
|
||||||
awRequiredWhen: {
|
awRequiredWhen: {
|
||||||
reqExpression: "aws_required",
|
reqExpression: "aws_required",
|
||||||
init: false
|
init: false
|
||||||
},
|
},
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
ask: false,
|
subCheckbox: {
|
||||||
|
variable: 'secret_key_ask',
|
||||||
|
text: 'Ask at runtime?',
|
||||||
|
ngChange: 'ask(\'secret_key\', \'undefined\')'
|
||||||
|
},
|
||||||
clear: false,
|
clear: false,
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
apiField: 'password',
|
apiField: 'password',
|
||||||
@@ -141,7 +146,7 @@ export default
|
|||||||
"host": {
|
"host": {
|
||||||
labelBind: 'hostLabel',
|
labelBind: 'hostLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
ngShow: "kind.value == 'vmware' || kind.value == 'openstack'",
|
ngShow: "kind.value == 'vmware' || kind.value == 'openstack' || kind.value === 'foreman' || kind.value === 'cloudforms'",
|
||||||
awPopOverWatch: "hostPopOver",
|
awPopOverWatch: "hostPopOver",
|
||||||
awPopOver: "set in helpers/credentials",
|
awPopOver: "set in helpers/credentials",
|
||||||
dataTitle: 'Host',
|
dataTitle: 'Host',
|
||||||
@@ -154,6 +159,23 @@ export default
|
|||||||
},
|
},
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
|
"subscription": {
|
||||||
|
label: "Subscription ID",
|
||||||
|
type: 'text',
|
||||||
|
ngShow: "kind.value == 'azure' || kind.value == 'azure_rm'",
|
||||||
|
awRequiredWhen: {
|
||||||
|
reqExpression: 'subscription_required',
|
||||||
|
init: false
|
||||||
|
},
|
||||||
|
addRequired: false,
|
||||||
|
editRequired: false,
|
||||||
|
autocomplete: false,
|
||||||
|
awPopOver: '<p>Subscription ID is an Azure construct, which is mapped to a username.</p>',
|
||||||
|
dataTitle: 'Subscription ID',
|
||||||
|
dataPlacement: 'right',
|
||||||
|
dataContainer: "body",
|
||||||
|
subForm: 'credentialSubForm'
|
||||||
|
},
|
||||||
"username": {
|
"username": {
|
||||||
labelBind: 'usernameLabel',
|
labelBind: 'usernameLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -181,23 +203,6 @@ export default
|
|||||||
dataContainer: "body",
|
dataContainer: "body",
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
"subscription_id": {
|
|
||||||
labelBind: "usernameLabel",
|
|
||||||
type: 'text',
|
|
||||||
ngShow: "kind.value == 'azure'",
|
|
||||||
awRequiredWhen: {
|
|
||||||
reqExpression: 'subscription_required',
|
|
||||||
init: false
|
|
||||||
},
|
|
||||||
addRequired: false,
|
|
||||||
editRequired: false,
|
|
||||||
autocomplete: false,
|
|
||||||
awPopOver: '<p>Subscription ID is an Azure construct, which is mapped to a username.</p>',
|
|
||||||
dataTitle: 'Subscription ID',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: "body",
|
|
||||||
subForm: 'credentialSubForm'
|
|
||||||
},
|
|
||||||
"api_key": {
|
"api_key": {
|
||||||
label: 'API Key',
|
label: 'API Key',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
@@ -207,7 +212,6 @@ export default
|
|||||||
init: false
|
init: false
|
||||||
},
|
},
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
ask: false,
|
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
clear: false,
|
clear: false,
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
@@ -215,10 +219,7 @@ export default
|
|||||||
"password": {
|
"password": {
|
||||||
labelBind: 'passwordLabel',
|
labelBind: 'passwordLabel',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'scm' || kind.value == 'vmware' || kind.value == 'openstack'",
|
ngShow: "kind.value == 'scm' || kind.value == 'vmware' || kind.value == 'openstack'|| kind.value == 'foreman'|| kind.value == 'cloudforms'|| kind.value == 'net' || kind.value == 'azure_rm'",
|
||||||
addRequired: false,
|
|
||||||
editRequired: false,
|
|
||||||
ask: false,
|
|
||||||
clear: false,
|
clear: false,
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
@@ -229,12 +230,17 @@ export default
|
|||||||
subForm: "credentialSubForm"
|
subForm: "credentialSubForm"
|
||||||
},
|
},
|
||||||
"ssh_password": {
|
"ssh_password": {
|
||||||
label: 'Password', // formally 'SSH Password'
|
label: 'Password',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'ssh'",
|
ngShow: "kind.value == 'ssh'",
|
||||||
|
ngDisabled: "ssh_password_ask",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
ask: true,
|
subCheckbox: {
|
||||||
|
variable: 'ssh_password_ask',
|
||||||
|
text: 'Ask at runtime?',
|
||||||
|
ngChange: 'ask(\'ssh_password\', \'undefined\')'
|
||||||
|
},
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
@@ -243,7 +249,7 @@ export default
|
|||||||
labelBind: 'sshKeyDataLabel',
|
labelBind: 'sshKeyDataLabel',
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
ngShow: "kind.value == 'ssh' || kind.value == 'scm' || " +
|
ngShow: "kind.value == 'ssh' || kind.value == 'scm' || " +
|
||||||
"kind.value == 'gce' || kind.value == 'azure'",
|
"kind.value == 'gce' || kind.value == 'azure' || kind.value == 'net'",
|
||||||
awRequiredWhen: {
|
awRequiredWhen: {
|
||||||
reqExpression: 'key_required',
|
reqExpression: 'key_required',
|
||||||
init: true
|
init: true
|
||||||
@@ -267,10 +273,15 @@ export default
|
|||||||
ngShow: "kind.value == 'ssh' || kind.value == 'scm'",
|
ngShow: "kind.value == 'ssh' || kind.value == 'scm'",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
ngDisabled: "keyEntered === false",
|
ngDisabled: "keyEntered === false || ssh_key_unlock_ask",
|
||||||
ask: true,
|
subCheckbox: {
|
||||||
|
variable: 'ssh_key_unlock_ask',
|
||||||
|
ngShow: "kind.value == 'ssh'",
|
||||||
|
text: 'Ask at runtime?',
|
||||||
|
ngChange: 'ask(\'ssh_key_unlock\', \'undefined\')',
|
||||||
|
ngDisabled: "keyEntered === false"
|
||||||
|
},
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
askShow: "kind.value == 'ssh'", // Only allow ask for machine credentials
|
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
"become_method": {
|
"become_method": {
|
||||||
@@ -288,25 +299,77 @@ export default
|
|||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
"become_username": {
|
"become_username": {
|
||||||
label: 'Privilege Escalation Username',
|
labelBind: 'becomeUsernameLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
ngShow: "kind.value == 'ssh' && (become_method && become_method.value)",
|
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
"become_password": {
|
"become_password": {
|
||||||
label: 'Privilege Escalation Password',
|
labelBind: 'becomePasswordLabel',
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'ssh' && (become_method && become_method.value)",
|
ngShow: "(kind.value == 'ssh' && (become_method && become_method.value)) ",
|
||||||
|
ngDisabled: "become_password_ask",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
ask: true,
|
subCheckbox: {
|
||||||
|
variable: 'become_password_ask',
|
||||||
|
text: 'Ask at runtime?',
|
||||||
|
ngChange: 'ask(\'become_password\', \'undefined\')'
|
||||||
|
},
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
},
|
},
|
||||||
|
client:{
|
||||||
|
type: 'text',
|
||||||
|
label: 'Client ID',
|
||||||
|
awRequiredWhen: {
|
||||||
|
reqExpression: "azure_rm_required",
|
||||||
|
init: false
|
||||||
|
},
|
||||||
|
subForm: 'credentialSubForm',
|
||||||
|
ngShow: "kind.value === 'azure_rm'"
|
||||||
|
},
|
||||||
|
secret:{
|
||||||
|
type: 'sensitive',
|
||||||
|
hasShowInputButton: true,
|
||||||
|
autocomplete: false,
|
||||||
|
label: 'Client Secret',
|
||||||
|
awRequiredWhen: {
|
||||||
|
reqExpression: "azure_rm_required",
|
||||||
|
init: false
|
||||||
|
},
|
||||||
|
subForm: 'credentialSubForm',
|
||||||
|
ngShow: "kind.value === 'azure_rm'"
|
||||||
|
},
|
||||||
|
tenant: {
|
||||||
|
type: 'text',
|
||||||
|
label: 'Tenent ID',
|
||||||
|
awRequiredWhen: {
|
||||||
|
reqExpression: "azure_rm_required",
|
||||||
|
init: false
|
||||||
|
},
|
||||||
|
subForm: 'credentialSubForm',
|
||||||
|
ngShow: "kind.value === 'azure_rm'"
|
||||||
|
},
|
||||||
|
authorize: {
|
||||||
|
label: 'Authorize',
|
||||||
|
type: 'checkbox',
|
||||||
|
ngChange: "toggleCallback('host_config_key')",
|
||||||
|
subForm: 'credentialSubForm',
|
||||||
|
ngShow: "kind.value === 'net'"
|
||||||
|
},
|
||||||
|
authorize_password: {
|
||||||
|
label: 'Authorize Password',
|
||||||
|
type: 'sensitive',
|
||||||
|
hasShowInputButton: true,
|
||||||
|
autocomplete: false,
|
||||||
|
subForm: 'credentialSubForm',
|
||||||
|
ngShow: "authorize && authorize !== 'false'",
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
labelBind: 'projectLabel',
|
labelBind: 'projectLabel',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
@@ -344,9 +407,14 @@ export default
|
|||||||
label: "Vault Password",
|
label: "Vault Password",
|
||||||
type: 'sensitive',
|
type: 'sensitive',
|
||||||
ngShow: "kind.value == 'ssh'",
|
ngShow: "kind.value == 'ssh'",
|
||||||
|
ngDisabled: "vault_password_ask",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
ask: true,
|
subCheckbox: {
|
||||||
|
variable: 'vault_password_ask',
|
||||||
|
text: 'Ask at runtime?',
|
||||||
|
ngChange: 'ask(\'vault_password\', \'undefined\')'
|
||||||
|
},
|
||||||
hasShowInputButton: true,
|
hasShowInputButton: true,
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
subForm: 'credentialSubForm'
|
subForm: 'credentialSubForm'
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export default
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
ngOptions: 'source.label for source in source_region_choices track by source.value',
|
ngOptions: 'source.label for source in source_region_choices track by source.value',
|
||||||
multiSelect: true,
|
multiSelect: true,
|
||||||
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure')",
|
ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure' || source.value == 'azure_rm')",
|
||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false,
|
editRequired: false,
|
||||||
dataTitle: 'Source Regions',
|
dataTitle: 'Source Regions',
|
||||||
|
|||||||
@@ -15,12 +15,23 @@ export default
|
|||||||
.value('HostForm', {
|
.value('HostForm', {
|
||||||
|
|
||||||
addTitle: 'Create Host',
|
addTitle: 'Create Host',
|
||||||
editTitle: '{{ name }}',
|
editTitle: '{{ host.name }}',
|
||||||
name: 'host',
|
name: 'host',
|
||||||
well: false,
|
well: false,
|
||||||
formLabelSize: 'col-lg-3',
|
formLabelSize: 'col-lg-3',
|
||||||
formFieldSize: 'col-lg-9',
|
formFieldSize: 'col-lg-9',
|
||||||
|
iterator: 'host',
|
||||||
|
headerFields:{
|
||||||
|
enabled: {
|
||||||
|
class: 'Form-header-field',
|
||||||
|
ngClick: 'toggleHostEnabled(host)',
|
||||||
|
type: 'toggle',
|
||||||
|
editRequired: false,
|
||||||
|
awToolTip: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
|
||||||
|
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
|
||||||
|
dataTitle: 'Host Enabled',
|
||||||
|
}
|
||||||
|
},
|
||||||
fields: {
|
fields: {
|
||||||
name: {
|
name: {
|
||||||
label: 'Host Name',
|
label: 'Host Name',
|
||||||
@@ -43,19 +54,6 @@ export default
|
|||||||
addRequired: false,
|
addRequired: false,
|
||||||
editRequired: false
|
editRequired: false
|
||||||
},
|
},
|
||||||
enabled: {
|
|
||||||
label: 'Enabled?',
|
|
||||||
type: 'checkbox',
|
|
||||||
addRequired: false,
|
|
||||||
editRequired: false,
|
|
||||||
"default": true,
|
|
||||||
awPopOver: "<p>Indicates if a host is available and should be included in running jobs.</p><p>For hosts that " +
|
|
||||||
"are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process.</p>",
|
|
||||||
dataTitle: 'Host Enabled',
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataContainer: 'body',
|
|
||||||
ngDisabled: 'has_inventory_sources == true'
|
|
||||||
},
|
|
||||||
variables: {
|
variables: {
|
||||||
label: 'Variables',
|
label: 'Variables',
|
||||||
type: 'textarea',
|
type: 'textarea',
|
||||||
@@ -82,17 +80,15 @@ export default
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
buttons: { //for now always generates <button> tags
|
buttons: {
|
||||||
/*
|
|
||||||
save: {
|
save: {
|
||||||
ngClick: 'formSave()', //$scope.function to call on click, optional
|
ngClick: 'formSave()',
|
||||||
ngDisabled: true //Disable when $pristine or $invalid, optional
|
ngDisabled: true
|
||||||
},
|
},
|
||||||
reset: {
|
cancel: {
|
||||||
ngClick: 'formReset()',
|
ngClick: 'formCancel()',
|
||||||
ngDisabled: true //Disabled when $pristine
|
ngDisabled: true
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
related: {}
|
related: {}
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ export default
|
|||||||
" syntax, test environment setup and report problems.</p>",
|
" syntax, test environment setup and report problems.</p>",
|
||||||
dataTitle: 'Job Type',
|
dataTitle: 'Job Type',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
|
subCheckbox: {
|
||||||
|
variable: 'ask_job_type_on_launch',
|
||||||
|
ngShow: "!job_type.value || job_type.value !== 'scan'",
|
||||||
|
text: 'Prompt on launch'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
inventory: {
|
inventory: {
|
||||||
label: 'Inventory',
|
label: 'Inventory',
|
||||||
@@ -59,14 +64,20 @@ export default
|
|||||||
sourceField: 'name',
|
sourceField: 'name',
|
||||||
ngClick: 'lookUpInventory()',
|
ngClick: 'lookUpInventory()',
|
||||||
awRequiredWhen: {
|
awRequiredWhen: {
|
||||||
reqExpression: "inventoryrequired",
|
reqExpression: '!ask_inventory_on_launch',
|
||||||
init: "true"
|
alwaysShowAsterisk: true
|
||||||
},
|
},
|
||||||
|
requiredErrorMsg: "Please select an Inventory or check the Prompt on launch option.",
|
||||||
column: 1,
|
column: 1,
|
||||||
awPopOver: "<p>Select the inventory containing the hosts you want this job to manage.</p>",
|
awPopOver: "<p>Select the inventory containing the hosts you want this job to manage.</p>",
|
||||||
dataTitle: 'Inventory',
|
dataTitle: 'Inventory',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
|
subCheckbox: {
|
||||||
|
variable: 'ask_inventory_on_launch',
|
||||||
|
ngShow: "!job_type.value || job_type.value !== 'scan'",
|
||||||
|
text: 'Prompt on launch'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
label: 'Project',
|
label: 'Project',
|
||||||
@@ -111,14 +122,21 @@ export default
|
|||||||
sourceModel: 'credential',
|
sourceModel: 'credential',
|
||||||
sourceField: 'name',
|
sourceField: 'name',
|
||||||
ngClick: 'lookUpCredential()',
|
ngClick: 'lookUpCredential()',
|
||||||
addRequired: false,
|
awRequiredWhen: {
|
||||||
editRequired: false,
|
reqExpression: '!ask_credential_on_launch',
|
||||||
|
alwaysShowAsterisk: true
|
||||||
|
},
|
||||||
|
requiredErrorMsg: "Please select a Machine Credential or check the Prompt on launch option.",
|
||||||
column: 1,
|
column: 1,
|
||||||
awPopOver: "<p>Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " +
|
awPopOver: "<p>Select the credential you want the job to use when accessing the remote hosts. Choose the credential containing " +
|
||||||
" the username and SSH key or password that Ansible will need to log into the remote hosts.</p>",
|
" the username and SSH key or password that Ansible will need to log into the remote hosts.</p>",
|
||||||
dataTitle: 'Credential',
|
dataTitle: 'Credential',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
|
subCheckbox: {
|
||||||
|
variable: 'ask_credential_on_launch',
|
||||||
|
text: 'Prompt on launch'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
cloud_credential: {
|
cloud_credential: {
|
||||||
label: 'Cloud Credential',
|
label: 'Cloud Credential',
|
||||||
@@ -165,7 +183,11 @@ export default
|
|||||||
"<a href=\"http://docs.ansible.com/intro_patterns.html\" target=\"_blank\">the Patterns topic at docs.ansible.com</a>.</p>",
|
"<a href=\"http://docs.ansible.com/intro_patterns.html\" target=\"_blank\">the Patterns topic at docs.ansible.com</a>.</p>",
|
||||||
dataTitle: 'Limit',
|
dataTitle: 'Limit',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
|
subCheckbox: {
|
||||||
|
variable: 'ask_limit_on_launch',
|
||||||
|
text: 'Prompt on launch'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
verbosity: {
|
verbosity: {
|
||||||
label: 'Verbosity',
|
label: 'Verbosity',
|
||||||
@@ -196,7 +218,11 @@ export default
|
|||||||
"in the Job Tags field:</p>\n<blockquote>configuration,packages</blockquote>\n",
|
"in the Job Tags field:</p>\n<blockquote>configuration,packages</blockquote>\n",
|
||||||
dataTitle: "Job Tags",
|
dataTitle: "Job Tags",
|
||||||
dataPlacement: "right",
|
dataPlacement: "right",
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
|
subCheckbox: {
|
||||||
|
variable: 'ask_tags_on_launch',
|
||||||
|
text: 'Prompt on launch'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
label: 'Labels',
|
label: 'Labels',
|
||||||
@@ -227,20 +253,11 @@ export default
|
|||||||
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n",
|
"<blockquote>---<br />somevar: somevalue<br />password: magic<br /></blockquote>\n",
|
||||||
dataTitle: 'Extra Variables',
|
dataTitle: 'Extra Variables',
|
||||||
dataPlacement: 'right',
|
dataPlacement: 'right',
|
||||||
dataContainer: "body"
|
dataContainer: "body",
|
||||||
},
|
subCheckbox: {
|
||||||
ask_variables_on_launch: {
|
variable: 'ask_variables_on_launch',
|
||||||
label: 'Prompt for Extra Variables',
|
text: 'Prompt on launch'
|
||||||
type: 'checkbox',
|
}
|
||||||
addRequired: false,
|
|
||||||
editRequird: false,
|
|
||||||
trueValue: 'true',
|
|
||||||
falseValue: 'false',
|
|
||||||
column: 2,
|
|
||||||
awPopOver: "<p>If checked, user will be prompted at job launch with a dialog allowing override of the extra variables setting.</p>",
|
|
||||||
dataPlacement: 'right',
|
|
||||||
dataTitle: 'Prompt for Extra Variables',
|
|
||||||
dataContainer: "body"
|
|
||||||
},
|
},
|
||||||
become_enabled: {
|
become_enabled: {
|
||||||
label: 'Enable Privilege Escalation',
|
label: 'Enable Privilege Escalation',
|
||||||
|
|||||||
@@ -46,100 +46,6 @@ export default
|
|||||||
},
|
},
|
||||||
|
|
||||||
related: {
|
related: {
|
||||||
|
|
||||||
users: {
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Users',
|
|
||||||
iterator: 'user',
|
|
||||||
index: false,
|
|
||||||
open: false,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "add('users')",
|
|
||||||
label: 'Add',
|
|
||||||
awToolTip: 'Add a new user',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
username: {
|
|
||||||
key: true,
|
|
||||||
label: 'Username'
|
|
||||||
},
|
|
||||||
first_name: {
|
|
||||||
label: 'First Name'
|
|
||||||
},
|
|
||||||
last_name: {
|
|
||||||
label: 'Last Name'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldActions: {
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('users', user.id, user.username)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
'class': 'btn-default',
|
|
||||||
awToolTip: 'Edit user'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('users', user.id, user.username, 'user')",
|
|
||||||
icon: 'icon-trash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Remove user'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
admins: { // Assumes a plural name (e.g. things)
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Administrators',
|
|
||||||
iterator: 'admin', // Singular form of name (e.g. thing)
|
|
||||||
index: false,
|
|
||||||
open: false, // Open accordion on load?
|
|
||||||
base: '/users',
|
|
||||||
actions: { // Actions displayed top right of list
|
|
||||||
add: {
|
|
||||||
ngClick: "add('admins')",
|
|
||||||
label: 'Add',
|
|
||||||
awToolTip: 'Add new administrator',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
username: {
|
|
||||||
key: true,
|
|
||||||
label: 'Username'
|
|
||||||
},
|
|
||||||
first_name: {
|
|
||||||
label: 'First Name'
|
|
||||||
},
|
|
||||||
last_name: {
|
|
||||||
label: 'Last Name'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fieldActions: { // Actions available on each row
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('users', admin.id, admin.username)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
awToolTip: 'Edit administrator',
|
|
||||||
'class': 'btn-default'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('admins', admin.id, admin.username, 'administrator')",
|
|
||||||
icon: 'icon-trash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Remove administrator'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
permissions: {
|
permissions: {
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
title: 'Permissions',
|
title: 'Permissions',
|
||||||
|
|||||||
@@ -59,11 +59,10 @@ export default
|
|||||||
},
|
},
|
||||||
|
|
||||||
related: {
|
related: {
|
||||||
/*
|
access_list: {
|
||||||
permissions: {
|
|
||||||
basePath: 'teams/:id/access_list/',
|
basePath: 'teams/:id/access_list/',
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
title: 'Permissions',
|
title: 'Users',
|
||||||
iterator: 'permission',
|
iterator: 'permission',
|
||||||
index: false,
|
index: false,
|
||||||
open: false,
|
open: false,
|
||||||
@@ -76,148 +75,59 @@ export default
|
|||||||
actionClass: 'btn List-buttonSubmit',
|
actionClass: 'btn List-buttonSubmit',
|
||||||
buttonContent: '+ ADD'
|
buttonContent: '+ ADD'
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
credentials: {
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Credentials',
|
|
||||||
iterator: 'credential',
|
|
||||||
open: false,
|
|
||||||
index: false,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "add('credentials')",
|
|
||||||
label: 'Add',
|
|
||||||
add: 'Add a new credential',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
name: {
|
|
||||||
key: true,
|
|
||||||
label: 'Name'
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldActions: {
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('credentials', credential.id, credential.name)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
awToolTip: 'Modify the credential',
|
|
||||||
'class': 'btn btn-default'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('credentials', credential.id, credential.name, 'credential')",
|
|
||||||
icon: 'icon-trash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Remove the credential'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
projects: {
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Projects',
|
|
||||||
iterator: 'project',
|
|
||||||
open: false,
|
|
||||||
index: false,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "add('projects')",
|
|
||||||
label: 'Add',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
name: {
|
|
||||||
key: true,
|
|
||||||
label: 'Name'
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldActions: {
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('projects', project.id, project.name)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
awToolTip: 'Modify the project',
|
|
||||||
'class': 'btn btn-default'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('projects', project.id, project.name, 'project')",
|
|
||||||
icon: 'icon-trash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Remove the project'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
users: {
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Users',
|
|
||||||
iterator: 'user',
|
|
||||||
open: false,
|
|
||||||
index: false,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "add('users')",
|
|
||||||
label: 'Add',
|
|
||||||
awToolTip: 'Add a user',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fields: {
|
fields: {
|
||||||
username: {
|
username: {
|
||||||
key: true,
|
key: true,
|
||||||
label: 'Username'
|
label: 'User',
|
||||||
|
linkBase: 'users',
|
||||||
|
class: 'col-lg-3 col-md-3 col-sm-3 col-xs-4'
|
||||||
},
|
},
|
||||||
first_name: {
|
role: {
|
||||||
label: 'First Name'
|
label: 'Role',
|
||||||
},
|
type: 'role',
|
||||||
last_name: {
|
noSort: true,
|
||||||
label: 'Last Name'
|
class: 'col-lg-9 col-md-9 col-sm-9 col-xs-8'
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldActions: {
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('users', user.id, user.username)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
awToolTip: 'Edit user',
|
|
||||||
'class': 'btn btn-default'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('users', user.id, user.username, 'user')",
|
|
||||||
icon: 'icon-terash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Remove user'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
type: 'collection',
|
||||||
|
title: 'Permissions',
|
||||||
|
iterator: 'role',
|
||||||
|
open: false,
|
||||||
|
index: false,
|
||||||
|
actions: {},
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
ngBind: 'role.summary_fields.resource_name',
|
||||||
|
linkTo: '{{convertApiUrl(role.related[role.summary_fields.resource_type])}}',
|
||||||
|
noSort: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: 'Type',
|
||||||
|
ngBind: 'role.summary_fields.resource_type_display_name',
|
||||||
|
noSort: true
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
label: 'Role',
|
||||||
|
ngBind: 'role.name',
|
||||||
|
noSort: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fieldActions: {
|
||||||
|
"delete": {
|
||||||
|
label: 'Remove',
|
||||||
|
ngClick: 'deletePermissionFromTeam(team_id, team_obj.name, role.name, role.summary_fields.resource_name, role.related.teams)',
|
||||||
|
class: "List-actionButton--delete",
|
||||||
|
iconClass: 'fa fa-times',
|
||||||
|
awToolTip: 'Dissasociate permission from team'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideOnSuperuser: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
}
|
|
||||||
|
|
||||||
}); //InventoryForm
|
}); //InventoryForm
|
||||||
|
|||||||
@@ -115,71 +115,6 @@ export default
|
|||||||
},
|
},
|
||||||
|
|
||||||
related: {
|
related: {
|
||||||
/*
|
|
||||||
permissions: {
|
|
||||||
basePath: 'teams/:id/access_list/',
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Permissions',
|
|
||||||
iterator: 'permission',
|
|
||||||
index: false,
|
|
||||||
open: false,
|
|
||||||
searchType: 'select',
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "addPermission",
|
|
||||||
label: 'Add',
|
|
||||||
awToolTip: 'Add a permission',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
credentials: {
|
|
||||||
type: 'collection',
|
|
||||||
title: 'Credentials',
|
|
||||||
iterator: 'credential',
|
|
||||||
open: false,
|
|
||||||
index: false,
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add: {
|
|
||||||
ngClick: "add('credentials')",
|
|
||||||
label: 'Add',
|
|
||||||
awToolTip: 'Add a credential for this user',
|
|
||||||
actionClass: 'btn List-buttonSubmit',
|
|
||||||
buttonContent: '+ ADD'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
name: {
|
|
||||||
key: true,
|
|
||||||
label: 'Name'
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
label: 'Description'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldActions: {
|
|
||||||
edit: {
|
|
||||||
label: 'Edit',
|
|
||||||
ngClick: "edit('credentials', credential.id, credential.name)",
|
|
||||||
icon: 'icon-edit',
|
|
||||||
awToolTip: 'Edit the credential',
|
|
||||||
'class': 'btn btn-default'
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
label: 'Delete',
|
|
||||||
ngClick: "delete('credentials', credential.id, credential.name, 'credential')",
|
|
||||||
icon: 'icon-trash',
|
|
||||||
"class": 'btn-danger',
|
|
||||||
awToolTip: 'Delete the credential'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
organizations: {
|
organizations: {
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
title: 'Organizations',
|
title: 'Organizations',
|
||||||
@@ -197,9 +132,9 @@ export default
|
|||||||
description: {
|
description: {
|
||||||
label: 'Description'
|
label: 'Description'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
hideOnSuperuser: true
|
||||||
},
|
},
|
||||||
|
|
||||||
teams: {
|
teams: {
|
||||||
type: 'collection',
|
type: 'collection',
|
||||||
title: 'Teams',
|
title: 'Teams',
|
||||||
@@ -217,8 +152,44 @@ export default
|
|||||||
description: {
|
description: {
|
||||||
label: 'Description'
|
label: 'Description'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
hideOnSuperuser: true
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
hideSearchAndActions: true,
|
||||||
|
type: 'collection',
|
||||||
|
title: 'Permissions',
|
||||||
|
iterator: 'permission',
|
||||||
|
open: false,
|
||||||
|
index: false,
|
||||||
|
fields: {
|
||||||
|
name: {
|
||||||
|
label: 'Name',
|
||||||
|
ngBind: 'permission.summary_fields.resource_name',
|
||||||
|
linkTo: '{{convertApiUrl(permission.related[permission.summary_fields.resource_type])}}',
|
||||||
|
noSort: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
label: 'Type',
|
||||||
|
ngBind: 'permission.summary_fields.resource_type_display_name',
|
||||||
|
noSort: true
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
label: 'Role',
|
||||||
|
ngBind: 'permission.name',
|
||||||
|
noSort: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fieldActions: {
|
||||||
|
"delete": {
|
||||||
|
label: 'Remove',
|
||||||
|
ngClick: 'deletePermissionFromUser(user_id, username, permission.name, permission.summary_fields.resource_name, permission.related.users)',
|
||||||
|
iconClass: 'fa fa-times',
|
||||||
|
awToolTip: 'Dissasociate permission from user'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideOnSuperuser: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}); //UserForm
|
});
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
break;
|
break;
|
||||||
case 'ssh':
|
case 'ssh':
|
||||||
scope.usernameLabel = 'Username'; //formally 'SSH Username'
|
scope.usernameLabel = 'Username'; //formally 'SSH Username'
|
||||||
|
scope.becomeUsernameLabel = 'Privilege Escalation Username';
|
||||||
|
scope.becomePasswordLabel = 'Privilege Escalation Password';
|
||||||
break;
|
break;
|
||||||
case 'scm':
|
case 'scm':
|
||||||
scope.sshKeyDataLabel = 'SCM Private Key';
|
scope.sshKeyDataLabel = 'SCM Private Key';
|
||||||
@@ -109,13 +111,20 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
"as: </p><p>adjective-noun-000</p>";
|
"as: </p><p>adjective-noun-000</p>";
|
||||||
break;
|
break;
|
||||||
case 'azure':
|
case 'azure':
|
||||||
scope.usernameLabel = "Subscription ID";
|
|
||||||
scope.sshKeyDataLabel = 'Management Certificate';
|
scope.sshKeyDataLabel = 'Management Certificate';
|
||||||
scope.subscription_required = true;
|
scope.subscription_required = true;
|
||||||
scope.key_required = true;
|
scope.key_required = true;
|
||||||
scope.key_description = "Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.";
|
scope.key_description = "Paste the contents of the PEM file that corresponds to the certificate you uploaded in the Microsoft Azure console.";
|
||||||
scope.key_hint= "drag and drop a management certificate file on the field below";
|
scope.key_hint= "drag and drop a management certificate file on the field below";
|
||||||
break;
|
break;
|
||||||
|
case 'azure_rm':
|
||||||
|
scope.usernameLabel = "Username";
|
||||||
|
scope.subscription_required = true;
|
||||||
|
scope.username_required = true;
|
||||||
|
scope.password_required = true;
|
||||||
|
scope.passwordLabel = 'Password';
|
||||||
|
scope.azure_rm_required = true;
|
||||||
|
break;
|
||||||
case 'vmware':
|
case 'vmware':
|
||||||
scope.username_required = true;
|
scope.username_required = true;
|
||||||
scope.host_required = true;
|
scope.host_required = true;
|
||||||
@@ -137,6 +146,26 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
scope.hostPopOver = "<p>The host to authenticate with." +
|
scope.hostPopOver = "<p>The host to authenticate with." +
|
||||||
"<br />For example, https://openstack.business.com/v2.0/";
|
"<br />For example, https://openstack.business.com/v2.0/";
|
||||||
break;
|
break;
|
||||||
|
case 'foreman':
|
||||||
|
scope.username_required = true;
|
||||||
|
scope.password_required = true;
|
||||||
|
scope.passwordLabel = 'Password';
|
||||||
|
scope.host_required = true;
|
||||||
|
scope.hostLabel = "Satellite 6 Host";
|
||||||
|
break;
|
||||||
|
case 'cloudforms':
|
||||||
|
scope.username_required = true;
|
||||||
|
scope.password_required = true;
|
||||||
|
scope.passwordLabel = 'Password';
|
||||||
|
scope.host_required = true;
|
||||||
|
scope.hostLabel = "CloudForms Host";
|
||||||
|
break;
|
||||||
|
case 'net':
|
||||||
|
scope.username_required = true;
|
||||||
|
scope.password_required = true;
|
||||||
|
scope.passwordLabel = 'Password';
|
||||||
|
scope.sshKeyDataLabel = 'SSH Key';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +234,7 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
|
if (fld !== 'access_key' && fld !== 'secret_key' && fld !== 'ssh_username' &&
|
||||||
fld !== 'ssh_password') {
|
fld !== 'ssh_password') {
|
||||||
if (fld === "organization" && !scope[fld]) {
|
if (fld === "organization" && !scope[fld]) {
|
||||||
data["user"] = $rootScope.current_user.id;
|
data.user = $rootScope.current_user.id;
|
||||||
} else if (scope[fld] === null) {
|
} else if (scope[fld] === null) {
|
||||||
data[fld] = "";
|
data[fld] = "";
|
||||||
} else {
|
} else {
|
||||||
@@ -238,7 +267,7 @@ angular.module('CredentialsHelper', ['Utilities'])
|
|||||||
data.project = scope.project;
|
data.project = scope.project;
|
||||||
break;
|
break;
|
||||||
case 'azure':
|
case 'azure':
|
||||||
data.username = scope.subscription_id;
|
data.username = scope.subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wait('start');
|
Wait('start');
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
CreateSelect2({
|
CreateSelect2({
|
||||||
element: '#source_source_regions'
|
element: '#source_source_regions'
|
||||||
});
|
});
|
||||||
} else if (scope.source.value === 'azure') {
|
} else if (scope.source.value === 'azure' || scope.source.value === 'azure_rm') {
|
||||||
scope.source_region_choices = scope.azure_regions;
|
scope.source_region_choices = scope.azure_regions;
|
||||||
$('#source_form').addClass('squeeze');
|
$('#source_form').addClass('squeeze');
|
||||||
CreateSelect2({
|
CreateSelect2({
|
||||||
@@ -312,8 +312,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
}
|
}
|
||||||
if (scope.source.value === 'rax' ||
|
if (scope.source.value === 'rax' ||
|
||||||
scope.source.value === 'ec2' ||
|
scope.source.value === 'ec2' ||
|
||||||
scope.source.value==='gce' ||
|
scope.source.value ==='gce' ||
|
||||||
|
scope.source.value === 'foreman' ||
|
||||||
|
scope.source.value ==='cloudforms' ||
|
||||||
scope.source.value === 'azure' ||
|
scope.source.value === 'azure' ||
|
||||||
|
scope.source.value === 'azure_rm' ||
|
||||||
scope.source.value === 'vmware' ||
|
scope.source.value === 'vmware' ||
|
||||||
scope.source.value === 'openstack') {
|
scope.source.value === 'openstack') {
|
||||||
if (scope.source.value === 'ec2') {
|
if (scope.source.value === 'ec2') {
|
||||||
@@ -1008,7 +1011,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', listGenerator.name
|
|||||||
}
|
}
|
||||||
else if(fld === "inventory_script"){
|
else if(fld === "inventory_script"){
|
||||||
// the API stores it as 'source_script', we call it inventory_script
|
// the API stores it as 'source_script', we call it inventory_script
|
||||||
data.summary_fields['inventory_script'] = data.summary_fields.source_script;
|
data.summary_fields.inventory_script = data.summary_fields.source_script;
|
||||||
sources_scope.inventory_script = data.source_script;
|
sources_scope.inventory_script = data.source_script;
|
||||||
master.inventory_script = sources_scope.inventory_script;
|
master.inventory_script = sources_scope.inventory_script;
|
||||||
} else if (fld === "source_regions") {
|
} else if (fld === "source_regions") {
|
||||||
|
|||||||
@@ -236,47 +236,6 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
|
|||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.factory('ToggleHostEnabled', [ 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Alert', 'Find', 'SetEnabledMsg',
|
|
||||||
function(GetBasePath, Rest, Wait, ProcessErrors, Alert, Find, SetEnabledMsg) {
|
|
||||||
return function(params) {
|
|
||||||
|
|
||||||
var id = params.host_id,
|
|
||||||
external_source = params.external_source,
|
|
||||||
parent_scope = params.parent_scope,
|
|
||||||
host_scope = params.host_scope,
|
|
||||||
host;
|
|
||||||
|
|
||||||
function setMsg(host) {
|
|
||||||
host.enabled = (host.enabled) ? false : true;
|
|
||||||
host.enabled_flag = host.enabled;
|
|
||||||
SetEnabledMsg(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!external_source) {
|
|
||||||
// Host is not managed by an external source
|
|
||||||
Wait('start');
|
|
||||||
host = Find({ list: host_scope.hosts, key: 'id', val: id });
|
|
||||||
setMsg(host);
|
|
||||||
|
|
||||||
Rest.setUrl(GetBasePath('hosts') + id + '/');
|
|
||||||
Rest.put(host)
|
|
||||||
.success( function() {
|
|
||||||
Wait('stop');
|
|
||||||
})
|
|
||||||
.error( function(data, status) {
|
|
||||||
// Flip the enabled flag back
|
|
||||||
setMsg(host);
|
|
||||||
ProcessErrors(parent_scope, data, status, null,
|
|
||||||
{ hdr: 'Error!', msg: 'Failed to update host. PUT returned status: ' + status });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Alert('Action Not Allowed', 'This host is managed by an external cloud source. Disable it at the external source, ' +
|
|
||||||
'then run an inventory sync to update Tower with the new status.', 'alert-info');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}])
|
|
||||||
|
|
||||||
.factory('HostsList', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostList', 'generateList',
|
.factory('HostsList', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostList', 'generateList',
|
||||||
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'HostsAdd', 'HostsReload', 'SelectionInit',
|
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'HostsAdd', 'HostsReload', 'SelectionInit',
|
||||||
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostList, GenerateList, Prompt, SearchInit,
|
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostList, GenerateList, Prompt, SearchInit,
|
||||||
|
|||||||
@@ -351,22 +351,23 @@ export default
|
|||||||
};
|
};
|
||||||
}])
|
}])
|
||||||
|
|
||||||
.factory('UpdateJobStatus', ['GetElapsed', 'Empty', 'JobIsFinished', function(GetElapsed, Empty, JobIsFinished) {
|
.factory('UpdateJobStatus', ['GetElapsed', 'Empty', 'JobIsFinished', 'longDateFilter', function(GetElapsed, Empty, JobIsFinished, longDateFilter) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
var scope = params.scope,
|
var scope = params.scope,
|
||||||
failed = params.failed,
|
failed = params.failed,
|
||||||
modified = params.modified,
|
modified = params.modified,
|
||||||
started = params.started;
|
started = params.started,
|
||||||
|
finished = params.finished;
|
||||||
|
|
||||||
if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' &&
|
if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' &&
|
||||||
scope.job_status.status !== 'canceled') {
|
scope.job_status.status !== 'canceled') {
|
||||||
scope.job_status.status = 'failed';
|
scope.job_status.status = 'failed';
|
||||||
}
|
}
|
||||||
if (JobIsFinished(scope) && !Empty(modified)) {
|
if (JobIsFinished(scope) && !Empty(modified)) {
|
||||||
scope.job_status.finished = longDateFilter(modified)
|
scope.job_status.finished = longDateFilter(modified);
|
||||||
}
|
}
|
||||||
if (!Empty(started) && Empty(scope.job_status.started)) {
|
if (!Empty(started) && Empty(scope.job_status.started)) {
|
||||||
scope.job_status.started = longDateFilter(modified)
|
scope.job_status.started = longDateFilter(modified);
|
||||||
}
|
}
|
||||||
if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) {
|
if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) {
|
||||||
scope.job_status.elapsed = GetElapsed({
|
scope.job_status.elapsed = GetElapsed({
|
||||||
@@ -900,8 +901,7 @@ export default
|
|||||||
.factory('SelectTask', ['JobDetailService', function(JobDetailService) {
|
.factory('SelectTask', ['JobDetailService', function(JobDetailService) {
|
||||||
return function(params) {
|
return function(params) {
|
||||||
var scope = params.scope,
|
var scope = params.scope,
|
||||||
id = params.id,
|
id = params.id;
|
||||||
callback = params.callback;
|
|
||||||
|
|
||||||
scope.selectedTask = id;
|
scope.selectedTask = id;
|
||||||
scope.tasks.forEach(function(task, idx) {
|
scope.tasks.forEach(function(task, idx) {
|
||||||
@@ -912,7 +912,7 @@ export default
|
|||||||
scope.tasks[idx].taskActiveClass = '';
|
scope.tasks[idx].taskActiveClass = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var params = {
|
params = {
|
||||||
parent: scope.selectedTask,
|
parent: scope.selectedTask,
|
||||||
event__startswith: 'runner',
|
event__startswith: 'runner',
|
||||||
page_size: scope.hostResultsMaxRows,
|
page_size: scope.hostResultsMaxRows,
|
||||||
|
|||||||
@@ -804,7 +804,7 @@ function($compile, Rest, GetBasePath, TextareaResize,CreateDialog, GenerateForm,
|
|||||||
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) ||
|
if((scope.portalMode===false || scope.$parent.portalMode===false ) && Empty(data.system_job) ||
|
||||||
(base === 'home')){
|
(base === 'home')){
|
||||||
// use $state.go with reload: true option to re-instantiate sockets in
|
// use $state.go with reload: true option to re-instantiate sockets in
|
||||||
$state.go('jobDetail', {id: job}, {reload: true})
|
$state.go('jobDetail', {id: job}, {reload: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user