mirror of
https://github.com/ansible/awx.git
synced 2026-03-17 08:57:33 -02:30
Merge branch 'devel' into workflow-channels
This commit is contained in:
13
Makefile
13
Makefile
@@ -284,18 +284,18 @@ virtualenv_tower:
|
|||||||
requirements_ansible: virtualenv_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 --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt ;\
|
$(VENV_BASE)/ansible/bin/pip install --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt ;\
|
||||||
else \
|
else \
|
||||||
pip install --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt ; \
|
pip install --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install third-party requirements needed for Tower's environment.
|
# Install third-party requirements needed for Tower's environment.
|
||||||
requirements_tower: virtualenv_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 --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt ;\
|
$(VENV_BASE)/tower/bin/pip install --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt ;\
|
||||||
else \
|
else \
|
||||||
pip install --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt ; \
|
pip install --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
requirements_tower_dev:
|
requirements_tower_dev:
|
||||||
@@ -464,7 +464,10 @@ pep8: reports
|
|||||||
@(set -o pipefail && $@ | tee reports/$@.report)
|
@(set -o pipefail && $@ | tee reports/$@.report)
|
||||||
|
|
||||||
flake8: reports
|
flake8: reports
|
||||||
@(set -o pipefail && $@ | tee reports/$@.report)
|
@if [ "$(VENV_BASE)" ]; then \
|
||||||
|
. $(VENV_BASE)/tower/bin/activate; \
|
||||||
|
fi; \
|
||||||
|
(set -o pipefail && $@ | tee reports/$@.report)
|
||||||
|
|
||||||
pyflakes: reports
|
pyflakes: reports
|
||||||
@(set -o pipefail && $@ | tee reports/$@.report)
|
@(set -o pipefail && $@ | tee reports/$@.report)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ try:
|
|||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
MODE = 'production'
|
MODE = 'production'
|
||||||
|
|
||||||
|
|
||||||
def find_commands(management_dir):
|
def find_commands(management_dir):
|
||||||
# Modified version of function from django/core/management/__init__.py.
|
# Modified version of function from django/core/management/__init__.py.
|
||||||
command_dir = os.path.join(management_dir, 'commands')
|
command_dir = os.path.join(management_dir, 'commands')
|
||||||
@@ -33,6 +34,7 @@ def find_commands(management_dir):
|
|||||||
pass
|
pass
|
||||||
return commands
|
return commands
|
||||||
|
|
||||||
|
|
||||||
def prepare_env():
|
def prepare_env():
|
||||||
# Update the default settings environment variable based on current mode.
|
# Update the default settings environment variable based on current mode.
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE)
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from awx.main.models import UnifiedJob, AuthToken
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.api.authentication')
|
logger = logging.getLogger('awx.api.authentication')
|
||||||
|
|
||||||
|
|
||||||
class TokenAuthentication(authentication.TokenAuthentication):
|
class TokenAuthentication(authentication.TokenAuthentication):
|
||||||
'''
|
'''
|
||||||
Custom token authentication using tokens that expire and are associated
|
Custom token authentication using tokens that expire and are associated
|
||||||
|
|||||||
@@ -20,12 +20,14 @@ from rest_framework.filters import BaseFilterBackend
|
|||||||
# Ansible Tower
|
# Ansible Tower
|
||||||
from awx.main.utils import get_type_for_model, to_python_boolean
|
from awx.main.utils import get_type_for_model, to_python_boolean
|
||||||
|
|
||||||
|
|
||||||
class MongoFilterBackend(BaseFilterBackend):
|
class MongoFilterBackend(BaseFilterBackend):
|
||||||
|
|
||||||
# FIX: Note that MongoEngine can't use the filter backends from DRF
|
# FIX: Note that MongoEngine can't use the filter backends from DRF
|
||||||
def filter_queryset(self, request, queryset, view):
|
def filter_queryset(self, request, queryset, view):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class TypeFilterBackend(BaseFilterBackend):
|
class TypeFilterBackend(BaseFilterBackend):
|
||||||
'''
|
'''
|
||||||
Filter on type field now returned with all objects.
|
Filter on type field now returned with all objects.
|
||||||
@@ -62,6 +64,7 @@ class TypeFilterBackend(BaseFilterBackend):
|
|||||||
# Return a 400 for invalid field names.
|
# Return a 400 for invalid field names.
|
||||||
raise ParseError(*e.args)
|
raise ParseError(*e.args)
|
||||||
|
|
||||||
|
|
||||||
class FieldLookupBackend(BaseFilterBackend):
|
class FieldLookupBackend(BaseFilterBackend):
|
||||||
'''
|
'''
|
||||||
Filter using field lookups provided via query string parameters.
|
Filter using field lookups provided via query string parameters.
|
||||||
@@ -229,6 +232,7 @@ class FieldLookupBackend(BaseFilterBackend):
|
|||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise ParseError(e.messages)
|
raise ParseError(e.messages)
|
||||||
|
|
||||||
|
|
||||||
class OrderByBackend(BaseFilterBackend):
|
class OrderByBackend(BaseFilterBackend):
|
||||||
'''
|
'''
|
||||||
Filter to apply ordering based on query string parameters.
|
Filter to apply ordering based on query string parameters.
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.api.generics')
|
logger = logging.getLogger('awx.api.generics')
|
||||||
|
|
||||||
|
|
||||||
def get_view_name(cls, suffix=None):
|
def get_view_name(cls, suffix=None):
|
||||||
'''
|
'''
|
||||||
Wrapper around REST framework get_view_name() to support get_name() method
|
Wrapper around REST framework get_view_name() to support get_name() method
|
||||||
@@ -59,6 +60,7 @@ def get_view_name(cls, suffix=None):
|
|||||||
return ('%s %s' % (name, suffix)) if suffix else name
|
return ('%s %s' % (name, suffix)) if suffix else name
|
||||||
return views.get_view_name(cls, suffix=None)
|
return views.get_view_name(cls, suffix=None)
|
||||||
|
|
||||||
|
|
||||||
def get_view_description(cls, html=False):
|
def get_view_description(cls, html=False):
|
||||||
'''
|
'''
|
||||||
Wrapper around REST framework get_view_description() to support
|
Wrapper around REST framework get_view_description() to support
|
||||||
@@ -78,6 +80,7 @@ def get_view_description(cls, html=False):
|
|||||||
desc = '<div class="description">%s</div>' % desc
|
desc = '<div class="description">%s</div>' % desc
|
||||||
return mark_safe(desc)
|
return mark_safe(desc)
|
||||||
|
|
||||||
|
|
||||||
class APIView(views.APIView):
|
class APIView(views.APIView):
|
||||||
|
|
||||||
def initialize_request(self, request, *args, **kwargs):
|
def initialize_request(self, request, *args, **kwargs):
|
||||||
@@ -227,11 +230,13 @@ class GenericAPIView(generics.GenericAPIView, APIView):
|
|||||||
d['settings'] = settings
|
d['settings'] = settings
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class SimpleListAPIView(generics.ListAPIView, GenericAPIView):
|
class SimpleListAPIView(generics.ListAPIView, GenericAPIView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.request.user.get_queryset(self.model)
|
return self.request.user.get_queryset(self.model)
|
||||||
|
|
||||||
|
|
||||||
class ListAPIView(generics.ListAPIView, GenericAPIView):
|
class ListAPIView(generics.ListAPIView, GenericAPIView):
|
||||||
# Base class for a read-only list view.
|
# Base class for a read-only list view.
|
||||||
|
|
||||||
@@ -266,10 +271,12 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
|
|||||||
fields.append(field.name)
|
fields.append(field.name)
|
||||||
return fields
|
return fields
|
||||||
|
|
||||||
|
|
||||||
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
|
||||||
# Base class for a list view that allows creating new objects.
|
# Base class for a list view that allows creating new objects.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ParentMixin(object):
|
class ParentMixin(object):
|
||||||
|
|
||||||
def get_parent_object(self):
|
def get_parent_object(self):
|
||||||
@@ -288,6 +295,7 @@ class ParentMixin(object):
|
|||||||
if not self.request.user.can_access(*args):
|
if not self.request.user.can_access(*args):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
class SubListAPIView(ListAPIView, ParentMixin):
|
class SubListAPIView(ListAPIView, ParentMixin):
|
||||||
# Base class for a read-only sublist view.
|
# Base class for a read-only sublist view.
|
||||||
|
|
||||||
@@ -315,6 +323,7 @@ class SubListAPIView(ListAPIView, ParentMixin):
|
|||||||
sublist_qs = getattrd(parent, self.relationship).distinct()
|
sublist_qs = getattrd(parent, self.relationship).distinct()
|
||||||
return qs & sublist_qs
|
return qs & sublist_qs
|
||||||
|
|
||||||
|
|
||||||
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||||
# Base class for a sublist view that allows for creating subobjects
|
# Base class for a sublist view that allows for creating subobjects
|
||||||
# associated with the parent object.
|
# associated with the parent object.
|
||||||
@@ -367,6 +376,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
|||||||
headers = {'Location': obj.get_absolute_url()}
|
headers = {'Location': obj.get_absolute_url()}
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
||||||
# Base class for a sublist view that allows for creating subobjects and
|
# Base class for a sublist view that allows for creating subobjects and
|
||||||
# attaching/detaching them from the parent.
|
# attaching/detaching them from the parent.
|
||||||
@@ -469,12 +479,13 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
|
|||||||
else:
|
else:
|
||||||
return self.attach(request, *args, **kwargs)
|
return self.attach(request, *args, **kwargs)
|
||||||
|
|
||||||
'''
|
|
||||||
Models for which you want the last instance to be deleted from the database
|
|
||||||
when the last disassociate is called should inherit from this class. Further,
|
|
||||||
the model should implement is_detached()
|
|
||||||
'''
|
|
||||||
class DeleteLastUnattachLabelMixin(object):
|
class DeleteLastUnattachLabelMixin(object):
|
||||||
|
'''
|
||||||
|
Models for which you want the last instance to be deleted from the database
|
||||||
|
when the last disassociate is called should inherit from this class. Further,
|
||||||
|
the model should implement is_detached()
|
||||||
|
'''
|
||||||
def unattach(self, request, *args, **kwargs):
|
def unattach(self, request, *args, **kwargs):
|
||||||
(sub_id, res) = super(DeleteLastUnattachLabelMixin, self).unattach_validate(request)
|
(sub_id, res) = super(DeleteLastUnattachLabelMixin, self).unattach_validate(request)
|
||||||
if res:
|
if res:
|
||||||
@@ -489,12 +500,15 @@ class DeleteLastUnattachLabelMixin(object):
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class SubDetailAPIView(generics.RetrieveAPIView, GenericAPIView, ParentMixin):
|
class SubDetailAPIView(generics.RetrieveAPIView, GenericAPIView, ParentMixin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
|
class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
||||||
|
|
||||||
def update(self, request, *args, **kwargs):
|
def update(self, request, *args, **kwargs):
|
||||||
@@ -509,6 +523,7 @@ class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
|
|||||||
''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering '''
|
''' scrub any fields the user cannot/should not put/patch, based on user context. This runs after read-only serialization filtering '''
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
|
class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
@@ -519,9 +534,11 @@ class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
|
|||||||
obj.delete()
|
obj.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView):
|
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DestroyAPIView(GenericAPIView, generics.DestroyAPIView):
|
class DestroyAPIView(GenericAPIView, generics.DestroyAPIView):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
|
|
||||||
class RoleMetadata(Metadata):
|
class RoleMetadata(Metadata):
|
||||||
def determine_metadata(self, request, view):
|
def determine_metadata(self, request, view):
|
||||||
metadata = super(RoleMetadata, self).determine_metadata(request, view)
|
metadata = super(RoleMetadata, self).determine_metadata(request, view)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ logger = logging.getLogger('awx.api.permissions')
|
|||||||
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
|
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
|
||||||
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission']
|
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission']
|
||||||
|
|
||||||
|
|
||||||
class ModelAccessPermission(permissions.BasePermission):
|
class ModelAccessPermission(permissions.BasePermission):
|
||||||
'''
|
'''
|
||||||
Default permissions class to check user access based on the model and
|
Default permissions class to check user access based on the model and
|
||||||
@@ -140,6 +141,7 @@ class ModelAccessPermission(permissions.BasePermission):
|
|||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return self.has_permission(request, view, obj)
|
return self.has_permission(request, view, obj)
|
||||||
|
|
||||||
|
|
||||||
class JobTemplateCallbackPermission(ModelAccessPermission):
|
class JobTemplateCallbackPermission(ModelAccessPermission):
|
||||||
'''
|
'''
|
||||||
Permission check used by job template callback view for requests from
|
Permission check used by job template callback view for requests from
|
||||||
@@ -165,6 +167,7 @@ class JobTemplateCallbackPermission(ModelAccessPermission):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class TaskPermission(ModelAccessPermission):
|
class TaskPermission(ModelAccessPermission):
|
||||||
'''
|
'''
|
||||||
Permission checks used for API callbacks from running a task.
|
Permission checks used for API callbacks from running a task.
|
||||||
@@ -192,6 +195,7 @@ class TaskPermission(ModelAccessPermission):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdatePermission(ModelAccessPermission):
|
class ProjectUpdatePermission(ModelAccessPermission):
|
||||||
'''
|
'''
|
||||||
Permission check used by ProjectUpdateView to determine who can update projects
|
Permission check used by ProjectUpdateView to determine who can update projects
|
||||||
|
|||||||
@@ -496,6 +496,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
|||||||
class EmptySerializer(serializers.Serializer):
|
class EmptySerializer(serializers.Serializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BaseFactSerializer(BaseSerializer):
|
class BaseFactSerializer(BaseSerializer):
|
||||||
|
|
||||||
__metaclass__ = BaseSerializerMetaclass
|
__metaclass__ = BaseSerializerMetaclass
|
||||||
@@ -509,6 +510,7 @@ class BaseFactSerializer(BaseSerializer):
|
|||||||
ret['module'] = serializers.ChoiceField(choices=choices, read_only=True, required=False)
|
ret['module'] = serializers.ChoiceField(choices=choices, read_only=True, required=False)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobTemplateSerializer(BaseSerializer):
|
class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1293,6 +1295,7 @@ class GroupVariableDataSerializer(BaseVariableDataSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Group
|
model = Group
|
||||||
|
|
||||||
|
|
||||||
class CustomInventoryScriptSerializer(BaseSerializer):
|
class CustomInventoryScriptSerializer(BaseSerializer):
|
||||||
|
|
||||||
script = serializers.CharField(trim_whitespace=False)
|
script = serializers.CharField(trim_whitespace=False)
|
||||||
@@ -1496,7 +1499,6 @@ class TeamSerializer(BaseSerializer):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RoleSerializer(BaseSerializer):
|
class RoleSerializer(BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -1792,13 +1794,19 @@ class OrganizationCredentialSerializerCreate(CredentialSerializerCreate):
|
|||||||
class LabelsListMixin(object):
|
class LabelsListMixin(object):
|
||||||
|
|
||||||
def _summary_field_labels(self, obj):
|
def _summary_field_labels(self, obj):
|
||||||
return {'count': obj.labels.count(), 'results': [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('name')[:10]]}
|
label_list = [{'id': x.id, 'name': x.name} for x in obj.labels.all().order_by('name')[:10]]
|
||||||
|
if len(label_list) < 10:
|
||||||
|
label_ct = len(label_list)
|
||||||
|
else:
|
||||||
|
label_ct = obj.labels.count()
|
||||||
|
return {'count': label_ct, 'results': label_list}
|
||||||
|
|
||||||
def get_summary_fields(self, obj):
|
def get_summary_fields(self, obj):
|
||||||
res = super(LabelsListMixin, self).get_summary_fields(obj)
|
res = super(LabelsListMixin, self).get_summary_fields(obj)
|
||||||
res['labels'] = self._summary_field_labels(obj)
|
res['labels'] = self._summary_field_labels(obj)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2063,6 +2071,7 @@ class JobRelaunchSerializer(JobSerializer):
|
|||||||
attrs = super(JobRelaunchSerializer, self).validate(attrs)
|
attrs = super(JobRelaunchSerializer, self).validate(attrs)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandSerializer(UnifiedJobSerializer):
|
class AdHocCommandSerializer(UnifiedJobSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2163,6 +2172,7 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer):
|
|||||||
))
|
))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class SystemJobSerializer(UnifiedJobSerializer):
|
class SystemJobSerializer(UnifiedJobSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2179,6 +2189,7 @@ class SystemJobSerializer(UnifiedJobSerializer):
|
|||||||
res['cancel'] = reverse('api:system_job_cancel', args=(obj.pk,))
|
res['cancel'] = reverse('api:system_job_cancel', args=(obj.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class SystemJobCancelSerializer(SystemJobSerializer):
|
class SystemJobCancelSerializer(SystemJobSerializer):
|
||||||
|
|
||||||
can_cancel = serializers.BooleanField(read_only=True)
|
can_cancel = serializers.BooleanField(read_only=True)
|
||||||
@@ -2186,6 +2197,7 @@ class SystemJobCancelSerializer(SystemJobSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
fields = ('can_cancel',)
|
fields = ('can_cancel',)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateSerializer(LabelsListMixin, UnifiedJobTemplateSerializer):
|
class WorkflowJobTemplateSerializer(LabelsListMixin, UnifiedJobTemplateSerializer):
|
||||||
show_capabilities = ['start', 'edit', 'delete']
|
show_capabilities = ['start', 'edit', 'delete']
|
||||||
|
|
||||||
@@ -2211,10 +2223,12 @@ class WorkflowJobTemplateSerializer(LabelsListMixin, UnifiedJobTemplateSerialize
|
|||||||
def validate_extra_vars(self, value):
|
def validate_extra_vars(self, value):
|
||||||
return vars_validate_or_raise(value)
|
return vars_validate_or_raise(value)
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
class WorkflowJobTemplateListSerializer(WorkflowJobTemplateSerializer):
|
class WorkflowJobTemplateListSerializer(WorkflowJobTemplateSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
|
class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
|
||||||
|
|
||||||
@@ -2242,10 +2256,12 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
|
|||||||
ret['extra_vars'] = obj.display_extra_vars()
|
ret['extra_vars'] = obj.display_extra_vars()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer):
|
class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobCancelSerializer(WorkflowJobSerializer):
|
class WorkflowJobCancelSerializer(WorkflowJobSerializer):
|
||||||
|
|
||||||
can_cancel = serializers.BooleanField(read_only=True)
|
can_cancel = serializers.BooleanField(read_only=True)
|
||||||
@@ -2341,6 +2357,7 @@ class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer):
|
|||||||
"unified_job_template": _("Can not nest a %s inside a WorkflowJobTemplate") % ujt_obj.__class__.__name__})
|
"unified_job_template": _("Can not nest a %s inside a WorkflowJobTemplate") % ujt_obj.__class__.__name__})
|
||||||
return super(WorkflowJobTemplateNodeSerializer, self).validate(attrs)
|
return super(WorkflowJobTemplateNodeSerializer, self).validate(attrs)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
|
class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WorkflowJobNode
|
model = WorkflowJobNode
|
||||||
@@ -2357,14 +2374,16 @@ class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
|
|||||||
res['workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job.pk,))
|
res['workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeListSerializer(WorkflowJobNodeSerializer):
|
class WorkflowJobNodeListSerializer(WorkflowJobNodeSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeDetailSerializer(WorkflowJobNodeSerializer):
|
class WorkflowJobNodeDetailSerializer(WorkflowJobNodeSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer):
|
|
||||||
|
|
||||||
|
class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer):
|
||||||
'''
|
'''
|
||||||
Influence the api browser sample data to not include workflow_job_template
|
Influence the api browser sample data to not include workflow_job_template
|
||||||
when editing a WorkflowNode.
|
when editing a WorkflowNode.
|
||||||
@@ -2379,18 +2398,23 @@ class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer)
|
|||||||
field_kwargs.pop('queryset', None)
|
field_kwargs.pop('queryset', None)
|
||||||
return field_class, field_kwargs
|
return field_class, field_kwargs
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeListSerializer(WorkflowJobTemplateNodeSerializer):
|
class WorkflowJobTemplateNodeListSerializer(WorkflowJobTemplateNodeSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JobListSerializer(JobSerializer, UnifiedJobListSerializer):
|
class JobListSerializer(JobSerializer, UnifiedJobListSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandListSerializer(AdHocCommandSerializer, UnifiedJobListSerializer):
|
class AdHocCommandListSerializer(AdHocCommandSerializer, UnifiedJobListSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SystemJobListSerializer(SystemJobSerializer, UnifiedJobListSerializer):
|
class SystemJobListSerializer(SystemJobSerializer, UnifiedJobListSerializer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JobHostSummarySerializer(BaseSerializer):
|
class JobHostSummarySerializer(BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2607,6 +2631,7 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
obj.credential = JT_credential
|
obj.credential = JT_credential
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobLaunchSerializer(BaseSerializer):
|
class WorkflowJobLaunchSerializer(BaseSerializer):
|
||||||
|
|
||||||
can_start_without_user_input = serializers.BooleanField(read_only=True)
|
can_start_without_user_input = serializers.BooleanField(read_only=True)
|
||||||
@@ -2665,6 +2690,7 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
|
|||||||
obj.extra_vars = WFJT_extra_vars
|
obj.extra_vars = WFJT_extra_vars
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class NotificationTemplateSerializer(BaseSerializer):
|
class NotificationTemplateSerializer(BaseSerializer):
|
||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
|
|
||||||
@@ -2754,6 +2780,7 @@ class NotificationTemplateSerializer(BaseSerializer):
|
|||||||
raise serializers.ValidationError(error_list)
|
raise serializers.ValidationError(error_list)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class NotificationSerializer(BaseSerializer):
|
class NotificationSerializer(BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2768,6 +2795,7 @@ class NotificationSerializer(BaseSerializer):
|
|||||||
))
|
))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class LabelSerializer(BaseSerializer):
|
class LabelSerializer(BaseSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -2780,6 +2808,7 @@ class LabelSerializer(BaseSerializer):
|
|||||||
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
|
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class ScheduleSerializer(BaseSerializer):
|
class ScheduleSerializer(BaseSerializer):
|
||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
|
|
||||||
@@ -2853,6 +2882,7 @@ class ScheduleSerializer(BaseSerializer):
|
|||||||
raise serializers.ValidationError(_("rrule parsing failed validation."))
|
raise serializers.ValidationError(_("rrule parsing failed validation."))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ActivityStreamSerializer(BaseSerializer):
|
class ActivityStreamSerializer(BaseSerializer):
|
||||||
|
|
||||||
changes = serializers.SerializerMethodField()
|
changes = serializers.SerializerMethodField()
|
||||||
@@ -2996,6 +3026,7 @@ class FactVersionSerializer(BaseFactSerializer):
|
|||||||
res['fact_view'] = build_url('api:host_fact_compare_view', args=(obj.host.pk,), get=params)
|
res['fact_view'] = build_url('api:host_fact_compare_view', args=(obj.host.pk,), get=params)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class FactSerializer(BaseFactSerializer):
|
class FactSerializer(BaseFactSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
|
||||||
def paginated(method):
|
def paginated(method):
|
||||||
"""Given an method with a Django REST Framework API method signature
|
"""Given an method with a Django REST Framework API method signature
|
||||||
(e.g. `def get(self, request, ...):`), abstract out boilerplate pagination
|
(e.g. `def get(self, request, ...):`), abstract out boilerplate pagination
|
||||||
|
|||||||
223
awx/api/views.py
223
awx/api/views.py
File diff suppressed because it is too large
Load Diff
@@ -57,9 +57,11 @@ register(
|
|||||||
category_slug='cows',
|
category_slug='cows',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_read_only_ansible_cow_selection_default():
|
def _get_read_only_ansible_cow_selection_default():
|
||||||
return getattr(settings, 'ANSIBLE_COW_SELECTION', 'No default cow!')
|
return getattr(settings, 'ANSIBLE_COW_SELECTION', 'No default cow!')
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
'READONLY_ANSIBLE_COW_SELECTION',
|
'READONLY_ANSIBLE_COW_SELECTION',
|
||||||
field_class=fields.CharField,
|
field_class=fields.CharField,
|
||||||
|
|||||||
@@ -314,9 +314,6 @@ class Command(BaseCommand):
|
|||||||
self.stdout.write(' No settings to migrate!')
|
self.stdout.write(' No settings to migrate!')
|
||||||
for name, db_value in to_migrate.items():
|
for name, db_value in to_migrate.items():
|
||||||
display_value = json.dumps(db_value, indent=4)
|
display_value = json.dumps(db_value, indent=4)
|
||||||
# Always encode "raw" strings as JSON.
|
|
||||||
if isinstance(db_value, basestring):
|
|
||||||
db_value = json.dumps(db_value)
|
|
||||||
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
||||||
action = 'No Change'
|
action = 'No Change'
|
||||||
if not setting:
|
if not setting:
|
||||||
|
|||||||
@@ -14,11 +14,6 @@ def copy_tower_settings(apps, schema_editor):
|
|||||||
# LICENSE is stored as a string; convert it to a dict.
|
# LICENSE is stored as a string; convert it to a dict.
|
||||||
if tower_setting.key == 'LICENSE':
|
if tower_setting.key == 'LICENSE':
|
||||||
value = json.loads(value)
|
value = json.loads(value)
|
||||||
# Anything else (e.g. TOWER_URL_BASE) that is stored as a string
|
|
||||||
# needs to be converted to a JSON-encoded string to work with the
|
|
||||||
# JSON field.
|
|
||||||
elif tower_setting.value_type == 'string':
|
|
||||||
value = json.dumps(value)
|
|
||||||
setting, created = Setting.objects.get_or_create(
|
setting, created = Setting.objects.get_or_create(
|
||||||
key=tower_setting.key,
|
key=tower_setting.key,
|
||||||
user=tower_setting.user,
|
user=tower_setting.user,
|
||||||
|
|||||||
20
awx/conf/migrations/0003_v310_JSONField_changes.py
Normal file
20
awx/conf/migrations/0003_v310_JSONField_changes.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('conf', '0002_v310_copy_tower_settings'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='setting',
|
||||||
|
name='value',
|
||||||
|
field=awx.main.fields.JSONField(null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,11 +7,9 @@ import json
|
|||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# Tower
|
# Tower
|
||||||
from awx.main.models.base import CreatedModifiedModel
|
from awx.main.models.base import CreatedModifiedModel
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
__all__ = ['Setting']
|
__all__ = ['Setting']
|
||||||
|
|
||||||
@@ -23,8 +21,6 @@ class Setting(CreatedModifiedModel):
|
|||||||
)
|
)
|
||||||
value = JSONField(
|
value = JSONField(
|
||||||
null=True,
|
null=True,
|
||||||
# FIXME: Enable when we upgrade to JSONField with support:
|
|
||||||
# load_kwargs={'object_pairs_hook': collections.OrderedDict},
|
|
||||||
)
|
)
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
'auth.User',
|
'auth.User',
|
||||||
|
|||||||
@@ -118,4 +118,5 @@ class SettingsRegistry(object):
|
|||||||
logger.warning('Unable to retrieve default value for setting "%s".', setting, exc_info=True)
|
logger.warning('Unable to retrieve default value for setting "%s".', setting, exc_info=True)
|
||||||
return field_instance
|
return field_instance
|
||||||
|
|
||||||
|
|
||||||
settings_registry = SettingsRegistry()
|
settings_registry = SettingsRegistry()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Python
|
# Python
|
||||||
import contextlib
|
import contextlib
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
@@ -142,16 +141,18 @@ class SettingsWrapper(UserSettingsHolder):
|
|||||||
def _get_local(self, name):
|
def _get_local(self, name):
|
||||||
self._preload_cache()
|
self._preload_cache()
|
||||||
cache_key = Setting.get_cache_key(name)
|
cache_key = Setting.get_cache_key(name)
|
||||||
value = cache.get(cache_key, empty)
|
cache_value = cache.get(cache_key, empty)
|
||||||
logger.debug('cache get(%r, %r) -> %r', cache_key, empty, value)
|
logger.debug('cache get(%r, %r) -> %r', cache_key, empty, cache_value)
|
||||||
if value == SETTING_CACHE_NOTSET:
|
if cache_value == SETTING_CACHE_NOTSET:
|
||||||
value = empty
|
value = empty
|
||||||
elif value == SETTING_CACHE_NONE:
|
elif cache_value == SETTING_CACHE_NONE:
|
||||||
value = None
|
value = None
|
||||||
elif value == SETTING_CACHE_EMPTY_LIST:
|
elif cache_value == SETTING_CACHE_EMPTY_LIST:
|
||||||
value = []
|
value = []
|
||||||
elif value == SETTING_CACHE_EMPTY_DICT:
|
elif cache_value == SETTING_CACHE_EMPTY_DICT:
|
||||||
value = {}
|
value = {}
|
||||||
|
else:
|
||||||
|
value = cache_value
|
||||||
field = settings_registry.get_setting_field(name)
|
field = settings_registry.get_setting_field(name)
|
||||||
if value is empty:
|
if value is empty:
|
||||||
setting = None
|
setting = None
|
||||||
@@ -159,9 +160,6 @@ class SettingsWrapper(UserSettingsHolder):
|
|||||||
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
||||||
if setting:
|
if setting:
|
||||||
value = setting.value
|
value = setting.value
|
||||||
# If None implies not set, convert when reading the value.
|
|
||||||
if value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE:
|
|
||||||
value = SETTING_CACHE_NOTSET
|
|
||||||
else:
|
else:
|
||||||
value = SETTING_CACHE_NOTSET
|
value = SETTING_CACHE_NOTSET
|
||||||
if SETTING_CACHE_DEFAULTS:
|
if SETTING_CACHE_DEFAULTS:
|
||||||
@@ -169,8 +167,12 @@ class SettingsWrapper(UserSettingsHolder):
|
|||||||
value = field.get_default()
|
value = field.get_default()
|
||||||
except SkipField:
|
except SkipField:
|
||||||
pass
|
pass
|
||||||
logger.debug('cache set(%r, %r, %r)', cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT)
|
# If None implies not set, convert when reading the value.
|
||||||
cache.set(cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT)
|
if value is None and SETTING_CACHE_NOTSET == SETTING_CACHE_NONE:
|
||||||
|
value = SETTING_CACHE_NOTSET
|
||||||
|
if cache_value != value:
|
||||||
|
logger.debug('cache set(%r, %r, %r)', cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT)
|
||||||
|
cache.set(cache_key, self._get_cache_value(value), SETTING_CACHE_TIMEOUT)
|
||||||
if value == SETTING_CACHE_NOTSET and not SETTING_CACHE_DEFAULTS:
|
if value == SETTING_CACHE_NOTSET and not SETTING_CACHE_DEFAULTS:
|
||||||
try:
|
try:
|
||||||
value = field.get_default()
|
value = field.get_default()
|
||||||
@@ -218,9 +220,6 @@ class SettingsWrapper(UserSettingsHolder):
|
|||||||
logger.exception('Unable to assign value "%r" to setting "%s".', value, name, exc_info=True)
|
logger.exception('Unable to assign value "%r" to setting "%s".', value, name, exc_info=True)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
# Always encode "raw" strings as JSON.
|
|
||||||
if isinstance(db_value, basestring):
|
|
||||||
db_value = json.dumps(db_value)
|
|
||||||
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
setting = Setting.objects.filter(key=name, user__isnull=True).order_by('pk').first()
|
||||||
if not setting:
|
if not setting:
|
||||||
setting = Setting.objects.create(key=name, user=None, value=db_value)
|
setting = Setting.objects.create(key=name, user=None, value=db_value)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# Python
|
# Python
|
||||||
import collections
|
import collections
|
||||||
import json
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
@@ -68,7 +67,7 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
if self.category_slug not in category_slugs:
|
if self.category_slug not in category_slugs:
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug)
|
registered_settings = settings_registry.get_registered_settings(category_slug=self.category_slug, read_only=False)
|
||||||
if self.category_slug == 'user':
|
if self.category_slug == 'user':
|
||||||
return Setting.objects.filter(key__in=registered_settings, user=self.request.user)
|
return Setting.objects.filter(key__in=registered_settings, user=self.request.user)
|
||||||
else:
|
else:
|
||||||
@@ -100,9 +99,6 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
|||||||
if key == 'LICENSE':
|
if key == 'LICENSE':
|
||||||
continue
|
continue
|
||||||
setattr(serializer.instance, key, value)
|
setattr(serializer.instance, key, value)
|
||||||
# Always encode "raw" strings as JSON.
|
|
||||||
if isinstance(value, basestring):
|
|
||||||
value = json.dumps(value)
|
|
||||||
setting = settings_qs.filter(key=key).order_by('pk').first()
|
setting = settings_qs.filter(key=key).order_by('pk').first()
|
||||||
if not setting:
|
if not setting:
|
||||||
setting = Setting.objects.create(key=key, user=user, value=value)
|
setting = Setting.objects.create(key=key, user=user, value=value)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import sys
|
|||||||
# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize tower display
|
# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize tower display
|
||||||
# callback as early as possible to wrap ansible.display.Display methods.
|
# callback as early as possible to wrap ansible.display.Display methods.
|
||||||
|
|
||||||
|
|
||||||
def argv_ready(argv):
|
def argv_ready(argv):
|
||||||
if argv and os.path.basename(argv[0]) in {'ansible', 'ansible-playbook'}:
|
if argv and os.path.basename(argv[0]) in {'ansible', 'ansible-playbook'}:
|
||||||
import tower_display_callback # noqa
|
import tower_display_callback # noqa
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ def with_verbosity(f):
|
|||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
Display.verbose = with_verbosity(Display.verbose)
|
Display.verbose = with_verbosity(Display.verbose)
|
||||||
|
|
||||||
|
|
||||||
@@ -89,4 +90,5 @@ def display_with_context(f):
|
|||||||
event_context.remove_local(uuid=None)
|
event_context.remove_local(uuid=None)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
Display.display = display_with_context(Display.display)
|
Display.display = display_with_context(Display.display)
|
||||||
|
|||||||
@@ -134,4 +134,5 @@ class EventContext(object):
|
|||||||
def dump_end(self, fileobj):
|
def dump_end(self, fileobj):
|
||||||
self.dump(fileobj, self.get_end_dict())
|
self.dump(fileobj, self.get_end_dict())
|
||||||
|
|
||||||
|
|
||||||
event_context = EventContext()
|
event_context = EventContext()
|
||||||
|
|||||||
@@ -58,13 +58,16 @@ access_registry = {
|
|||||||
# ...
|
# ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class StateConflict(ValidationError):
|
class StateConflict(ValidationError):
|
||||||
status_code = 409
|
status_code = 409
|
||||||
|
|
||||||
|
|
||||||
def register_access(model_class, access_class):
|
def register_access(model_class, access_class):
|
||||||
access_classes = access_registry.setdefault(model_class, [])
|
access_classes = access_registry.setdefault(model_class, [])
|
||||||
access_classes.append(access_class)
|
access_classes.append(access_class)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_admin_role(self):
|
def user_admin_role(self):
|
||||||
role = Role.objects.get(
|
role = Role.objects.get(
|
||||||
@@ -76,9 +79,11 @@ def user_admin_role(self):
|
|||||||
role.parents = [org.admin_role.pk for org in self.organizations]
|
role.parents = [org.admin_role.pk for org in self.organizations]
|
||||||
return role
|
return role
|
||||||
|
|
||||||
|
|
||||||
def user_accessible_objects(user, role_name):
|
def user_accessible_objects(user, role_name):
|
||||||
return ResourceMixin._accessible_objects(User, user, role_name)
|
return ResourceMixin._accessible_objects(User, user, role_name)
|
||||||
|
|
||||||
|
|
||||||
def get_user_queryset(user, model_class):
|
def get_user_queryset(user, model_class):
|
||||||
'''
|
'''
|
||||||
Return a queryset for the given model_class containing only the instances
|
Return a queryset for the given model_class containing only the instances
|
||||||
@@ -98,6 +103,7 @@ def get_user_queryset(user, model_class):
|
|||||||
queryset = queryset.filter(pk__in=qs.values_list('pk', flat=True))
|
queryset = queryset.filter(pk__in=qs.values_list('pk', flat=True))
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
def check_user_access(user, model_class, action, *args, **kwargs):
|
def check_user_access(user, model_class, action, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Return True if user can perform action against model_class with the
|
Return True if user can perform action against model_class with the
|
||||||
@@ -117,6 +123,7 @@ def check_user_access(user, model_class, action, *args, **kwargs):
|
|||||||
return result
|
return result
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_user_capabilities(user, instance, **kwargs):
|
def get_user_capabilities(user, instance, **kwargs):
|
||||||
'''
|
'''
|
||||||
Returns a dictionary of capabilities the user has on the particular
|
Returns a dictionary of capabilities the user has on the particular
|
||||||
@@ -129,6 +136,7 @@ def get_user_capabilities(user, instance, **kwargs):
|
|||||||
return access_class(user).get_user_capabilities(instance, **kwargs)
|
return access_class(user).get_user_capabilities(instance, **kwargs)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_superuser(func):
|
def check_superuser(func):
|
||||||
'''
|
'''
|
||||||
check_superuser is a decorator that provides a simple short circuit
|
check_superuser is a decorator that provides a simple short circuit
|
||||||
@@ -141,6 +149,7 @@ def check_superuser(func):
|
|||||||
return func(self, *args, **kwargs)
|
return func(self, *args, **kwargs)
|
||||||
return wrapper
|
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
|
||||||
@@ -488,6 +497,7 @@ class OrganizationAccess(BaseAccess):
|
|||||||
"active_jobs": active_jobs})
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class InventoryAccess(BaseAccess):
|
class InventoryAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see inventory when:
|
I can see inventory when:
|
||||||
@@ -557,6 +567,7 @@ class InventoryAccess(BaseAccess):
|
|||||||
def can_run_ad_hoc_commands(self, obj):
|
def can_run_ad_hoc_commands(self, obj):
|
||||||
return self.user in obj.adhoc_role
|
return self.user in obj.adhoc_role
|
||||||
|
|
||||||
|
|
||||||
class HostAccess(BaseAccess):
|
class HostAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see hosts whenever I can see their inventory.
|
I can see hosts whenever I can see their inventory.
|
||||||
@@ -611,6 +622,7 @@ class HostAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return obj and self.user in obj.inventory.admin_role
|
return obj and self.user in obj.inventory.admin_role
|
||||||
|
|
||||||
|
|
||||||
class GroupAccess(BaseAccess):
|
class GroupAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see groups whenever I can see their inventory.
|
I can see groups whenever I can see their inventory.
|
||||||
@@ -678,6 +690,7 @@ class GroupAccess(BaseAccess):
|
|||||||
return self.user.can_access(InventorySource, 'start', obj.inventory_source, validate_license=validate_license)
|
return self.user.can_access(InventorySource, 'start', obj.inventory_source, validate_license=validate_license)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceAccess(BaseAccess):
|
class InventorySourceAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see inventory sources whenever I can see their group or inventory.
|
I can see inventory sources whenever I can see their group or inventory.
|
||||||
@@ -757,6 +770,7 @@ class InventoryUpdateAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return self.user in obj.inventory_source.inventory.admin_role
|
return self.user in obj.inventory_source.inventory.admin_role
|
||||||
|
|
||||||
|
|
||||||
class CredentialAccess(BaseAccess):
|
class CredentialAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see credentials when:
|
I can see credentials when:
|
||||||
@@ -829,6 +843,7 @@ class CredentialAccess(BaseAccess):
|
|||||||
# return True
|
# return True
|
||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
|
|
||||||
|
|
||||||
class TeamAccess(BaseAccess):
|
class TeamAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see a team when:
|
I can see a team when:
|
||||||
@@ -889,6 +904,7 @@ class TeamAccess(BaseAccess):
|
|||||||
return super(TeamAccess, self).can_unattach(obj, sub_obj, relationship,
|
return super(TeamAccess, self).can_unattach(obj, sub_obj, relationship,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ProjectAccess(BaseAccess):
|
class ProjectAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see projects when:
|
I can see projects when:
|
||||||
@@ -943,6 +959,7 @@ class ProjectAccess(BaseAccess):
|
|||||||
def can_start(self, obj, validate_license=True):
|
def can_start(self, obj, validate_license=True):
|
||||||
return obj and self.user in obj.update_role
|
return obj and self.user in obj.update_role
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdateAccess(BaseAccess):
|
class ProjectUpdateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see project updates when I can see the project.
|
I can see project updates when I can see the project.
|
||||||
@@ -979,6 +996,7 @@ class ProjectUpdateAccess(BaseAccess):
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
class JobTemplateAccess(BaseAccess):
|
class JobTemplateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see job templates when:
|
I can see job templates when:
|
||||||
@@ -1175,6 +1193,7 @@ class JobTemplateAccess(BaseAccess):
|
|||||||
"active_jobs": active_jobs})
|
"active_jobs": active_jobs})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class JobAccess(BaseAccess):
|
class JobAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see jobs when:
|
I can see jobs when:
|
||||||
@@ -1313,6 +1332,7 @@ class JobAccess(BaseAccess):
|
|||||||
return True
|
return True
|
||||||
return obj.job_template is not None and self.user in obj.job_template.admin_role
|
return obj.job_template is not None and self.user in obj.job_template.admin_role
|
||||||
|
|
||||||
|
|
||||||
class SystemJobTemplateAccess(BaseAccess):
|
class SystemJobTemplateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can only see/manage System Job Templates if I'm a super user
|
I can only see/manage System Job Templates if I'm a super user
|
||||||
@@ -1325,6 +1345,7 @@ class SystemJobTemplateAccess(BaseAccess):
|
|||||||
'''Only a superuser can start a job from a SystemJobTemplate'''
|
'''Only a superuser can start a job from a SystemJobTemplate'''
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class SystemJobAccess(BaseAccess):
|
class SystemJobAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can only see manage System Jobs if I'm a super user
|
I can only see manage System Jobs if I'm a super user
|
||||||
@@ -1334,6 +1355,7 @@ class SystemJobAccess(BaseAccess):
|
|||||||
def can_start(self, obj, validate_license=True):
|
def can_start(self, obj, validate_license=True):
|
||||||
return False # no relaunching of system jobs
|
return False # no relaunching of system jobs
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
class WorkflowJobTemplateNodeAccess(BaseAccess):
|
class WorkflowJobTemplateNodeAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -1430,6 +1452,7 @@ class WorkflowJobTemplateNodeAccess(BaseAccess):
|
|||||||
def can_unattach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
def can_unattach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
||||||
return self.wfjt_admin(obj) and self.check_same_WFJT(obj, sub_obj)
|
return self.wfjt_admin(obj) and self.check_same_WFJT(obj, sub_obj)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNodeAccess(BaseAccess):
|
class WorkflowJobNodeAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see a WorkflowJobNode if I have permission to...
|
I can see a WorkflowJobNode if I have permission to...
|
||||||
@@ -1462,6 +1485,7 @@ class WorkflowJobNodeAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# TODO: revisit for survey logic, notification attachments?
|
# TODO: revisit for survey logic, notification attachments?
|
||||||
class WorkflowJobTemplateAccess(BaseAccess):
|
class WorkflowJobTemplateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
@@ -1554,7 +1578,7 @@ class WorkflowJobTemplateAccess(BaseAccess):
|
|||||||
is_delete_allowed = self.user.is_superuser or self.user in obj.admin_role
|
is_delete_allowed = self.user.is_superuser or self.user in obj.admin_role
|
||||||
if not is_delete_allowed:
|
if not is_delete_allowed:
|
||||||
return False
|
return False
|
||||||
active_jobs = [dict(type="job", id=o.id)
|
active_jobs = [dict(type="workflow_job", id=o.id)
|
||||||
for o in obj.jobs.filter(status__in=ACTIVE_STATES)]
|
for o in obj.jobs.filter(status__in=ACTIVE_STATES)]
|
||||||
if len(active_jobs) > 0:
|
if len(active_jobs) > 0:
|
||||||
raise StateConflict({"conflict": _("Resource is being used by running jobs"),
|
raise StateConflict({"conflict": _("Resource is being used by running jobs"),
|
||||||
@@ -1604,6 +1628,7 @@ class WorkflowJobAccess(BaseAccess):
|
|||||||
return False
|
return False
|
||||||
return self.can_delete(obj) or self.user == obj.created_by
|
return self.can_delete(obj) or self.user == obj.created_by
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandAccess(BaseAccess):
|
class AdHocCommandAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can only see/run ad hoc commands when:
|
I can only see/run ad hoc commands when:
|
||||||
@@ -1660,6 +1685,7 @@ class AdHocCommandAccess(BaseAccess):
|
|||||||
return True
|
return True
|
||||||
return obj.inventory is not None and self.user in obj.inventory.admin_role
|
return obj.inventory is not None and self.user in obj.inventory.admin_role
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandEventAccess(BaseAccess):
|
class AdHocCommandEventAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see ad hoc command event records whenever I can read both ad hoc
|
I can see ad hoc command event records whenever I can read both ad hoc
|
||||||
@@ -1688,6 +1714,7 @@ class AdHocCommandEventAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class JobHostSummaryAccess(BaseAccess):
|
class JobHostSummaryAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see job/host summary records whenever I can read both job and host.
|
I can see job/host summary records whenever I can read both job and host.
|
||||||
@@ -1713,6 +1740,7 @@ class JobHostSummaryAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class JobEventAccess(BaseAccess):
|
class JobEventAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see job event records whenever I can read both job and host.
|
I can see job event records whenever I can read both job and host.
|
||||||
@@ -1746,6 +1774,7 @@ class JobEventAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobTemplateAccess(BaseAccess):
|
class UnifiedJobTemplateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see a unified job template whenever I can see the same project,
|
I can see a unified job template whenever I can see the same project,
|
||||||
@@ -1788,6 +1817,7 @@ class UnifiedJobTemplateAccess(BaseAccess):
|
|||||||
|
|
||||||
return qs.all()
|
return qs.all()
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobAccess(BaseAccess):
|
class UnifiedJobAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see a unified job whenever I can see the same project update,
|
I can see a unified job whenever I can see the same project update,
|
||||||
@@ -1838,6 +1868,7 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
#)
|
#)
|
||||||
return qs.all()
|
return qs.all()
|
||||||
|
|
||||||
|
|
||||||
class ScheduleAccess(BaseAccess):
|
class ScheduleAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see a schedule if I can see it's related unified job, I can create them or update them if I have write access
|
I can see a schedule if I can see it's related unified job, I can create them or update them if I have write access
|
||||||
@@ -1878,6 +1909,7 @@ class ScheduleAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return self.can_change(obj, {})
|
return self.can_change(obj, {})
|
||||||
|
|
||||||
|
|
||||||
class NotificationTemplateAccess(BaseAccess):
|
class NotificationTemplateAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see/use a notification_template if I have permission to
|
I can see/use a notification_template if I have permission to
|
||||||
@@ -1926,6 +1958,7 @@ class NotificationTemplateAccess(BaseAccess):
|
|||||||
return False
|
return False
|
||||||
return self.user in obj.organization.admin_role
|
return self.user in obj.organization.admin_role
|
||||||
|
|
||||||
|
|
||||||
class NotificationAccess(BaseAccess):
|
class NotificationAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see/use a notification if I have permission to
|
I can see/use a notification if I have permission to
|
||||||
@@ -1947,6 +1980,7 @@ class NotificationAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return self.user.can_access(NotificationTemplate, 'delete', obj.notification_template)
|
return self.user.can_access(NotificationTemplate, 'delete', obj.notification_template)
|
||||||
|
|
||||||
|
|
||||||
class LabelAccess(BaseAccess):
|
class LabelAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see/use a Label if I have permission to associated organization
|
I can see/use a Label if I have permission to associated organization
|
||||||
@@ -1980,6 +2014,7 @@ class LabelAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return self.can_change(obj, None)
|
return self.can_change(obj, None)
|
||||||
|
|
||||||
|
|
||||||
class ActivityStreamAccess(BaseAccess):
|
class ActivityStreamAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
I can see activity stream events only when I have permission on all objects included in the event
|
I can see activity stream events only when I have permission on all objects included in the event
|
||||||
@@ -2058,6 +2093,7 @@ class ActivityStreamAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CustomInventoryScriptAccess(BaseAccess):
|
class CustomInventoryScriptAccess(BaseAccess):
|
||||||
|
|
||||||
model = CustomInventoryScript
|
model = CustomInventoryScript
|
||||||
@@ -2085,6 +2121,7 @@ class CustomInventoryScriptAccess(BaseAccess):
|
|||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
return self.can_admin(obj)
|
return self.can_admin(obj)
|
||||||
|
|
||||||
|
|
||||||
class RoleAccess(BaseAccess):
|
class RoleAccess(BaseAccess):
|
||||||
'''
|
'''
|
||||||
- I can see roles when
|
- I can see roles when
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ register(
|
|||||||
category_slug='system',
|
category_slug='system',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _load_default_license_from_file():
|
def _load_default_license_from_file():
|
||||||
try:
|
try:
|
||||||
license_file = os.environ.get('AWX_LICENSE_FILE', '/etc/tower/license')
|
license_file = os.environ.get('AWX_LICENSE_FILE', '/etc/tower/license')
|
||||||
@@ -88,6 +89,7 @@ def _load_default_license_from_file():
|
|||||||
logger.warning('Could not read license from "%s".', license_file, exc_info=True)
|
logger.warning('Could not read license from "%s".', license_file, exc_info=True)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
register(
|
register(
|
||||||
'LICENSE',
|
'LICENSE',
|
||||||
field_class=fields.DictField,
|
field_class=fields.DictField,
|
||||||
@@ -169,7 +171,7 @@ register(
|
|||||||
'AWX_ANSIBLE_CALLBACK_PLUGINS',
|
'AWX_ANSIBLE_CALLBACK_PLUGINS',
|
||||||
field_class=fields.StringListField,
|
field_class=fields.StringListField,
|
||||||
label=_('Ansible Callback Plugins'),
|
label=_('Ansible Callback Plugins'),
|
||||||
help_text=_('List of paths for extra callback plugins to be used when running jobs.'),
|
help_text=_('List of paths to search for extra callback plugins to be used when running jobs.'),
|
||||||
category=_('Jobs'),
|
category=_('Jobs'),
|
||||||
category_slug='jobs',
|
category_slug='jobs',
|
||||||
)
|
)
|
||||||
@@ -178,6 +180,7 @@ register(
|
|||||||
'DEFAULT_JOB_TIMEOUT',
|
'DEFAULT_JOB_TIMEOUT',
|
||||||
field_class=fields.IntegerField,
|
field_class=fields.IntegerField,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
|
default=0,
|
||||||
label=_('Default Job Timeout'),
|
label=_('Default Job Timeout'),
|
||||||
help_text=_('Maximum time to allow jobs to run. Use value of 0 to indicate that no '
|
help_text=_('Maximum time to allow jobs to run. Use value of 0 to indicate that no '
|
||||||
'timeout should be imposed. A timeout set on an individual job template will override this.'),
|
'timeout should be imposed. A timeout set on an individual job template will override this.'),
|
||||||
@@ -189,6 +192,7 @@ register(
|
|||||||
'DEFAULT_INVENTORY_UPDATE_TIMEOUT',
|
'DEFAULT_INVENTORY_UPDATE_TIMEOUT',
|
||||||
field_class=fields.IntegerField,
|
field_class=fields.IntegerField,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
|
default=0,
|
||||||
label=_('Default Inventory Update Timeout'),
|
label=_('Default Inventory Update Timeout'),
|
||||||
help_text=_('Maximum time to allow inventory updates to run. Use value of 0 to indicate that no '
|
help_text=_('Maximum time to allow inventory updates to run. Use value of 0 to indicate that no '
|
||||||
'timeout should be imposed. A timeout set on an individual inventory source will override this.'),
|
'timeout should be imposed. A timeout set on an individual inventory source will override this.'),
|
||||||
@@ -200,6 +204,7 @@ register(
|
|||||||
'DEFAULT_PROJECT_UPDATE_TIMEOUT',
|
'DEFAULT_PROJECT_UPDATE_TIMEOUT',
|
||||||
field_class=fields.IntegerField,
|
field_class=fields.IntegerField,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
|
default=0,
|
||||||
label=_('Default Project Update Timeout'),
|
label=_('Default Project Update Timeout'),
|
||||||
help_text=_('Maximum time to allow project updates to run. Use value of 0 to indicate that no '
|
help_text=_('Maximum time to allow project updates to run. Use value of 0 to indicate that no '
|
||||||
'timeout should be imposed. A timeout set on an individual project will override this.'),
|
'timeout should be imposed. A timeout set on an individual project will override this.'),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ def user_from_token(auth_token):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@channel_session
|
@channel_session
|
||||||
def ws_connect(message):
|
def ws_connect(message):
|
||||||
token = None
|
token = None
|
||||||
|
|||||||
@@ -19,17 +19,28 @@ from django.db.models.fields.related import (
|
|||||||
)
|
)
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
|
|
||||||
|
# Django-JSONField
|
||||||
|
from jsonfield import JSONField as upstream_JSONField
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
|
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', 'JSONField']
|
||||||
|
|
||||||
|
|
||||||
|
class JSONField(upstream_JSONField):
|
||||||
|
|
||||||
|
def from_db_value(self, value, expression, connection, context):
|
||||||
|
if value in {'', None} and not self.null:
|
||||||
|
return {}
|
||||||
|
return super(JSONField, self).from_db_value(value, expression, connection, context)
|
||||||
|
|
||||||
# Based on AutoOneToOneField from django-annoying:
|
# Based on AutoOneToOneField from django-annoying:
|
||||||
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
|
||||||
|
|
||||||
|
|
||||||
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
||||||
"""Descriptor for access to the object from its related class."""
|
"""Descriptor for access to the object from its related class."""
|
||||||
|
|
||||||
@@ -46,6 +57,7 @@ class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
|
|||||||
obj.save()
|
obj.save()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class AutoOneToOneField(models.OneToOneField):
|
class AutoOneToOneField(models.OneToOneField):
|
||||||
"""OneToOneField that creates related object if it doesn't exist."""
|
"""OneToOneField that creates related object if it doesn't exist."""
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import Instance
|
from awx.main.models import Instance
|
||||||
|
|
||||||
|
|
||||||
def is_ha_environment():
|
def is_ha_environment():
|
||||||
"""Return True if this is an HA environment, and False
|
"""Return True if this is an HA environment, and False
|
||||||
otherwise.
|
otherwise.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from django.utils.timezone import now
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import ActivityStream
|
from awx.main.models import ActivityStream
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
Management command to purge old activity stream events.
|
Management command to purge old activity stream events.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from django.utils.timezone import now
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
'''
|
'''
|
||||||
Management command to cleanup expired auth tokens
|
Management command to cleanup expired auth tokens
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from awx.conf.license import feature_enabled
|
|||||||
OLDER_THAN = 'older_than'
|
OLDER_THAN = 'older_than'
|
||||||
GRANULARITY = 'granularity'
|
GRANULARITY = 'granularity'
|
||||||
|
|
||||||
|
|
||||||
class CleanupFacts(object):
|
class CleanupFacts(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.timestamp = None
|
self.timestamp = None
|
||||||
@@ -89,6 +90,7 @@ class CleanupFacts(object):
|
|||||||
deleted_count = self.cleanup(t - older_than, granularity, module=module)
|
deleted_count = self.cleanup(t - older_than, granularity, module=module)
|
||||||
print("Deleted %d facts." % deleted_count)
|
print("Deleted %d facts." % deleted_count)
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Cleanup facts. For each host older than the value specified, keep one fact scan for each time window (granularity).'
|
help = 'Cleanup facts. For each host older than the value specified, keep one fact scan for each time window (granularity).'
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
@@ -142,4 +144,3 @@ class Command(BaseCommand):
|
|||||||
raise CommandError('--granularity invalid value "%s"' % options[GRANULARITY])
|
raise CommandError('--granularity invalid value "%s"' % options[GRANULARITY])
|
||||||
|
|
||||||
cleanup_facts.run(older_than, granularity, module=options['module'])
|
cleanup_facts.run(older_than, granularity, module=options['module'])
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from django.utils.timezone import now
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import Job, AdHocCommand, ProjectUpdate, InventoryUpdate, SystemJob
|
from awx.main.models import Job, AdHocCommand, ProjectUpdate, InventoryUpdate, SystemJob
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
Management command to cleanup old jobs and project updates.
|
Management command to cleanup old jobs and project updates.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
from awx.main.models import Instance
|
from awx.main.models import Instance
|
||||||
from django.core.management.base import NoArgsCommand
|
from django.core.management.base import NoArgsCommand
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
"""List instances from the Tower database
|
"""List instances from the Tower database
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django.conf import settings
|
|||||||
from optparse import make_option
|
from optparse import make_option
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""
|
"""
|
||||||
Internal tower command.
|
Internal tower command.
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ from awx.main.models import * # noqa
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.commands.run_callback_receiver')
|
logger = logging.getLogger('awx.main.commands.run_callback_receiver')
|
||||||
|
|
||||||
class CallbackBrokerWorker(ConsumerMixin):
|
|
||||||
|
|
||||||
|
class CallbackBrokerWorker(ConsumerMixin):
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
|
||||||
@@ -51,6 +51,7 @@ class CallbackBrokerWorker(ConsumerMixin):
|
|||||||
logger.error('Callback Task Processor Raised Exception: %r', exc)
|
logger.error('Callback Task Processor Raised Exception: %r', exc)
|
||||||
message.ack()
|
message.ack()
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
Save Job Callback receiver (see awx.plugins.callbacks.job_event_callback)
|
Save Job Callback receiver (see awx.plugins.callbacks.job_event_callback)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from awx.main.socket_queue import Socket
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.commands.run_fact_cache_receiver')
|
logger = logging.getLogger('awx.main.commands.run_fact_cache_receiver')
|
||||||
|
|
||||||
|
|
||||||
class FactCacheReceiver(object):
|
class FactCacheReceiver(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.timestamp = None
|
self.timestamp = None
|
||||||
@@ -91,6 +92,7 @@ class FactCacheReceiver(object):
|
|||||||
else:
|
else:
|
||||||
self.process_fact_message(message)
|
self.process_fact_message(message)
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
blah blah
|
blah blah
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from socketio.namespace import BaseNamespace
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.commands.run_socketio_service')
|
logger = logging.getLogger('awx.main.commands.run_socketio_service')
|
||||||
|
|
||||||
|
|
||||||
class SocketSession(object):
|
class SocketSession(object):
|
||||||
def __init__(self, session_id, token_key, socket):
|
def __init__(self, session_id, token_key, socket):
|
||||||
self.socket = weakref.ref(socket)
|
self.socket = weakref.ref(socket)
|
||||||
@@ -45,8 +46,8 @@ class SocketSession(object):
|
|||||||
auth_token = auth_token[0]
|
auth_token = auth_token[0]
|
||||||
return bool(not auth_token.is_expired())
|
return bool(not auth_token.is_expired())
|
||||||
|
|
||||||
class SocketSessionManager(object):
|
|
||||||
|
|
||||||
|
class SocketSessionManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.SESSIONS_MAX = 1000
|
self.SESSIONS_MAX = 1000
|
||||||
self.socket_sessions = []
|
self.socket_sessions = []
|
||||||
@@ -79,8 +80,8 @@ class SocketSessionManager(object):
|
|||||||
self._prune()
|
self._prune()
|
||||||
return session
|
return session
|
||||||
|
|
||||||
class SocketController(object):
|
|
||||||
|
|
||||||
|
class SocketController(object):
|
||||||
def __init__(self, SocketSessionManager):
|
def __init__(self, SocketSessionManager):
|
||||||
self.server = None
|
self.server = None
|
||||||
self.SocketSessionManager = SocketSessionManager
|
self.SocketSessionManager = SocketSessionManager
|
||||||
@@ -124,14 +125,15 @@ class SocketController(object):
|
|||||||
self.server = server
|
self.server = server
|
||||||
return server
|
return server
|
||||||
|
|
||||||
|
|
||||||
socketController = SocketController(SocketSessionManager())
|
socketController = SocketController(SocketSessionManager())
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Socket session is attached to self.session['socket_session']
|
# Socket session is attached to self.session['socket_session']
|
||||||
# self.session and self.socket.session point to the same dict
|
# self.session and self.socket.session point to the same dict
|
||||||
#
|
#
|
||||||
class TowerBaseNamespace(BaseNamespace):
|
class TowerBaseNamespace(BaseNamespace):
|
||||||
|
|
||||||
def get_allowed_methods(self):
|
def get_allowed_methods(self):
|
||||||
return ['recv_disconnect']
|
return ['recv_disconnect']
|
||||||
|
|
||||||
@@ -178,33 +180,33 @@ class TowerBaseNamespace(BaseNamespace):
|
|||||||
if socket_session and not socket_session.is_valid():
|
if socket_session and not socket_session.is_valid():
|
||||||
self.disconnect(silent=False)
|
self.disconnect(silent=False)
|
||||||
|
|
||||||
class TestNamespace(TowerBaseNamespace):
|
|
||||||
|
|
||||||
|
class TestNamespace(TowerBaseNamespace):
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
logger.info("Received client connect for test namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for test namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
self.emit('test', "If you see this then you attempted to connect to the test socket endpoint")
|
self.emit('test', "If you see this then you attempted to connect to the test socket endpoint")
|
||||||
super(TestNamespace, self).recv_connect()
|
super(TestNamespace, self).recv_connect()
|
||||||
|
|
||||||
class JobNamespace(TowerBaseNamespace):
|
|
||||||
|
|
||||||
|
class JobNamespace(TowerBaseNamespace):
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
logger.info("Received client connect for job namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for job namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(JobNamespace, self).recv_connect()
|
super(JobNamespace, self).recv_connect()
|
||||||
|
|
||||||
class JobEventNamespace(TowerBaseNamespace):
|
|
||||||
|
|
||||||
|
class JobEventNamespace(TowerBaseNamespace):
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
logger.info("Received client connect for job event namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for job event namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(JobEventNamespace, self).recv_connect()
|
super(JobEventNamespace, self).recv_connect()
|
||||||
|
|
||||||
class AdHocCommandEventNamespace(TowerBaseNamespace):
|
|
||||||
|
|
||||||
|
class AdHocCommandEventNamespace(TowerBaseNamespace):
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
logger.info("Received client connect for ad hoc command event namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for ad hoc command event namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(AdHocCommandEventNamespace, self).recv_connect()
|
super(AdHocCommandEventNamespace, self).recv_connect()
|
||||||
|
|
||||||
class ScheduleNamespace(TowerBaseNamespace):
|
|
||||||
|
|
||||||
|
class ScheduleNamespace(TowerBaseNamespace):
|
||||||
def get_allowed_methods(self):
|
def get_allowed_methods(self):
|
||||||
parent_allowed = super(ScheduleNamespace, self).get_allowed_methods()
|
parent_allowed = super(ScheduleNamespace, self).get_allowed_methods()
|
||||||
return parent_allowed + ["schedule_changed"]
|
return parent_allowed + ["schedule_changed"]
|
||||||
@@ -213,16 +215,16 @@ class ScheduleNamespace(TowerBaseNamespace):
|
|||||||
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(ScheduleNamespace, self).recv_connect()
|
super(ScheduleNamespace, self).recv_connect()
|
||||||
|
|
||||||
|
|
||||||
# Catch-all namespace.
|
# Catch-all namespace.
|
||||||
# Deliver 'global' events over this namespace
|
# Deliver 'global' events over this namespace
|
||||||
class ControlNamespace(TowerBaseNamespace):
|
class ControlNamespace(TowerBaseNamespace):
|
||||||
|
|
||||||
def recv_connect(self):
|
def recv_connect(self):
|
||||||
logger.warn("Received client connect for control namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
logger.warn("Received client connect for control namespace from %s" % str(self.environ['REMOTE_ADDR']))
|
||||||
super(ControlNamespace, self).recv_connect()
|
super(ControlNamespace, self).recv_connect()
|
||||||
|
|
||||||
class TowerSocket(object):
|
|
||||||
|
|
||||||
|
class TowerSocket(object):
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
path = environ['PATH_INFO'].strip('/') or 'index.html'
|
path = environ['PATH_INFO'].strip('/') or 'index.html'
|
||||||
if path.startswith('socket.io'):
|
if path.startswith('socket.io'):
|
||||||
@@ -237,6 +239,7 @@ class TowerSocket(object):
|
|||||||
start_response('404 Not Found', [])
|
start_response('404 Not Found', [])
|
||||||
return ['Tower version %s' % awx.__version__]
|
return ['Tower version %s' % awx.__version__]
|
||||||
|
|
||||||
|
|
||||||
def notification_handler(server):
|
def notification_handler(server):
|
||||||
with Socket('websocket', 'r') as websocket:
|
with Socket('websocket', 'r') as websocket:
|
||||||
for message in websocket.listen():
|
for message in websocket.listen():
|
||||||
@@ -253,6 +256,7 @@ def notification_handler(server):
|
|||||||
else:
|
else:
|
||||||
socketController.broadcast_packet(packet)
|
socketController.broadcast_packet(packet)
|
||||||
|
|
||||||
|
|
||||||
class Command(NoArgsCommand):
|
class Command(NoArgsCommand):
|
||||||
'''
|
'''
|
||||||
SocketIO event emitter Tower service
|
SocketIO event emitter Tower service
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.core.management.base import BaseCommand
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import * # noqa
|
from awx.main.models import * # noqa
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
'''
|
'''
|
||||||
Emits some simple statistics suitable for external monitoring
|
Emits some simple statistics suitable for external monitoring
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from django.core.management.base import BaseCommand
|
|||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class UpdatePassword(object):
|
class UpdatePassword(object):
|
||||||
def update_password(self, username, password):
|
def update_password(self, username, password):
|
||||||
changed = False
|
changed = False
|
||||||
@@ -22,6 +23,7 @@ class UpdatePassword(object):
|
|||||||
changed = True
|
changed = True
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
make_option('--username', dest='username', action='store', type='string', default=None,
|
make_option('--username', dest='username', action='store', type='string', default=None,
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ EXPERIMENT_DEFAULT = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Experiment(object):
|
class Experiment(object):
|
||||||
def __init__(self, exp, fact_fixtures, raw_db, mongoengine_db):
|
def __init__(self, exp, fact_fixtures, raw_db, mongoengine_db):
|
||||||
self.db = raw_db
|
self.db = raw_db
|
||||||
@@ -291,6 +292,7 @@ class Experiment(object):
|
|||||||
print("Finished at: %s" % time_end)
|
print("Finished at: %s" % time_end)
|
||||||
print("Total runtime: %s seconds" % timedelta_total_seconds(time_end - time_start))
|
print("Total runtime: %s seconds" % timedelta_total_seconds(time_end - time_start))
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
option_list = BaseCommand.option_list + (
|
option_list = BaseCommand.option_list + (
|
||||||
make_option('--drop', dest='drop', action='store_true', default=False,
|
make_option('--drop', dest='drop', action='store_true', default=False,
|
||||||
@@ -334,4 +336,3 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
self.experiment = Experiment(exp, FACT_FIXTURES, db, enginedb)
|
self.experiment = Experiment(exp, FACT_FIXTURES, db, enginedb)
|
||||||
self.experiment.generate_workload()
|
self.experiment.generate_workload()
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class HostManager(models.Manager):
|
|||||||
except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name')
|
except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name')
|
||||||
return len(set(self.values_list('name', flat=True)))
|
return len(set(self.values_list('name', flat=True)))
|
||||||
|
|
||||||
|
|
||||||
class InstanceManager(models.Manager):
|
class InstanceManager(models.Manager):
|
||||||
"""A custom manager class for the Instance model.
|
"""A custom manager class for the Instance model.
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class ActivityStreamMiddleware(threading.local):
|
|||||||
if instance.id not in self.instance_ids:
|
if instance.id not in self.instance_ids:
|
||||||
self.instance_ids.append(instance.id)
|
self.instance_ids.append(instance.id)
|
||||||
|
|
||||||
|
|
||||||
class AuthTokenTimeoutMiddleware(object):
|
class AuthTokenTimeoutMiddleware(object):
|
||||||
"""Presume that when the user includes the auth header, they go through the
|
"""Presume that when the user includes the auth header, they go through the
|
||||||
authentication mechanism. Further, that mechanism is presumed to extend
|
authentication mechanism. Further, that mechanism is presumed to extend
|
||||||
|
|||||||
90
awx/main/migrations/0050_v310_JSONField_changes.py
Normal file
90
awx/main/migrations/0050_v310_JSONField_changes.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0049_v310_workflow_surveys'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='adhoccommandevent',
|
||||||
|
name='event_data',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='job',
|
||||||
|
name='artifacts',
|
||||||
|
field=awx.main.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='job',
|
||||||
|
name='survey_passwords',
|
||||||
|
field=awx.main.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobevent',
|
||||||
|
name='event_data',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobtemplate',
|
||||||
|
name='survey_spec',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notification',
|
||||||
|
name='body',
|
||||||
|
field=awx.main.fields.JSONField(default=dict, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='notificationtemplate',
|
||||||
|
name='notification_configuration',
|
||||||
|
field=awx.main.fields.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='project',
|
||||||
|
name='playbook_files',
|
||||||
|
field=awx.main.fields.JSONField(default=[], help_text='List of playbooks found in the project', verbose_name='Playbook Files', editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='schedule',
|
||||||
|
name='extra_data',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unifiedjob',
|
||||||
|
name='job_env',
|
||||||
|
field=awx.main.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workflowjob',
|
||||||
|
name='survey_passwords',
|
||||||
|
field=awx.main.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workflowjobnode',
|
||||||
|
name='ancestor_artifacts',
|
||||||
|
field=awx.main.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workflowjobnode',
|
||||||
|
name='char_prompts',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workflowjobtemplate',
|
||||||
|
name='survey_spec',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='workflowjobtemplatenode',
|
||||||
|
name='char_prompts',
|
||||||
|
field=awx.main.fields.JSONField(default={}, blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -28,14 +28,19 @@ from awx.main.models.channels import * # noqa
|
|||||||
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
|
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
|
||||||
from django.core.serializers.python import Serializer as _PythonSerializer
|
from django.core.serializers.python import Serializer as _PythonSerializer
|
||||||
_original_handle_m2m_field = _PythonSerializer.handle_m2m_field
|
_original_handle_m2m_field = _PythonSerializer.handle_m2m_field
|
||||||
|
|
||||||
|
|
||||||
def _new_handle_m2m_field(self, obj, field):
|
def _new_handle_m2m_field(self, obj, field):
|
||||||
try:
|
try:
|
||||||
field.rel.through._meta
|
field.rel.through._meta
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
return _original_handle_m2m_field(self, obj, field)
|
return _original_handle_m2m_field(self, obj, field)
|
||||||
|
|
||||||
|
|
||||||
_PythonSerializer.handle_m2m_field = _new_handle_m2m_field
|
_PythonSerializer.handle_m2m_field = _new_handle_m2m_field
|
||||||
|
|
||||||
|
|
||||||
# Add custom methods to User model for permissions checks.
|
# Add custom methods to User model for permissions checks.
|
||||||
from django.contrib.auth.models import User # noqa
|
from django.contrib.auth.models import User # noqa
|
||||||
from awx.main.access import * # noqa
|
from awx.main.access import * # noqa
|
||||||
@@ -46,26 +51,32 @@ User.add_to_class('can_access', check_user_access)
|
|||||||
User.add_to_class('accessible_objects', user_accessible_objects)
|
User.add_to_class('accessible_objects', user_accessible_objects)
|
||||||
User.add_to_class('admin_role', user_admin_role)
|
User.add_to_class('admin_role', user_admin_role)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_get_organizations(user):
|
def user_get_organizations(user):
|
||||||
return Organization.objects.filter(member_role__members=user)
|
return Organization.objects.filter(member_role__members=user)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_get_admin_of_organizations(user):
|
def user_get_admin_of_organizations(user):
|
||||||
return Organization.objects.filter(admin_role__members=user)
|
return Organization.objects.filter(admin_role__members=user)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_get_auditor_of_organizations(user):
|
def user_get_auditor_of_organizations(user):
|
||||||
return Organization.objects.filter(auditor_role__members=user)
|
return Organization.objects.filter(auditor_role__members=user)
|
||||||
|
|
||||||
|
|
||||||
User.add_to_class('organizations', user_get_organizations)
|
User.add_to_class('organizations', user_get_organizations)
|
||||||
User.add_to_class('admin_of_organizations', user_get_admin_of_organizations)
|
User.add_to_class('admin_of_organizations', user_get_admin_of_organizations)
|
||||||
User.add_to_class('auditor_of_organizations', user_get_auditor_of_organizations)
|
User.add_to_class('auditor_of_organizations', user_get_auditor_of_organizations)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_is_system_auditor(user):
|
def user_is_system_auditor(user):
|
||||||
return Role.singleton('system_auditor').members.filter(id=user.id).exists()
|
return Role.singleton('system_auditor').members.filter(id=user.id).exists()
|
||||||
|
|
||||||
|
|
||||||
@user_is_system_auditor.setter
|
@user_is_system_auditor.setter
|
||||||
def user_is_system_auditor(user, tf):
|
def user_is_system_auditor(user, tf):
|
||||||
if user.id:
|
if user.id:
|
||||||
@@ -74,6 +85,7 @@ def user_is_system_auditor(user, tf):
|
|||||||
else:
|
else:
|
||||||
Role.singleton('system_auditor').members.remove(user)
|
Role.singleton('system_auditor').members.remove(user)
|
||||||
|
|
||||||
|
|
||||||
User.add_to_class('is_system_auditor', user_is_system_auditor)
|
User.add_to_class('is_system_auditor', user_is_system_auditor)
|
||||||
|
|
||||||
# Import signal handlers only after models have been defined.
|
# Import signal handlers only after models have been defined.
|
||||||
|
|||||||
@@ -17,13 +17,11 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.unified_jobs import * # noqa
|
from awx.main.models.unified_jobs import * # noqa
|
||||||
from awx.main.models.notifications import JobNotificationMixin
|
from awx.main.models.notifications import JobNotificationMixin
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
|
logger = logging.getLogger('awx.main.models.ad_hoc_commands')
|
||||||
|
|
||||||
@@ -211,6 +209,7 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
|
|||||||
def get_notification_friendly_name(self):
|
def get_notification_friendly_name(self):
|
||||||
return "AdHoc Command"
|
return "AdHoc Command"
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandEvent(CreatedModifiedModel):
|
class AdHocCommandEvent(CreatedModifiedModel):
|
||||||
'''
|
'''
|
||||||
An event/message logged from the ad hoc event callback for each host.
|
An event/message logged from the ad hoc event callback for each host.
|
||||||
|
|||||||
@@ -320,6 +320,7 @@ class CommonModelNameNotUnique(PrimordialModel):
|
|||||||
unique=False,
|
unique=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class NotificationFieldsModel(BaseModel):
|
class NotificationFieldsModel(BaseModel):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class ChannelGroup(models.Model):
|
class ChannelGroup(models.Model):
|
||||||
group = models.CharField(max_length=200, unique=True)
|
group = models.CharField(max_length=200, unique=True)
|
||||||
channels = models.TextField()
|
channels = models.TextField()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from jsonbfield.fields import JSONField
|
|||||||
|
|
||||||
__all__ = ('Fact', )
|
__all__ = ('Fact', )
|
||||||
|
|
||||||
|
|
||||||
class Fact(models.Model):
|
class Fact(models.Model):
|
||||||
"""A model representing a fact returned from Ansible.
|
"""A model representing a fact returned from Ansible.
|
||||||
Facts are stored as JSON dictionaries.
|
Facts are stored as JSON dictionaries.
|
||||||
|
|||||||
@@ -39,9 +39,11 @@ class Instance(models.Model):
|
|||||||
# NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing
|
# NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing
|
||||||
return "tower"
|
return "tower"
|
||||||
|
|
||||||
|
|
||||||
class TowerScheduleState(SingletonModel):
|
class TowerScheduleState(SingletonModel):
|
||||||
schedule_last_run = models.DateTimeField(auto_now_add=True)
|
schedule_last_run = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
|
||||||
class JobOrigin(models.Model):
|
class JobOrigin(models.Model):
|
||||||
"""A model representing the relationship between a unified job and
|
"""A model representing the relationship between a unified job and
|
||||||
the instance that was responsible for starting that job.
|
the instance that was responsible for starting that job.
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.constants import CLOUD_PROVIDERS
|
from awx.main.constants import CLOUD_PROVIDERS
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
@@ -40,6 +37,7 @@ from awx.main.redact import PlainTextCleaner
|
|||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin
|
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin
|
||||||
from awx.main.models.base import PERM_INVENTORY_SCAN
|
from awx.main.models.base import PERM_INVENTORY_SCAN
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
|
||||||
@@ -190,6 +188,7 @@ class JobOptions(BaseModel):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, ResourceMixin):
|
class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
A job template is a reusable job definition for applying a project (with
|
A job template is a reusable job definition for applying a project (with
|
||||||
@@ -394,6 +393,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
|
|||||||
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(organization_notification_templates_for_any=self.project.organization)))
|
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(organization_notification_templates_for_any=self.project.organization)))
|
||||||
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
|
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
|
||||||
|
|
||||||
|
|
||||||
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
|
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
A job applies a project (with playbook) to an inventory source with a given
|
A job applies a project (with playbook) to an inventory source with a given
|
||||||
@@ -648,6 +648,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
|
|||||||
def get_notification_friendly_name(self):
|
def get_notification_friendly_name(self):
|
||||||
return "Job"
|
return "Job"
|
||||||
|
|
||||||
|
|
||||||
class JobHostSummary(CreatedModifiedModel):
|
class JobHostSummary(CreatedModifiedModel):
|
||||||
'''
|
'''
|
||||||
Per-host statistics for each job.
|
Per-host statistics for each job.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob
|
|||||||
|
|
||||||
__all__ = ('Label', )
|
__all__ = ('Label', )
|
||||||
|
|
||||||
|
|
||||||
class Label(CommonModelNameNotUnique):
|
class Label(CommonModelNameNotUnique):
|
||||||
'''
|
'''
|
||||||
Generic Tag. Designed for tagging Job Templates, but expandable to other models.
|
Generic Tag. Designed for tagging Job Templates, but expandable to other models.
|
||||||
@@ -37,7 +38,7 @@ class Label(CommonModelNameNotUnique):
|
|||||||
return \
|
return \
|
||||||
Label.objects.filter(
|
Label.objects.filter(
|
||||||
organization=None,
|
organization=None,
|
||||||
jobtemplate_labels__isnull=True
|
unifiedjobtemplate_labels__isnull=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_detached(self):
|
def is_detached(self):
|
||||||
@@ -55,4 +56,3 @@ class Label(CommonModelNameNotUnique):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,18 @@ import json
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth.models import User # noqa
|
from django.contrib.auth.models import User # noqa
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.rbac import (
|
from awx.main.models.rbac import (
|
||||||
Role, RoleAncestorEntry, get_roles_on_resource
|
Role, RoleAncestorEntry, get_roles_on_resource
|
||||||
)
|
)
|
||||||
from awx.main.utils import parse_yaml_or_json
|
from awx.main.utils import parse_yaml_or_json
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin']
|
__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin']
|
||||||
|
|
||||||
|
|
||||||
class ResourceMixin(models.Model):
|
class ResourceMixin(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -216,4 +217,3 @@ class SurveyJobMixin(models.Model):
|
|||||||
return json.dumps(extra_vars)
|
return json.dumps(extra_vars)
|
||||||
else:
|
else:
|
||||||
return self.extra_vars
|
return self.extra_vars
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ from awx.main.notifications.pagerduty_backend import PagerDutyBackend
|
|||||||
from awx.main.notifications.hipchat_backend import HipChatBackend
|
from awx.main.notifications.hipchat_backend import HipChatBackend
|
||||||
from awx.main.notifications.webhook_backend import WebhookBackend
|
from awx.main.notifications.webhook_backend import WebhookBackend
|
||||||
from awx.main.notifications.irc_backend import IrcBackend
|
from awx.main.notifications.irc_backend import IrcBackend
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.notifications')
|
logger = logging.getLogger('awx.main.models.notifications')
|
||||||
|
|
||||||
__all__ = ['NotificationTemplate', 'Notification']
|
__all__ = ['NotificationTemplate', 'Notification']
|
||||||
|
|
||||||
|
|
||||||
class NotificationTemplate(CommonModel):
|
class NotificationTemplate(CommonModel):
|
||||||
|
|
||||||
NOTIFICATION_TYPES = [('email', _('Email'), CustomEmailBackend),
|
NOTIFICATION_TYPES = [('email', _('Email'), CustomEmailBackend),
|
||||||
@@ -117,6 +117,7 @@ class NotificationTemplate(CommonModel):
|
|||||||
notification_obj = EmailMessage(subject, backend_obj.format_body(body), sender, recipients)
|
notification_obj = EmailMessage(subject, backend_obj.format_body(body), sender, recipients)
|
||||||
return backend_obj.send_messages([notification_obj])
|
return backend_obj.send_messages([notification_obj])
|
||||||
|
|
||||||
|
|
||||||
class Notification(CreatedModifiedModel):
|
class Notification(CreatedModifiedModel):
|
||||||
'''
|
'''
|
||||||
A notification event emitted when a NotificationTemplate is run
|
A notification event emitted when a NotificationTemplate is run
|
||||||
@@ -172,6 +173,7 @@ class Notification(CreatedModifiedModel):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:notification_detail', args=(self.pk,))
|
return reverse('api:notification_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class JobNotificationMixin(object):
|
class JobNotificationMixin(object):
|
||||||
def get_notification_templates(self):
|
def get_notification_templates(self):
|
||||||
raise RuntimeError("Define me")
|
raise RuntimeError("Define me")
|
||||||
@@ -194,4 +196,3 @@ class JobNotificationMixin(object):
|
|||||||
|
|
||||||
def build_notification_failed_message(self):
|
def build_notification_failed_message(self):
|
||||||
return self._build_notification_message('failed')
|
return self._build_notification_message('failed')
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Team(CommonModelNameNotUnique, ResourceMixin):
|
class Team(CommonModelNameNotUnique, ResourceMixin):
|
||||||
'''
|
'''
|
||||||
A team is a group of users that work on common projects.
|
A team is a group of users that work on common projects.
|
||||||
@@ -191,6 +190,7 @@ class Profile(CreatedModifiedModel):
|
|||||||
default='',
|
default='',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Since expiration and session expiration is event driven a token could be
|
Since expiration and session expiration is event driven a token could be
|
||||||
invalidated for both reasons. Further, we only support a single reason for a
|
invalidated for both reasons. Further, we only support a single reason for a
|
||||||
@@ -199,6 +199,8 @@ session token being invalid. For this case, mark the token as expired.
|
|||||||
Note: Again, because the value of reason is event based. The reason may not be
|
Note: Again, because the value of reason is event based. The reason may not be
|
||||||
set (i.e. may equal '') even though a session is expired or a limit is reached.
|
set (i.e. may equal '') even though a session is expired or a limit is reached.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AuthToken(BaseModel):
|
class AuthToken(BaseModel):
|
||||||
'''
|
'''
|
||||||
Custom authentication tokens per user with expiration and request-specific
|
Custom authentication tokens per user with expiration and request-specific
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
# JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
@@ -34,6 +31,7 @@ from awx.main.models.rbac import (
|
|||||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||||
ROLE_SINGLETON_SYSTEM_AUDITOR,
|
ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||||
)
|
)
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
__all__ = ['Project', 'ProjectUpdate']
|
__all__ = ['Project', 'ProjectUpdate']
|
||||||
|
|
||||||
@@ -393,6 +391,7 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('api:project_detail', args=(self.pk,))
|
return reverse('api:project_detail', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
|
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
|
||||||
'''
|
'''
|
||||||
Internal job for tracking project updates from SCM.
|
Internal job for tracking project updates from SCM.
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ def check_singleton(func):
|
|||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def batch_role_ancestor_rebuilding(allow_nesting=False):
|
def batch_role_ancestor_rebuilding(allow_nesting=False):
|
||||||
'''
|
'''
|
||||||
@@ -426,6 +427,7 @@ class Role(models.Model):
|
|||||||
def is_ancestor_of(self, role):
|
def is_ancestor_of(self, role):
|
||||||
return role.ancestors.filter(id=self.id).exists()
|
return role.ancestors.filter(id=self.id).exists()
|
||||||
|
|
||||||
|
|
||||||
class RoleAncestorEntry(models.Model):
|
class RoleAncestorEntry(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ from django.db import models
|
|||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.utils.timezone import now, make_aware, get_default_timezone
|
from django.utils.timezone import now, make_aware, get_default_timezone
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.utils import ignore_inventory_computed_fields
|
from awx.main.utils import ignore_inventory_computed_fields
|
||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.schedule')
|
logger = logging.getLogger('awx.main.models.schedule')
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ from django.utils.timezone import now
|
|||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
# Django-JSONField
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# Django-Polymorphic
|
# Django-Polymorphic
|
||||||
from polymorphic import PolymorphicModel
|
from polymorphic import PolymorphicModel
|
||||||
|
|
||||||
@@ -35,6 +32,7 @@ from awx.main.models.schedules import Schedule
|
|||||||
from awx.main.utils import decrypt_field, _inventory_updates
|
from awx.main.utils import decrypt_field, _inventory_updates
|
||||||
from awx.main.redact import UriCleaner, REPLACE_STR
|
from awx.main.redact import UriCleaner, REPLACE_STR
|
||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
||||||
|
|
||||||
@@ -358,6 +356,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
|||||||
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
|
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
|
||||||
return unified_job
|
return unified_job
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJobTypeStringMixin(object):
|
class UnifiedJobTypeStringMixin(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _underscore_to_camel(cls, word):
|
def _underscore_to_camel(cls, word):
|
||||||
@@ -381,6 +380,7 @@ class UnifiedJobTypeStringMixin(object):
|
|||||||
return None
|
return None
|
||||||
return model.objects.get(id=job_id)
|
return model.objects.get(id=job_id)
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin):
|
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin):
|
||||||
'''
|
'''
|
||||||
Concrete base class for unified job run by the task engine.
|
Concrete base class for unified job run by the task engine.
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ from django.db import models
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
#from django import settings as tower_settings
|
#from django import settings as tower_settings
|
||||||
|
|
||||||
from jsonfield import JSONField
|
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import UnifiedJobTemplate, UnifiedJob
|
from awx.main.models import UnifiedJobTemplate, UnifiedJob
|
||||||
from awx.main.models.notifications import (
|
from awx.main.models.notifications import (
|
||||||
@@ -26,6 +24,7 @@ from awx.main.fields import ImplicitRoleField
|
|||||||
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin
|
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin
|
||||||
from awx.main.redact import REPLACE_STR
|
from awx.main.redact import REPLACE_STR
|
||||||
from awx.main.utils import parse_yaml_or_json
|
from awx.main.utils import parse_yaml_or_json
|
||||||
|
from awx.main.fields import JSONField
|
||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ __all__ = ['WorkflowJobTemplate', 'WorkflowJob', 'WorkflowJobOptions', 'Workflow
|
|||||||
|
|
||||||
CHAR_PROMPTS_LIST = ['job_type', 'job_tags', 'skip_tags', 'limit']
|
CHAR_PROMPTS_LIST = ['job_type', 'job_tags', 'skip_tags', 'limit']
|
||||||
|
|
||||||
|
|
||||||
class WorkflowNodeBase(CreatedModifiedModel):
|
class WorkflowNodeBase(CreatedModifiedModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@@ -159,6 +159,7 @@ class WorkflowNodeBase(CreatedModifiedModel):
|
|||||||
return ['workflow_job', 'unified_job_template',
|
return ['workflow_job', 'unified_job_template',
|
||||||
'inventory', 'credential', 'char_prompts']
|
'inventory', 'credential', 'char_prompts']
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNode(WorkflowNodeBase):
|
class WorkflowJobTemplateNode(WorkflowNodeBase):
|
||||||
workflow_job_template = models.ForeignKey(
|
workflow_job_template = models.ForeignKey(
|
||||||
'WorkflowJobTemplate',
|
'WorkflowJobTemplate',
|
||||||
@@ -184,6 +185,7 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
|
|||||||
create_kwargs[field_name] = getattr(self, field_name)
|
create_kwargs[field_name] = getattr(self, field_name)
|
||||||
return WorkflowJobNode.objects.create(**create_kwargs)
|
return WorkflowJobNode.objects.create(**create_kwargs)
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobNode(WorkflowNodeBase):
|
class WorkflowJobNode(WorkflowNodeBase):
|
||||||
job = models.OneToOneField(
|
job = models.OneToOneField(
|
||||||
'UnifiedJob',
|
'UnifiedJob',
|
||||||
@@ -260,6 +262,7 @@ class WorkflowJobNode(WorkflowNodeBase):
|
|||||||
data['launch_type'] = 'workflow'
|
data['launch_type'] = 'workflow'
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobOptions(BaseModel):
|
class WorkflowJobOptions(BaseModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@@ -271,8 +274,8 @@ class WorkflowJobOptions(BaseModel):
|
|||||||
|
|
||||||
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
extra_vars_dict = VarsDictProperty('extra_vars', True)
|
||||||
|
|
||||||
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
|
|
||||||
|
|
||||||
|
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
|
|
||||||
@@ -374,6 +377,7 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
|
|||||||
warning_data[node.pk] = node_prompts_warnings
|
warning_data[node.pk] = node_prompts_warnings
|
||||||
return warning_data
|
return warning_data
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobInheritNodesMixin(object):
|
class WorkflowJobInheritNodesMixin(object):
|
||||||
def _inherit_relationship(self, old_node, new_node, node_ids_map, node_type):
|
def _inherit_relationship(self, old_node, new_node, node_ids_map, node_type):
|
||||||
old_related_nodes = self._get_all_by_type(old_node, node_type)
|
old_related_nodes = self._get_all_by_type(old_node, node_type)
|
||||||
@@ -418,7 +422,6 @@ class WorkflowJobInheritNodesMixin(object):
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin, WorkflowJobInheritNodesMixin):
|
class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin, WorkflowJobInheritNodesMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import urlparse
|
|||||||
|
|
||||||
REPLACE_STR = '$encrypted$'
|
REPLACE_STR = '$encrypted$'
|
||||||
|
|
||||||
|
|
||||||
class UriCleaner(object):
|
class UriCleaner(object):
|
||||||
REPLACE_STR = REPLACE_STR
|
REPLACE_STR = REPLACE_STR
|
||||||
# https://regex101.com/r/sV2dO2/2
|
# https://regex101.com/r/sV2dO2/2
|
||||||
@@ -51,6 +52,7 @@ class UriCleaner(object):
|
|||||||
|
|
||||||
return redactedtext
|
return redactedtext
|
||||||
|
|
||||||
|
|
||||||
class PlainTextCleaner(object):
|
class PlainTextCleaner(object):
|
||||||
REPLACE_STR = REPLACE_STR
|
REPLACE_STR = REPLACE_STR
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,5 @@ class ActivityStreamRegistrar(object):
|
|||||||
m2m_attr = getattr(model, m2mfield.name)
|
m2m_attr = getattr(model, m2mfield.name)
|
||||||
m2m_changed.disconnect(dispatch_uid=str(self.__class__) + str(m2m_attr.through) + "_associate")
|
m2m_changed.disconnect(dispatch_uid=str(self.__class__) + str(m2m_attr.through) + "_associate")
|
||||||
|
|
||||||
|
|
||||||
activity_stream_registrar = ActivityStreamRegistrar()
|
activity_stream_registrar = ActivityStreamRegistrar()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from celery.task.control import inspect
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.scheduler')
|
logger = logging.getLogger('awx.main.scheduler')
|
||||||
|
|
||||||
|
|
||||||
class TaskManager():
|
class TaskManager():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.graph = DependencyGraph()
|
self.graph = DependencyGraph()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from awx.main.models import (
|
|||||||
SystemJob,
|
SystemJob,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SimpleDAG(object):
|
class SimpleDAG(object):
|
||||||
''' A simple implementation of a directed acyclic graph '''
|
''' A simple implementation of a directed acyclic graph '''
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.scheduler.dag_simple import SimpleDAG
|
from awx.main.scheduler.dag_simple import SimpleDAG
|
||||||
|
|
||||||
|
|
||||||
class WorkflowDAG(SimpleDAG):
|
class WorkflowDAG(SimpleDAG):
|
||||||
def __init__(self, workflow_job=None):
|
def __init__(self, workflow_job=None):
|
||||||
super(WorkflowDAG, self).__init__()
|
super(WorkflowDAG, self).__init__()
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from awx.main.scheduler.partial import (
|
|||||||
AdHocCommandDict,
|
AdHocCommandDict,
|
||||||
WorkflowJobDict,
|
WorkflowJobDict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DependencyGraph(object):
|
class DependencyGraph(object):
|
||||||
PROJECT_UPDATES = 'project_updates'
|
PROJECT_UPDATES = 'project_updates'
|
||||||
INVENTORY_UPDATES = 'inventory_updates'
|
INVENTORY_UPDATES = 'inventory_updates'
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from awx.main.models import (
|
|||||||
WorkflowJob,
|
WorkflowJob,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PartialModelDict(object):
|
class PartialModelDict(object):
|
||||||
FIELDS = ()
|
FIELDS = ()
|
||||||
model = None
|
model = None
|
||||||
@@ -60,6 +61,7 @@ class PartialModelDict(object):
|
|||||||
def task_impact(self):
|
def task_impact(self):
|
||||||
raise RuntimeError("Inherit and implement me")
|
raise RuntimeError("Inherit and implement me")
|
||||||
|
|
||||||
|
|
||||||
class JobDict(PartialModelDict):
|
class JobDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'status', 'job_template_id', 'inventory_id', 'project_id',
|
'id', 'status', 'job_template_id', 'inventory_id', 'project_id',
|
||||||
@@ -83,6 +85,7 @@ class JobDict(PartialModelDict):
|
|||||||
start_args = start_args or {}
|
start_args = start_args or {}
|
||||||
return start_args.get('inventory_sources_already_updated', [])
|
return start_args.get('inventory_sources_already_updated', [])
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdateDict(PartialModelDict):
|
class ProjectUpdateDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'status', 'project_id', 'created', 'celery_task_id',
|
'id', 'status', 'project_id', 'created', 'celery_task_id',
|
||||||
@@ -105,6 +108,7 @@ class ProjectUpdateDict(PartialModelDict):
|
|||||||
}
|
}
|
||||||
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
||||||
|
|
||||||
|
|
||||||
class ProjectUpdateLatestDict(ProjectUpdateDict):
|
class ProjectUpdateLatestDict(ProjectUpdateDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'status', 'project_id', 'created', 'finished',
|
'id', 'status', 'project_id', 'created', 'finished',
|
||||||
@@ -125,6 +129,7 @@ class ProjectUpdateLatestDict(ProjectUpdateDict):
|
|||||||
results.append(cls(cls.model.objects.filter(id=qs[0].id).values(*cls.get_db_values())[0]))
|
results.append(cls(cls.model.objects.filter(id=qs[0].id).values(*cls.get_db_values())[0]))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdateDict(PartialModelDict):
|
class InventoryUpdateDict(PartialModelDict):
|
||||||
#'inventory_source__update_on_launch',
|
#'inventory_source__update_on_launch',
|
||||||
#'inventory_source__update_cache_timeout',
|
#'inventory_source__update_cache_timeout',
|
||||||
@@ -139,6 +144,7 @@ class InventoryUpdateDict(PartialModelDict):
|
|||||||
def task_impact(self):
|
def task_impact(self):
|
||||||
return 20
|
return 20
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdateLatestDict(InventoryUpdateDict):
|
class InventoryUpdateLatestDict(InventoryUpdateDict):
|
||||||
#'inventory_source__update_on_launch',
|
#'inventory_source__update_on_launch',
|
||||||
#'inventory_source__update_cache_timeout',
|
#'inventory_source__update_cache_timeout',
|
||||||
@@ -166,6 +172,7 @@ class InventoryUpdateLatestDict(InventoryUpdateDict):
|
|||||||
results.append(cls(cls.model.objects.filter(id=qs[0].id).values(*cls.get_db_values())[0]))
|
results.append(cls(cls.model.objects.filter(id=qs[0].id).values(*cls.get_db_values())[0]))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
class InventorySourceDict(PartialModelDict):
|
class InventorySourceDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id',
|
'id',
|
||||||
@@ -187,6 +194,7 @@ class InventorySourceDict(PartialModelDict):
|
|||||||
}
|
}
|
||||||
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
||||||
|
|
||||||
|
|
||||||
class SystemJobDict(PartialModelDict):
|
class SystemJobDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'created', 'status',
|
'id', 'created', 'status',
|
||||||
@@ -206,6 +214,7 @@ class SystemJobDict(PartialModelDict):
|
|||||||
}
|
}
|
||||||
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
|
||||||
|
|
||||||
|
|
||||||
class AdHocCommandDict(PartialModelDict):
|
class AdHocCommandDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'created', 'status', 'inventory_id',
|
'id', 'created', 'status', 'inventory_id',
|
||||||
@@ -218,6 +227,7 @@ class AdHocCommandDict(PartialModelDict):
|
|||||||
def task_impact(self):
|
def task_impact(self):
|
||||||
return 20
|
return 20
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobDict(PartialModelDict):
|
class WorkflowJobDict(PartialModelDict):
|
||||||
FIELDS = (
|
FIELDS = (
|
||||||
'id', 'created', 'status', 'workflow_job_template_id',
|
'id', 'created', 'status', 'workflow_job_template_id',
|
||||||
|
|||||||
@@ -21,18 +21,22 @@ logger = logging.getLogger('awx.main.scheduler')
|
|||||||
# Would we need the request loop then? I think so. Even if we get the in-memory
|
# Would we need the request loop then? I think so. Even if we get the in-memory
|
||||||
# updated model, the call to schedule() may get stale data.
|
# updated model, the call to schedule() may get stale data.
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def run_job_launch(job_id):
|
def run_job_launch(job_id):
|
||||||
TaskManager().schedule()
|
TaskManager().schedule()
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def run_job_complete(job_id):
|
def run_job_complete(job_id):
|
||||||
TaskManager().schedule()
|
TaskManager().schedule()
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def run_task_manager():
|
def run_task_manager():
|
||||||
TaskManager().schedule()
|
TaskManager().schedule()
|
||||||
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def run_fail_inconsistent_running_jobs():
|
def run_fail_inconsistent_running_jobs():
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ logger = logging.getLogger('awx.main.signals')
|
|||||||
# Update has_active_failures for inventory/groups when a Host/Group is deleted,
|
# Update has_active_failures for inventory/groups when a Host/Group is deleted,
|
||||||
# when a Host-Group or Group-Group relationship is updated, or when a Job is deleted
|
# when a Host-Group or Group-Group relationship is updated, or when a Job is deleted
|
||||||
|
|
||||||
|
|
||||||
def emit_job_event_detail(sender, **kwargs):
|
def emit_job_event_detail(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
created = kwargs['created']
|
created = kwargs['created']
|
||||||
@@ -48,6 +49,7 @@ def emit_job_event_detail(sender, **kwargs):
|
|||||||
event_serialized['group_name'] = "workflow_events"
|
event_serialized['group_name'] = "workflow_events"
|
||||||
emit_channel_notification('workflow_events-' + str(instance.job.workflow_job_id), event_serialized)
|
emit_channel_notification('workflow_events-' + str(instance.job.workflow_job_id), event_serialized)
|
||||||
|
|
||||||
|
|
||||||
def emit_ad_hoc_command_event_detail(sender, **kwargs):
|
def emit_ad_hoc_command_event_detail(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
created = kwargs['created']
|
created = kwargs['created']
|
||||||
@@ -60,6 +62,7 @@ def emit_ad_hoc_command_event_detail(sender, **kwargs):
|
|||||||
event_serialized["group_name"] = "ad_hoc_command_events"
|
event_serialized["group_name"] = "ad_hoc_command_events"
|
||||||
emit_channel_notification('ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized)
|
emit_channel_notification('ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized)
|
||||||
|
|
||||||
|
|
||||||
def emit_update_inventory_computed_fields(sender, **kwargs):
|
def emit_update_inventory_computed_fields(sender, **kwargs):
|
||||||
logger.debug("In update inventory computed fields")
|
logger.debug("In update inventory computed fields")
|
||||||
if getattr(_inventory_updates, 'is_updating', False):
|
if getattr(_inventory_updates, 'is_updating', False):
|
||||||
@@ -94,6 +97,7 @@ def emit_update_inventory_computed_fields(sender, **kwargs):
|
|||||||
else:
|
else:
|
||||||
update_inventory_computed_fields.delay(inventory.id, True)
|
update_inventory_computed_fields.delay(inventory.id, True)
|
||||||
|
|
||||||
|
|
||||||
def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
||||||
if getattr(_inventory_updates, 'is_updating', False):
|
if getattr(_inventory_updates, 'is_updating', False):
|
||||||
return
|
return
|
||||||
@@ -114,6 +118,7 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
|
|||||||
if inventory is not None:
|
if inventory is not None:
|
||||||
update_inventory_computed_fields.delay(inventory.id, True)
|
update_inventory_computed_fields.delay(inventory.id, True)
|
||||||
|
|
||||||
|
|
||||||
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 == 'post_add':
|
if action == 'post_add':
|
||||||
@@ -128,6 +133,7 @@ def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwarg
|
|||||||
else:
|
else:
|
||||||
model.rebuild_role_ancestor_list([], [instance.id])
|
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'
|
||||||
if instance.is_superuser:
|
if instance.is_superuser:
|
||||||
@@ -135,6 +141,7 @@ def sync_superuser_status_to_rbac(instance, **kwargs):
|
|||||||
else:
|
else:
|
||||||
Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance)
|
Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance)
|
||||||
|
|
||||||
|
|
||||||
def create_user_role(instance, **kwargs):
|
def create_user_role(instance, **kwargs):
|
||||||
try:
|
try:
|
||||||
Role.objects.get(
|
Role.objects.get(
|
||||||
@@ -149,6 +156,7 @@ def create_user_role(instance, **kwargs):
|
|||||||
)
|
)
|
||||||
role.members.add(instance)
|
role.members.add(instance)
|
||||||
|
|
||||||
|
|
||||||
def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
|
def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
|
||||||
content_type = ContentType.objects.get_for_model(Organization)
|
content_type = ContentType.objects.get_for_model(Organization)
|
||||||
|
|
||||||
@@ -164,6 +172,7 @@ 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):
|
def rbac_activity_stream(instance, sender, **kwargs):
|
||||||
user_type = ContentType.objects.get_for_model(User)
|
user_type = ContentType.objects.get_for_model(User)
|
||||||
# Only if we are associating/disassociating
|
# Only if we are associating/disassociating
|
||||||
@@ -198,11 +207,13 @@ def rbac_activity_stream(instance, sender, **kwargs):
|
|||||||
|
|
||||||
activity_stream_associate(sender, instance, role=role, **kwargs)
|
activity_stream_associate(sender, instance, role=role, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs):
|
def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs):
|
||||||
for l in instance.labels.all():
|
for l in instance.labels.all():
|
||||||
if l.is_candidate_for_detach():
|
if l.is_candidate_for_detach():
|
||||||
l.delete()
|
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)
|
||||||
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Group)
|
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Group)
|
||||||
@@ -228,6 +239,7 @@ pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJobT
|
|||||||
|
|
||||||
# Migrate hosts, groups to parent group(s) whenever a group is deleted
|
# Migrate hosts, groups to parent group(s) whenever a group is deleted
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Group)
|
@receiver(pre_delete, sender=Group)
|
||||||
def save_related_pks_before_group_delete(sender, **kwargs):
|
def save_related_pks_before_group_delete(sender, **kwargs):
|
||||||
if getattr(_inventory_updates, 'is_removing', False):
|
if getattr(_inventory_updates, 'is_removing', False):
|
||||||
@@ -238,6 +250,7 @@ def save_related_pks_before_group_delete(sender, **kwargs):
|
|||||||
instance._saved_hosts_pks = set(instance.hosts.values_list('pk', flat=True))
|
instance._saved_hosts_pks = set(instance.hosts.values_list('pk', flat=True))
|
||||||
instance._saved_children_pks = set(instance.children.values_list('pk', flat=True))
|
instance._saved_children_pks = set(instance.children.values_list('pk', flat=True))
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Group)
|
@receiver(post_delete, sender=Group)
|
||||||
def migrate_children_from_deleted_group_to_parent_groups(sender, **kwargs):
|
def migrate_children_from_deleted_group_to_parent_groups(sender, **kwargs):
|
||||||
if getattr(_inventory_updates, 'is_removing', False):
|
if getattr(_inventory_updates, 'is_removing', False):
|
||||||
@@ -271,6 +284,7 @@ def migrate_children_from_deleted_group_to_parent_groups(sender, **kwargs):
|
|||||||
|
|
||||||
# Update host pointers to last_job and last_job_host_summary when a job is deleted
|
# Update host pointers to last_job and last_job_host_summary when a job is deleted
|
||||||
|
|
||||||
|
|
||||||
def _update_host_last_jhs(host):
|
def _update_host_last_jhs(host):
|
||||||
jhs_qs = JobHostSummary.objects.filter(host__pk=host.pk)
|
jhs_qs = JobHostSummary.objects.filter(host__pk=host.pk)
|
||||||
try:
|
try:
|
||||||
@@ -288,12 +302,14 @@ def _update_host_last_jhs(host):
|
|||||||
if update_fields:
|
if update_fields:
|
||||||
host.save(update_fields=update_fields)
|
host.save(update_fields=update_fields)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Job)
|
@receiver(pre_delete, sender=Job)
|
||||||
def save_host_pks_before_job_delete(sender, **kwargs):
|
def save_host_pks_before_job_delete(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
hosts_qs = Host.objects.filter( last_job__pk=instance.pk)
|
hosts_qs = Host.objects.filter( last_job__pk=instance.pk)
|
||||||
instance._saved_hosts_pks = set(hosts_qs.values_list('pk', flat=True))
|
instance._saved_hosts_pks = set(hosts_qs.values_list('pk', flat=True))
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Job)
|
@receiver(post_delete, sender=Job)
|
||||||
def update_host_last_job_after_job_deleted(sender, **kwargs):
|
def update_host_last_job_after_job_deleted(sender, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
@@ -303,6 +319,7 @@ def update_host_last_job_after_job_deleted(sender, **kwargs):
|
|||||||
|
|
||||||
# Set via ActivityStreamRegistrar to record activity stream events
|
# Set via ActivityStreamRegistrar to record activity stream events
|
||||||
|
|
||||||
|
|
||||||
class ActivityStreamEnabled(threading.local):
|
class ActivityStreamEnabled(threading.local):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
@@ -310,8 +327,10 @@ class ActivityStreamEnabled(threading.local):
|
|||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.enabled and getattr(settings, 'ACTIVITY_STREAM_ENABLED', True))
|
return bool(self.enabled and getattr(settings, 'ACTIVITY_STREAM_ENABLED', True))
|
||||||
|
|
||||||
|
|
||||||
activity_stream_enabled = ActivityStreamEnabled()
|
activity_stream_enabled = ActivityStreamEnabled()
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def disable_activity_stream():
|
def disable_activity_stream():
|
||||||
'''
|
'''
|
||||||
@@ -342,6 +361,7 @@ model_serializer_mapping = {
|
|||||||
Notification: NotificationSerializer,
|
Notification: NotificationSerializer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def activity_stream_create(sender, instance, created, **kwargs):
|
def activity_stream_create(sender, instance, created, **kwargs):
|
||||||
if created and activity_stream_enabled:
|
if created and activity_stream_enabled:
|
||||||
# Skip recording any inventory source directly associated with a group.
|
# Skip recording any inventory source directly associated with a group.
|
||||||
@@ -364,6 +384,7 @@ def activity_stream_create(sender, instance, created, **kwargs):
|
|||||||
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
|
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
|
||||||
getattr(activity_entry, object1).add(instance)
|
getattr(activity_entry, object1).add(instance)
|
||||||
|
|
||||||
|
|
||||||
def activity_stream_update(sender, instance, **kwargs):
|
def activity_stream_update(sender, instance, **kwargs):
|
||||||
if instance.id is None:
|
if instance.id is None:
|
||||||
return
|
return
|
||||||
@@ -387,6 +408,7 @@ def activity_stream_update(sender, instance, **kwargs):
|
|||||||
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
|
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
|
||||||
getattr(activity_entry, object1).add(instance)
|
getattr(activity_entry, object1).add(instance)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
@@ -401,6 +423,7 @@ def activity_stream_delete(sender, instance, **kwargs):
|
|||||||
object1=object1)
|
object1=object1)
|
||||||
activity_entry.save()
|
activity_entry.save()
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ Try upgrading OpenSSH or providing your private key in an different format. \
|
|||||||
|
|
||||||
logger = logging.getLogger('awx.main.tasks')
|
logger = logging.getLogger('awx.main.tasks')
|
||||||
|
|
||||||
|
|
||||||
@celeryd_init.connect
|
@celeryd_init.connect
|
||||||
def celery_startup(conf=None, **kwargs):
|
def celery_startup(conf=None, **kwargs):
|
||||||
# Re-init all schedules
|
# Re-init all schedules
|
||||||
@@ -81,6 +82,7 @@ def celery_startup(conf=None, **kwargs):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to rebuild schedule {}: {}".format(sch, e))
|
logger.error("Failed to rebuild schedule {}: {}".format(sch, e))
|
||||||
|
|
||||||
|
|
||||||
@task(queue='default')
|
@task(queue='default')
|
||||||
def send_notifications(notification_list, job_id=None):
|
def send_notifications(notification_list, job_id=None):
|
||||||
if not isinstance(notification_list, list):
|
if not isinstance(notification_list, list):
|
||||||
@@ -102,6 +104,7 @@ def send_notifications(notification_list, job_id=None):
|
|||||||
if job_id is not None:
|
if job_id is not None:
|
||||||
job_actual.notifications.add(notification)
|
job_actual.notifications.add(notification)
|
||||||
|
|
||||||
|
|
||||||
@task(bind=True, queue='default')
|
@task(bind=True, queue='default')
|
||||||
def run_administrative_checks(self):
|
def run_administrative_checks(self):
|
||||||
if not settings.TOWER_ADMIN_ALERTS:
|
if not settings.TOWER_ADMIN_ALERTS:
|
||||||
@@ -122,10 +125,12 @@ def run_administrative_checks(self):
|
|||||||
tower_admin_emails,
|
tower_admin_emails,
|
||||||
fail_silently=True)
|
fail_silently=True)
|
||||||
|
|
||||||
|
|
||||||
@task(bind=True, queue='default')
|
@task(bind=True, queue='default')
|
||||||
def cleanup_authtokens(self):
|
def cleanup_authtokens(self):
|
||||||
AuthToken.objects.filter(expires__lt=now()).delete()
|
AuthToken.objects.filter(expires__lt=now()).delete()
|
||||||
|
|
||||||
|
|
||||||
@task(bind=True)
|
@task(bind=True)
|
||||||
def cluster_node_heartbeat(self):
|
def cluster_node_heartbeat(self):
|
||||||
inst = Instance.objects.filter(hostname=settings.CLUSTER_HOST_ID)
|
inst = Instance.objects.filter(hostname=settings.CLUSTER_HOST_ID)
|
||||||
@@ -136,6 +141,7 @@ def cluster_node_heartbeat(self):
|
|||||||
return
|
return
|
||||||
raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID))
|
raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID))
|
||||||
|
|
||||||
|
|
||||||
@task(bind=True, queue='default')
|
@task(bind=True, queue='default')
|
||||||
def tower_periodic_scheduler(self):
|
def tower_periodic_scheduler(self):
|
||||||
run_now = now()
|
run_now = now()
|
||||||
@@ -165,6 +171,7 @@ def tower_periodic_scheduler(self):
|
|||||||
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
|
||||||
state.save()
|
state.save()
|
||||||
|
|
||||||
|
|
||||||
def _send_notification_templates(instance, status_str):
|
def _send_notification_templates(instance, status_str):
|
||||||
if status_str not in ['succeeded', 'failed']:
|
if status_str not in ['succeeded', 'failed']:
|
||||||
raise ValueError(_("status_str must be either succeeded or failed"))
|
raise ValueError(_("status_str must be either succeeded or failed"))
|
||||||
@@ -192,6 +199,7 @@ def handle_work_success(self, result, task_actual):
|
|||||||
from awx.main.scheduler.tasks import run_job_complete
|
from awx.main.scheduler.tasks import run_job_complete
|
||||||
run_job_complete.delay(instance.id)
|
run_job_complete.delay(instance.id)
|
||||||
|
|
||||||
|
|
||||||
@task(bind=True, queue='default')
|
@task(bind=True, queue='default')
|
||||||
def handle_work_error(self, task_id, subtasks=None):
|
def handle_work_error(self, task_id, subtasks=None):
|
||||||
print('Executing error task id %s, subtasks: %s' %
|
print('Executing error task id %s, subtasks: %s' %
|
||||||
@@ -231,6 +239,7 @@ def handle_work_error(self, task_id, subtasks=None):
|
|||||||
run_job_complete.delay(first_instance.id)
|
run_job_complete.delay(first_instance.id)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@task(queue='default')
|
@task(queue='default')
|
||||||
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
|
||||||
'''
|
'''
|
||||||
@@ -1222,6 +1231,7 @@ class RunProjectUpdate(BaseTask):
|
|||||||
else:
|
else:
|
||||||
logger.error("Could not find scm revision in check")
|
logger.error("Could not find scm revision in check")
|
||||||
|
|
||||||
|
|
||||||
class RunInventoryUpdate(BaseTask):
|
class RunInventoryUpdate(BaseTask):
|
||||||
|
|
||||||
name = 'awx.main.tasks.run_inventory_update'
|
name = 'awx.main.tasks.run_inventory_update'
|
||||||
@@ -1556,6 +1566,7 @@ class RunInventoryUpdate(BaseTask):
|
|||||||
def get_idle_timeout(self):
|
def get_idle_timeout(self):
|
||||||
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
|
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
|
||||||
|
|
||||||
|
|
||||||
class RunAdHocCommand(BaseTask):
|
class RunAdHocCommand(BaseTask):
|
||||||
'''
|
'''
|
||||||
Celery task to run an ad hoc command using ansible.
|
Celery task to run an ad hoc command using ansible.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Helps with test cases.
|
# Helps with test cases.
|
||||||
# Save all components of a uri (i.e. scheme, username, password, etc.) so that
|
# Save all components of a uri (i.e. scheme, username, password, etc.) so that
|
||||||
# when we construct a uri string and decompose it, we can verify the decomposition
|
# when we construct a uri string and decompose it, we can verify the decomposition
|
||||||
|
|
||||||
|
|
||||||
class URI(object):
|
class URI(object):
|
||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
'scheme' : 'http',
|
'scheme' : 'http',
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ TEST_PLAYBOOK = '''- hosts: mygroup
|
|||||||
command: test 1 = 1
|
command: test 1 = 1
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class QueueTestMixin(object):
|
class QueueTestMixin(object):
|
||||||
def start_queue(self):
|
def start_queue(self):
|
||||||
self.start_rabbit()
|
self.start_rabbit()
|
||||||
@@ -83,13 +84,17 @@ class QueueStartStopTestMixin(QueueTestMixin):
|
|||||||
super(QueueStartStopTestMixin, self).tearDown()
|
super(QueueStartStopTestMixin, self).tearDown()
|
||||||
self.terminate_queue()
|
self.terminate_queue()
|
||||||
|
|
||||||
|
|
||||||
class MockCommonlySlowTestMixin(object):
|
class MockCommonlySlowTestMixin(object):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
from awx.api import generics
|
from awx.api import generics
|
||||||
mock.patch.object(generics, 'get_view_description', return_value=None).start()
|
mock.patch.object(generics, 'get_view_description', return_value=None).start()
|
||||||
super(MockCommonlySlowTestMixin, self).__init__(*args, **kwargs)
|
super(MockCommonlySlowTestMixin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
ansible_version = get_ansible_version()
|
ansible_version = get_ansible_version()
|
||||||
|
|
||||||
|
|
||||||
class BaseTestMixin(MockCommonlySlowTestMixin):
|
class BaseTestMixin(MockCommonlySlowTestMixin):
|
||||||
'''
|
'''
|
||||||
Mixin with shared code for use by all test cases.
|
Mixin with shared code for use by all test cases.
|
||||||
@@ -675,17 +680,20 @@ class BaseTestMixin(MockCommonlySlowTestMixin):
|
|||||||
u'expected no traceback, got:\n%s' %
|
u'expected no traceback, got:\n%s' %
|
||||||
job.result_traceback)
|
job.result_traceback)
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(BaseTestMixin, django.test.TestCase):
|
class BaseTest(BaseTestMixin, django.test.TestCase):
|
||||||
'''
|
'''
|
||||||
Base class for unit tests.
|
Base class for unit tests.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase):
|
class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase):
|
||||||
'''
|
'''
|
||||||
Base class for tests requiring transactions (or where the test database
|
Base class for tests requiring transactions (or where the test database
|
||||||
needs to be accessed by subprocesses).
|
needs to be accessed by subprocesses).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True,
|
@override_settings(CELERY_ALWAYS_EAGER=True,
|
||||||
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
|
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
|
||||||
ANSIBLE_TRANSPORT='local')
|
ANSIBLE_TRANSPORT='local')
|
||||||
@@ -697,6 +705,7 @@ class BaseLiveServerTest(BaseTestMixin, django.test.LiveServerTestCase):
|
|||||||
super(BaseLiveServerTest, self).setUp()
|
super(BaseLiveServerTest, self).setUp()
|
||||||
settings.INTERNAL_API_URL = self.live_server_url
|
settings.INTERNAL_API_URL = self.live_server_url
|
||||||
|
|
||||||
|
|
||||||
@override_settings(CELERY_ALWAYS_EAGER=True,
|
@override_settings(CELERY_ALWAYS_EAGER=True,
|
||||||
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
|
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
|
||||||
ANSIBLE_TRANSPORT='local',
|
ANSIBLE_TRANSPORT='local',
|
||||||
|
|||||||
@@ -11,22 +11,27 @@ from awx.main.tests.factories import (
|
|||||||
create_workflow_job_template,
|
create_workflow_job_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_factory():
|
def job_template_factory():
|
||||||
return create_job_template
|
return create_job_template
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def organization_factory():
|
def organization_factory():
|
||||||
return create_organization
|
return create_organization
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def notification_template_factory():
|
def notification_template_factory():
|
||||||
return create_notification_template
|
return create_notification_template
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def survey_spec_factory():
|
def survey_spec_factory():
|
||||||
return create_survey_spec
|
return create_survey_spec
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_with_survey_passwords_factory(job_template_factory):
|
def job_template_with_survey_passwords_factory(job_template_factory):
|
||||||
def rf(persisted):
|
def rf(persisted):
|
||||||
@@ -38,22 +43,27 @@ def job_template_with_survey_passwords_factory(job_template_factory):
|
|||||||
return objects.job_template
|
return objects.job_template
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_with_secret_key_unit(job_with_secret_key_factory):
|
def job_with_secret_key_unit(job_with_secret_key_factory):
|
||||||
return job_with_secret_key_factory(persisted=False)
|
return job_with_secret_key_factory(persisted=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def workflow_job_template_factory():
|
def workflow_job_template_factory():
|
||||||
return create_workflow_job_template
|
return create_workflow_job_template
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def get_ssh_version(mocker):
|
def get_ssh_version(mocker):
|
||||||
return mocker.patch('awx.main.tasks.get_ssh_version', return_value='OpenSSH_6.9p1, LibreSSL 2.1.8')
|
return mocker.patch('awx.main.tasks.get_ssh_version', return_value='OpenSSH_6.9p1, LibreSSL 2.1.8')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory):
|
def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory):
|
||||||
return job_template_with_survey_passwords_factory(persisted=False)
|
return job_template_with_survey_passwords_factory(persisted=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def enterprise_license():
|
def enterprise_license():
|
||||||
from awx.main.task_engine import TaskEnhancer
|
from awx.main.task_engine import TaskEnhancer
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from awx.main.models import (
|
|||||||
# persisted=False
|
# persisted=False
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def mk_instance(persisted=True):
|
def mk_instance(persisted=True):
|
||||||
if not persisted:
|
if not persisted:
|
||||||
raise RuntimeError('creating an Instance requires persisted=True')
|
raise RuntimeError('creating an Instance requires persisted=True')
|
||||||
@@ -158,6 +159,7 @@ def mk_job_template(name, job_type='run',
|
|||||||
jt.save()
|
jt.save()
|
||||||
return jt
|
return jt
|
||||||
|
|
||||||
|
|
||||||
def mk_workflow_job(status='new', workflow_job_template=None, extra_vars={},
|
def mk_workflow_job(status='new', workflow_job_template=None, extra_vars={},
|
||||||
persisted=True):
|
persisted=True):
|
||||||
job = WorkflowJob(status=status, extra_vars=json.dumps(extra_vars))
|
job = WorkflowJob(status=status, extra_vars=json.dumps(extra_vars))
|
||||||
@@ -168,6 +170,7 @@ def mk_workflow_job(status='new', workflow_job_template=None, extra_vars={},
|
|||||||
job.save()
|
job.save()
|
||||||
return job
|
return job
|
||||||
|
|
||||||
|
|
||||||
def mk_workflow_job_template(name, extra_vars='', spec=None, organization=None, persisted=True):
|
def mk_workflow_job_template(name, extra_vars='', spec=None, organization=None, persisted=True):
|
||||||
if extra_vars:
|
if extra_vars:
|
||||||
extra_vars = json.dumps(extra_vars)
|
extra_vars = json.dumps(extra_vars)
|
||||||
@@ -182,6 +185,7 @@ def mk_workflow_job_template(name, extra_vars='', spec=None, organization=None,
|
|||||||
wfjt.save()
|
wfjt.save()
|
||||||
return wfjt
|
return wfjt
|
||||||
|
|
||||||
|
|
||||||
def mk_workflow_job_template_node(workflow_job_template=None,
|
def mk_workflow_job_template_node(workflow_job_template=None,
|
||||||
unified_job_template=None,
|
unified_job_template=None,
|
||||||
success_nodes=None,
|
success_nodes=None,
|
||||||
@@ -197,6 +201,7 @@ def mk_workflow_job_template_node(workflow_job_template=None,
|
|||||||
workflow_node.save()
|
workflow_node.save()
|
||||||
return workflow_node
|
return workflow_node
|
||||||
|
|
||||||
|
|
||||||
def mk_workflow_job_node(unified_job_template=None,
|
def mk_workflow_job_node(unified_job_template=None,
|
||||||
success_nodes=None,
|
success_nodes=None,
|
||||||
failure_nodes=None,
|
failure_nodes=None,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from collections import namedtuple
|
|||||||
|
|
||||||
from .exc import NotUnique
|
from .exc import NotUnique
|
||||||
|
|
||||||
|
|
||||||
def generate_objects(artifacts, kwargs):
|
def generate_objects(artifacts, kwargs):
|
||||||
'''generate_objects takes a list of artifacts that are supported by
|
'''generate_objects takes a list of artifacts that are supported by
|
||||||
a create function and compares it to the kwargs passed in to the create
|
a create function and compares it to the kwargs passed in to the create
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ def apply_roles(roles, objects, persisted):
|
|||||||
else:
|
else:
|
||||||
raise RuntimeError('unable to add non-user {} for members list of {}'.format(member_str, obj_str))
|
raise RuntimeError('unable to add non-user {} for members list of {}'.format(member_str, obj_str))
|
||||||
|
|
||||||
|
|
||||||
def generate_users(organization, teams, superuser, persisted, **kwargs):
|
def generate_users(organization, teams, superuser, persisted, **kwargs):
|
||||||
'''generate_users evaluates a mixed list of User objects and strings.
|
'''generate_users evaluates a mixed list of User objects and strings.
|
||||||
If a string is encountered a user with that username is created and added to the lookup dict.
|
If a string is encountered a user with that username is created and added to the lookup dict.
|
||||||
@@ -112,6 +113,7 @@ def generate_users(organization, teams, superuser, persisted, **kwargs):
|
|||||||
users[p1] = mk_user(p1, organization=organization, team=None, is_superuser=superuser, persisted=persisted)
|
users[p1] = mk_user(p1, organization=organization, team=None, is_superuser=superuser, persisted=persisted)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
def generate_teams(organization, persisted, **kwargs):
|
def generate_teams(organization, persisted, **kwargs):
|
||||||
'''generate_teams evalutes a mixed list of Team objects and strings.
|
'''generate_teams evalutes a mixed list of Team objects and strings.
|
||||||
If a string is encountered a team with that string name is created and added to the lookup dict.
|
If a string is encountered a team with that string name is created and added to the lookup dict.
|
||||||
@@ -126,6 +128,7 @@ def generate_teams(organization, persisted, **kwargs):
|
|||||||
teams[t] = mk_team(t, organization=organization, persisted=persisted)
|
teams[t] = mk_team(t, organization=organization, persisted=persisted)
|
||||||
return teams
|
return teams
|
||||||
|
|
||||||
|
|
||||||
def create_survey_spec(variables=None, default_type='integer', required=True):
|
def create_survey_spec(variables=None, default_type='integer', required=True):
|
||||||
'''
|
'''
|
||||||
Returns a valid survey spec for a job template, based on the input
|
Returns a valid survey spec for a job template, based on the input
|
||||||
@@ -176,6 +179,7 @@ def create_survey_spec(variables=None, default_type='integer', required=True):
|
|||||||
# or encapsulated by specific factory fixtures in a conftest
|
# or encapsulated by specific factory fixtures in a conftest
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def create_job_template(name, roles=None, persisted=True, **kwargs):
|
def create_job_template(name, roles=None, persisted=True, **kwargs):
|
||||||
Objects = generate_objects(["job_template", "jobs",
|
Objects = generate_objects(["job_template", "jobs",
|
||||||
"organization",
|
"organization",
|
||||||
@@ -260,6 +264,7 @@ def create_job_template(name, roles=None, persisted=True, **kwargs):
|
|||||||
organization=org,
|
organization=org,
|
||||||
survey=spec,)
|
survey=spec,)
|
||||||
|
|
||||||
|
|
||||||
def create_organization(name, roles=None, persisted=True, **kwargs):
|
def create_organization(name, roles=None, persisted=True, **kwargs):
|
||||||
Objects = generate_objects(["organization",
|
Objects = generate_objects(["organization",
|
||||||
"teams", "users",
|
"teams", "users",
|
||||||
@@ -319,6 +324,7 @@ def create_organization(name, roles=None, persisted=True, **kwargs):
|
|||||||
notification_templates=_Mapped(notification_templates),
|
notification_templates=_Mapped(notification_templates),
|
||||||
inventories=_Mapped(inventories))
|
inventories=_Mapped(inventories))
|
||||||
|
|
||||||
|
|
||||||
def create_notification_template(name, roles=None, persisted=True, **kwargs):
|
def create_notification_template(name, roles=None, persisted=True, **kwargs):
|
||||||
Objects = generate_objects(["notification_template",
|
Objects = generate_objects(["notification_template",
|
||||||
"organization",
|
"organization",
|
||||||
@@ -346,6 +352,7 @@ def create_notification_template(name, roles=None, persisted=True, **kwargs):
|
|||||||
superusers=_Mapped(superusers),
|
superusers=_Mapped(superusers),
|
||||||
teams=teams)
|
teams=teams)
|
||||||
|
|
||||||
|
|
||||||
def generate_workflow_job_template_nodes(workflow_job_template,
|
def generate_workflow_job_template_nodes(workflow_job_template,
|
||||||
persisted,
|
persisted,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@@ -372,6 +379,7 @@ def generate_workflow_job_template_nodes(workflow_job_template,
|
|||||||
for related_index in workflow_job_template_nodes[i][node_type]:
|
for related_index in workflow_job_template_nodes[i][node_type]:
|
||||||
getattr(new_node, node_type).add(new_nodes[related_index])
|
getattr(new_node, node_type).add(new_nodes[related_index])
|
||||||
|
|
||||||
|
|
||||||
# TODO: Implement survey and jobs
|
# TODO: Implement survey and jobs
|
||||||
def create_workflow_job_template(name, organization=None, persisted=True, **kwargs):
|
def create_workflow_job_template(name, organization=None, persisted=True, **kwargs):
|
||||||
Objects = generate_objects(["workflow_job_template",
|
Objects = generate_objects(["workflow_job_template",
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ from awx.main.access import ActivityStreamAccess
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_enabled(feature):
|
def mock_feature_enabled(feature):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def activity_stream_entry(organization, org_admin):
|
def activity_stream_entry(organization, org_admin):
|
||||||
return ActivityStream.objects.filter(organization__pk=organization.pk, user=org_admin, operation='associate').first()
|
return ActivityStream.objects.filter(organization__pk=organization.pk, user=org_admin, operation='associate').first()
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_get_activity_stream_list(monkeypatch, organization, get, user, settings):
|
def test_get_activity_stream_list(monkeypatch, organization, get, user, settings):
|
||||||
@@ -23,6 +26,7 @@ def test_get_activity_stream_list(monkeypatch, organization, get, user, settings
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_basic_fields(monkeypatch, organization, get, user, settings):
|
def test_basic_fields(monkeypatch, organization, get, user, settings):
|
||||||
@@ -43,6 +47,7 @@ def test_basic_fields(monkeypatch, organization, get, user, settings):
|
|||||||
assert 'organization' in response.data['summary_fields']
|
assert 'organization' in response.data['summary_fields']
|
||||||
assert response.data['summary_fields']['organization'][0]['name'] == 'test-org'
|
assert response.data['summary_fields']['organization'][0]['name'] == 'test-org'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_middleware_actor_added(monkeypatch, post, get, user, settings):
|
def test_middleware_actor_added(monkeypatch, post, get, user, settings):
|
||||||
@@ -65,6 +70,7 @@ def test_middleware_actor_added(monkeypatch, post, get, user, settings):
|
|||||||
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'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_admin, settings):
|
def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_admin, settings):
|
||||||
@@ -74,6 +80,7 @@ def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_adm
|
|||||||
assert activity_stream_entry.role.first() == organization.admin_role
|
assert activity_stream_entry.role.first() == organization.admin_role
|
||||||
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin, settings):
|
def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin, settings):
|
||||||
@@ -83,6 +90,7 @@ def test_rbac_stream_user_roles(activity_stream_entry, organization, org_admin,
|
|||||||
assert activity_stream_entry.role.first() == organization.admin_role
|
assert activity_stream_entry.role.first() == organization.admin_role
|
||||||
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
assert activity_stream_entry.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.activity_stream_access
|
@pytest.mark.activity_stream_access
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@@ -94,6 +102,7 @@ def test_stream_access_cant_change(activity_stream_entry, organization, org_admi
|
|||||||
assert not access.can_change(activity_stream_entry, {'organization': None})
|
assert not access.can_change(activity_stream_entry, {'organization': None})
|
||||||
assert not access.can_delete(activity_stream_entry)
|
assert not access.can_delete(activity_stream_entry)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.activity_stream_access
|
@pytest.mark.activity_stream_access
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@@ -129,6 +138,7 @@ def test_stream_queryset_hides_shows_items(
|
|||||||
assert queryset.filter(team__pk=team.pk, operation='create').count() == 1
|
assert queryset.filter(team__pk=team.pk, operation='create').count() == 1
|
||||||
assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1
|
assert queryset.filter(notification_template__pk=notification_template.pk, operation='create').count() == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
def test_stream_user_direct_role_updates(get, post, organization_factory):
|
def test_stream_user_direct_role_updates(get, post, organization_factory):
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import pytest
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def run_test_ad_hoc_command(self, **kwargs):
|
def run_test_ad_hoc_command(self, **kwargs):
|
||||||
# Post to list to start a new ad hoc command.
|
# Post to list to start a new ad hoc command.
|
||||||
@@ -23,6 +22,7 @@ from django.core.urlresolvers import reverse
|
|||||||
return self.post(url, data, expect=expect)
|
return self.post(url, data, expect=expect)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def post_adhoc(post, inventory, machine_credential):
|
def post_adhoc(post, inventory, machine_credential):
|
||||||
def f(url, data, user, expect=201):
|
def f(url, data, user, expect=201):
|
||||||
@@ -46,7 +46,6 @@ def post_adhoc(post, inventory, machine_credential):
|
|||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_admin_post_ad_hoc_command_list(admin, post_adhoc, inventory, machine_credential):
|
def test_admin_post_ad_hoc_command_list(admin, post_adhoc, inventory, machine_credential):
|
||||||
res = post_adhoc(reverse('api:ad_hoc_command_list'), {}, admin, expect=201)
|
res = post_adhoc(reverse('api:ad_hoc_command_list'), {}, admin, expect=201)
|
||||||
@@ -65,35 +64,42 @@ def test_admin_post_ad_hoc_command_list(admin, post_adhoc, inventory, machine_cr
|
|||||||
def test_empty_post_403(admin, post):
|
def test_empty_post_403(admin, post):
|
||||||
post(reverse('api:ad_hoc_command_list'), {}, admin, expect=400)
|
post(reverse('api:ad_hoc_command_list'), {}, admin, expect=400)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_empty_put_405(admin, put):
|
def test_empty_put_405(admin, put):
|
||||||
put(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
|
put(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_empty_patch_405(admin, patch):
|
def test_empty_patch_405(admin, patch):
|
||||||
patch(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
|
patch(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_empty_delete_405(admin, delete):
|
def test_empty_delete_405(admin, delete):
|
||||||
delete(reverse('api:ad_hoc_command_list'), admin, expect=405)
|
delete(reverse('api:ad_hoc_command_list'), admin, expect=405)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_post_ad_hoc_command_list(alice, post_adhoc, inventory, machine_credential):
|
def test_user_post_ad_hoc_command_list(alice, post_adhoc, inventory, machine_credential):
|
||||||
inventory.adhoc_role.members.add(alice)
|
inventory.adhoc_role.members.add(alice)
|
||||||
machine_credential.use_role.members.add(alice)
|
machine_credential.use_role.members.add(alice)
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=201)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=201)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_post_ad_hoc_command_list_xfail(alice, post_adhoc, inventory, machine_credential):
|
def test_user_post_ad_hoc_command_list_xfail(alice, post_adhoc, inventory, machine_credential):
|
||||||
inventory.read_role.members.add(alice) # just read access? no dice.
|
inventory.read_role.members.add(alice) # just read access? no dice.
|
||||||
machine_credential.use_role.members.add(alice)
|
machine_credential.use_role.members.add(alice)
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_post_ad_hoc_command_list_without_creds(alice, post_adhoc, inventory, machine_credential):
|
def test_user_post_ad_hoc_command_list_without_creds(alice, post_adhoc, inventory, machine_credential):
|
||||||
inventory.adhoc_role.members.add(alice)
|
inventory.adhoc_role.members.add(alice)
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_post_ad_hoc_command_list_without_inventory(alice, post_adhoc, inventory, machine_credential):
|
def test_user_post_ad_hoc_command_list_without_inventory(alice, post_adhoc, inventory, machine_credential):
|
||||||
machine_credential.use_role.members.add(alice)
|
machine_credential.use_role.members.add(alice)
|
||||||
@@ -134,15 +140,17 @@ def test_get_inventory_ad_hoc_command_list(admin, alice, post_adhoc, get, invent
|
|||||||
def test_bad_data1(admin, post_adhoc):
|
def test_bad_data1(admin, post_adhoc):
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {'module_name': 'command', 'module_args': None}, admin, expect=400)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {'module_name': 'command', 'module_args': None}, admin, expect=400)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_bad_data2(admin, post_adhoc):
|
def test_bad_data2(admin, post_adhoc):
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {'job_type': 'baddata'}, admin, expect=400)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {'job_type': 'baddata'}, admin, expect=400)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_bad_data3(admin, post_adhoc):
|
def test_bad_data3(admin, post_adhoc):
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {'verbosity': -1}, admin, expect=400)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {'verbosity': -1}, admin, expect=400)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_bad_data4(admin, post_adhoc):
|
def test_bad_data4(admin, post_adhoc):
|
||||||
post_adhoc(reverse('api:ad_hoc_command_list'), {'forks': -1}, admin, expect=400)
|
post_adhoc(reverse('api:ad_hoc_command_list'), {'forks': -1}, admin, expect=400)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ def test_user_role_view_access(rando, inventory, mocker, post):
|
|||||||
inventory.admin_role, rando, 'members', data,
|
inventory.admin_role, rando, 'members', data,
|
||||||
skip_sub_obj_read_check=False)
|
skip_sub_obj_read_check=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_team_role_view_access(rando, team, inventory, mocker, post):
|
def test_team_role_view_access(rando, team, inventory, mocker, post):
|
||||||
"Assure correct access method is called when assigning teams new roles"
|
"Assure correct access method is called when assigning teams new roles"
|
||||||
@@ -30,6 +31,7 @@ def test_team_role_view_access(rando, team, inventory, mocker, post):
|
|||||||
inventory.admin_role, team, 'member_role.parents', data,
|
inventory.admin_role, team, 'member_role.parents', data,
|
||||||
skip_sub_obj_read_check=False)
|
skip_sub_obj_read_check=False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_role_team_view_access(rando, team, inventory, mocker, post):
|
def test_role_team_view_access(rando, team, inventory, mocker, post):
|
||||||
"""Assure that /role/N/teams/ enforces the same permission restrictions
|
"""Assure that /role/N/teams/ enforces the same permission restrictions
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse
|
|||||||
# user credential creation
|
# user credential creation
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_user_credential_via_credentials_list(post, get, alice):
|
def test_create_user_credential_via_credentials_list(post, get, alice):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -21,6 +22,7 @@ def test_create_user_credential_via_credentials_list(post, get, alice):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data['count'] == 1
|
assert response.data['count'] == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_credential_validation_error_with_bad_user(post, admin):
|
def test_credential_validation_error_with_bad_user(post, admin):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -31,6 +33,7 @@ def test_credential_validation_error_with_bad_user(post, admin):
|
|||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert response.data['user'][0] == 'Incorrect type. Expected pk value, received unicode.'
|
assert response.data['user'][0] == 'Incorrect type. Expected pk value, received unicode.'
|
||||||
|
|
||||||
|
|
||||||
@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,)), {
|
||||||
@@ -44,6 +47,7 @@ def test_create_user_credential_via_user_credentials_list(post, get, alice):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data['count'] == 1
|
assert response.data['count'] == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
|
def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -53,6 +57,7 @@ def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
|
|||||||
}, alice)
|
}, alice)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@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,)), {
|
||||||
@@ -67,6 +72,7 @@ def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob
|
|||||||
# team credential creation
|
# team credential creation
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_team_credential(post, get, team, organization, org_admin, team_member):
|
def test_create_team_credential(post, get, team, organization, org_admin, team_member):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -83,6 +89,7 @@ def test_create_team_credential(post, get, team, organization, org_admin, team_m
|
|||||||
# Assure that credential's organization is implictly set to team's org
|
# Assure that credential's organization is implictly set to team's org
|
||||||
assert response.data['results'][0]['summary_fields']['organization']['id'] == team.organization.id
|
assert response.data['results'][0]['summary_fields']['organization']['id'] == team.organization.id
|
||||||
|
|
||||||
|
|
||||||
@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,)), {
|
||||||
@@ -96,6 +103,7 @@ def test_create_team_credential_via_team_credentials_list(post, get, team, org_a
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data['count'] == 1
|
assert response.data['count'] == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_team_credential_by_urelated_user_xfail(post, team, organization, alice, team_member):
|
def test_create_team_credential_by_urelated_user_xfail(post, team, organization, alice, team_member):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -106,6 +114,7 @@ def test_create_team_credential_by_urelated_user_xfail(post, team, organization,
|
|||||||
}, alice)
|
}, alice)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_team_credential_by_team_member_xfail(post, team, organization, alice, team_member):
|
def test_create_team_credential_by_team_member_xfail(post, team, organization, alice, team_member):
|
||||||
# Members can't add credentials, only org admins.. for now?
|
# Members can't add credentials, only org admins.. for now?
|
||||||
@@ -122,6 +131,7 @@ def test_create_team_credential_by_team_member_xfail(post, team, organization, a
|
|||||||
# Permission granting
|
# Permission granting
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_org_user_through_role_users(post, credential, organization, org_admin, org_member):
|
def test_grant_org_credential_to_org_user_through_role_users(post, credential, organization, org_admin, org_member):
|
||||||
credential.organization = organization
|
credential.organization = organization
|
||||||
@@ -131,6 +141,7 @@ def test_grant_org_credential_to_org_user_through_role_users(post, credential, o
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_org_user_through_user_roles(post, credential, organization, org_admin, org_member):
|
def test_grant_org_credential_to_org_user_through_user_roles(post, credential, organization, org_admin, org_member):
|
||||||
credential.organization = organization
|
credential.organization = organization
|
||||||
@@ -140,6 +151,7 @@ def test_grant_org_credential_to_org_user_through_user_roles(post, credential, o
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_non_org_user_through_role_users(post, credential, organization, org_admin, alice):
|
def test_grant_org_credential_to_non_org_user_through_role_users(post, credential, organization, org_admin, alice):
|
||||||
credential.organization = organization
|
credential.organization = organization
|
||||||
@@ -149,6 +161,7 @@ def test_grant_org_credential_to_non_org_user_through_role_users(post, credentia
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_non_org_user_through_user_roles(post, credential, organization, org_admin, alice):
|
def test_grant_org_credential_to_non_org_user_through_user_roles(post, credential, organization, org_admin, alice):
|
||||||
credential.organization = organization
|
credential.organization = organization
|
||||||
@@ -158,6 +171,7 @@ def test_grant_org_credential_to_non_org_user_through_user_roles(post, credentia
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_private_credential_to_user_through_role_users(post, credential, alice, bob):
|
def test_grant_private_credential_to_user_through_role_users(post, credential, alice, bob):
|
||||||
# normal users can't do this
|
# normal users can't do this
|
||||||
@@ -167,6 +181,7 @@ def test_grant_private_credential_to_user_through_role_users(post, credential, a
|
|||||||
}, alice)
|
}, alice)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_private_credential_to_org_user_through_role_users(post, credential, org_admin, org_member):
|
def test_grant_private_credential_to_org_user_through_role_users(post, credential, org_admin, org_member):
|
||||||
# org admins can't either
|
# org admins can't either
|
||||||
@@ -176,6 +191,7 @@ def test_grant_private_credential_to_org_user_through_role_users(post, credentia
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob):
|
def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob):
|
||||||
# but system admins can
|
# but system admins can
|
||||||
@@ -184,6 +200,7 @@ def test_sa_grant_private_credential_to_user_through_role_users(post, credential
|
|||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_private_credential_to_user_through_user_roles(post, credential, alice, bob):
|
def test_grant_private_credential_to_user_through_user_roles(post, credential, alice, bob):
|
||||||
# normal users can't do this
|
# normal users can't do this
|
||||||
@@ -193,6 +210,7 @@ def test_grant_private_credential_to_user_through_user_roles(post, credential, a
|
|||||||
}, alice)
|
}, alice)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_private_credential_to_org_user_through_user_roles(post, credential, org_admin, org_member):
|
def test_grant_private_credential_to_org_user_through_user_roles(post, credential, org_admin, org_member):
|
||||||
# org admins can't either
|
# org admins can't either
|
||||||
@@ -202,6 +220,7 @@ def test_grant_private_credential_to_org_user_through_user_roles(post, credentia
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob):
|
def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob):
|
||||||
# but system admins can
|
# but system admins can
|
||||||
@@ -210,6 +229,7 @@ def test_sa_grant_private_credential_to_user_through_user_roles(post, credential
|
|||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_team_through_role_teams(post, credential, organization, org_admin, org_auditor, team):
|
def test_grant_org_credential_to_team_through_role_teams(post, credential, organization, org_admin, org_auditor, team):
|
||||||
assert org_auditor not in credential.read_role
|
assert org_auditor not in credential.read_role
|
||||||
@@ -221,6 +241,7 @@ def test_grant_org_credential_to_team_through_role_teams(post, credential, organ
|
|||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
assert org_auditor in credential.read_role
|
assert org_auditor in credential.read_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_grant_org_credential_to_team_through_team_roles(post, credential, organization, org_admin, org_auditor, team):
|
def test_grant_org_credential_to_team_through_team_roles(post, credential, organization, org_admin, org_auditor, team):
|
||||||
assert org_auditor not in credential.read_role
|
assert org_auditor not in credential.read_role
|
||||||
@@ -232,6 +253,7 @@ def test_grant_org_credential_to_team_through_team_roles(post, credential, organ
|
|||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
assert org_auditor in credential.read_role
|
assert org_auditor in credential.read_role
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_sa_grant_private_credential_to_team_through_role_teams(post, credential, admin, team):
|
def test_sa_grant_private_credential_to_team_through_role_teams(post, credential, admin, team):
|
||||||
# not even a system admin can grant a private cred to a team though
|
# not even a system admin can grant a private cred to a team though
|
||||||
@@ -240,6 +262,7 @@ def test_sa_grant_private_credential_to_team_through_role_teams(post, credential
|
|||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team):
|
def test_sa_grant_private_credential_to_team_through_team_roles(post, credential, admin, team):
|
||||||
# not even a system admin can grant a private cred to a team though
|
# not even a system admin can grant a private cred to a team though
|
||||||
@@ -249,12 +272,11 @@ def test_sa_grant_private_credential_to_team_through_team_roles(post, credential
|
|||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# organization credentials
|
# organization credentials
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_org_credential_as_not_admin(post, organization, org_member):
|
def test_create_org_credential_as_not_admin(post, organization, org_member):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -264,6 +286,7 @@ def test_create_org_credential_as_not_admin(post, organization, org_member):
|
|||||||
}, org_member)
|
}, org_member)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_org_credential_as_admin(post, organization, org_admin):
|
def test_create_org_credential_as_admin(post, organization, org_admin):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -273,6 +296,7 @@ def test_create_org_credential_as_admin(post, organization, org_admin):
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_credential_detail(post, get, organization, org_admin):
|
def test_credential_detail(post, get, organization, org_admin):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -288,6 +312,7 @@ def test_credential_detail(post, get, organization, org_admin):
|
|||||||
related_fields = response.data['related']
|
related_fields = response.data['related']
|
||||||
assert 'organization' in related_fields
|
assert 'organization' in related_fields
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_list_created_org_credentials(post, get, organization, org_admin, org_member):
|
def test_list_created_org_credentials(post, get, organization, org_admin, org_member):
|
||||||
response = post(reverse('api:credential_list'), {
|
response = post(reverse('api:credential_list'), {
|
||||||
@@ -336,6 +361,7 @@ def test_cant_change_organization(patch, credential, organization, org_admin):
|
|||||||
}, org_admin)
|
}, org_admin)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cant_add_organization(patch, credential, organization, org_admin):
|
def test_cant_add_organization(patch, credential, organization, org_admin):
|
||||||
assert credential.organization is None
|
assert credential.organization is None
|
||||||
@@ -350,6 +376,7 @@ def test_cant_add_organization(patch, credential, organization, org_admin):
|
|||||||
# Openstack Credentials
|
# Openstack Credentials
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_openstack_create_ok(post, organization, admin):
|
def test_openstack_create_ok(post, organization, admin):
|
||||||
data = {
|
data = {
|
||||||
@@ -364,6 +391,7 @@ def test_openstack_create_ok(post, organization, admin):
|
|||||||
response = post(reverse('api:credential_list'), data, admin)
|
response = post(reverse('api:credential_list'), data, admin)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_openstack_create_fail_required_fields(post, organization, admin):
|
def test_openstack_create_fail_required_fields(post, organization, admin):
|
||||||
data = {
|
data = {
|
||||||
@@ -383,6 +411,7 @@ def test_openstack_create_fail_required_fields(post, organization, admin):
|
|||||||
# misc xfail conditions
|
# misc xfail conditions
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_credential_missing_user_team_org_xfail(post, admin):
|
def test_create_credential_missing_user_team_org_xfail(post, admin):
|
||||||
# Must specify one of user, team, or organization
|
# Must specify one of user, team, or organization
|
||||||
@@ -391,4 +420,3 @@ def test_create_credential_missing_user_team_org_xfail(post, admin):
|
|||||||
'username': 'someusername',
|
'username': 'someusername',
|
||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,15 @@ from awx.main.utils import timestamp_apiformat
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_enabled(feature):
|
def mock_feature_enabled(feature):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_disabled(feature):
|
def mock_feature_disabled(feature):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params={}, host_count=1):
|
def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params={}, host_count=1):
|
||||||
hosts = hosts(host_count=host_count)
|
hosts = hosts(host_count=host_count)
|
||||||
fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
||||||
@@ -28,6 +31,7 @@ def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params=
|
|||||||
|
|
||||||
return (hosts[0], response)
|
return (hosts[0], response)
|
||||||
|
|
||||||
|
|
||||||
def check_url(url1_full, fact_known, module):
|
def check_url(url1_full, fact_known, module):
|
||||||
url1_split = urlparse.urlsplit(url1_full)
|
url1_split = urlparse.urlsplit(url1_full)
|
||||||
url1 = url1_split.path
|
url1 = url1_split.path
|
||||||
@@ -42,16 +46,19 @@ def check_url(url1_full, fact_known, module):
|
|||||||
url2_params_sorted = sorted(url2_params, key=lambda val: val[0])
|
url2_params_sorted = sorted(url2_params, key=lambda val: val[0])
|
||||||
assert urllib.urlencode(url1_params_sorted) == urllib.urlencode(url2_params_sorted)
|
assert urllib.urlencode(url1_params_sorted) == urllib.urlencode(url2_params_sorted)
|
||||||
|
|
||||||
|
|
||||||
def check_response_facts(facts_known, response):
|
def check_response_facts(facts_known, response):
|
||||||
for i, fact_known in enumerate(facts_known):
|
for i, fact_known in enumerate(facts_known):
|
||||||
assert fact_known.module == response.data['results'][i]['module']
|
assert fact_known.module == response.data['results'][i]['module']
|
||||||
assert timestamp_apiformat(fact_known.timestamp) == response.data['results'][i]['timestamp']
|
assert timestamp_apiformat(fact_known.timestamp) == response.data['results'][i]['timestamp']
|
||||||
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
||||||
|
|
||||||
|
|
||||||
def check_system_tracking_feature_forbidden(response):
|
def check_system_tracking_feature_forbidden(response):
|
||||||
assert 402 == response.status_code
|
assert 402 == response.status_code
|
||||||
assert 'Your license does not permit use of system tracking.' == response.data['detail']
|
assert 'Your license does not permit use of system tracking.' == response.data['detail']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -62,6 +69,7 @@ def test_system_tracking_license_get(hosts, get, user):
|
|||||||
|
|
||||||
check_system_tracking_feature_forbidden(response)
|
check_system_tracking_feature_forbidden(response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -72,6 +80,7 @@ def test_system_tracking_license_options(hosts, options, user):
|
|||||||
|
|
||||||
check_system_tracking_feature_forbidden(response)
|
check_system_tracking_feature_forbidden(response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -85,6 +94,7 @@ def test_no_facts_db(hosts, get, user):
|
|||||||
}
|
}
|
||||||
assert response_expected == response.data
|
assert response_expected == response.data
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_basic_fields(hosts, fact_scans, get, user):
|
def test_basic_fields(hosts, fact_scans, get, user):
|
||||||
@@ -101,6 +111,7 @@ def test_basic_fields(hosts, fact_scans, get, user):
|
|||||||
assert 'timestamp' in results[0]
|
assert 'timestamp' in results[0]
|
||||||
assert 'module' in results[0]
|
assert 'module' in results[0]
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -117,6 +128,7 @@ def test_basic_options_fields(hosts, fact_scans, options, user):
|
|||||||
assert ("services", "Services") in response.data['actions']['GET']['module']['choices']
|
assert ("services", "Services") in response.data['actions']['GET']['module']['choices']
|
||||||
assert ("packages", "Packages") in response.data['actions']['GET']['module']['choices']
|
assert ("packages", "Packages") in response.data['actions']['GET']['module']['choices']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_related_fact_view(hosts, fact_scans, get, user):
|
def test_related_fact_view(hosts, fact_scans, get, user):
|
||||||
@@ -130,6 +142,7 @@ def test_related_fact_view(hosts, fact_scans, get, user):
|
|||||||
for i, fact_known in enumerate(facts_known):
|
for i, fact_known in enumerate(facts_known):
|
||||||
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_multiple_hosts(hosts, fact_scans, get, user):
|
def test_multiple_hosts(hosts, fact_scans, get, user):
|
||||||
@@ -143,6 +156,7 @@ def test_multiple_hosts(hosts, fact_scans, get, user):
|
|||||||
for i, fact_known in enumerate(facts_known):
|
for i, fact_known in enumerate(facts_known):
|
||||||
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
check_url(response.data['results'][i]['related']['fact_view'], fact_known, fact_known.module)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_param_to_from(hosts, fact_scans, get, user):
|
def test_param_to_from(hosts, fact_scans, get, user):
|
||||||
@@ -159,6 +173,7 @@ def test_param_to_from(hosts, fact_scans, get, user):
|
|||||||
|
|
||||||
check_response_facts(facts_known, response)
|
check_response_facts(facts_known, response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_param_module(hosts, fact_scans, get, user):
|
def test_param_module(hosts, fact_scans, get, user):
|
||||||
@@ -174,6 +189,7 @@ def test_param_module(hosts, fact_scans, get, user):
|
|||||||
|
|
||||||
check_response_facts(facts_known, response)
|
check_response_facts(facts_known, response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_param_from(hosts, fact_scans, get, user):
|
def test_param_from(hosts, fact_scans, get, user):
|
||||||
@@ -189,6 +205,7 @@ def test_param_from(hosts, fact_scans, get, user):
|
|||||||
|
|
||||||
check_response_facts(facts_known, response)
|
check_response_facts(facts_known, response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_param_to(hosts, fact_scans, get, user):
|
def test_param_to(hosts, fact_scans, get, user):
|
||||||
@@ -204,6 +221,7 @@ def test_param_to(hosts, fact_scans, get, user):
|
|||||||
|
|
||||||
check_response_facts(facts_known, response)
|
check_response_facts(facts_known, response)
|
||||||
|
|
||||||
|
|
||||||
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
||||||
hosts = hosts(host_count=1)
|
hosts = hosts(host_count=1)
|
||||||
fact_scans(fact_scans=1)
|
fact_scans(fact_scans=1)
|
||||||
@@ -214,6 +232,7 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
|||||||
response = get(url, user_obj)
|
response = get(url, user_obj)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -224,6 +243,7 @@ def test_normal_user_403(hosts, fact_scans, get, user, team):
|
|||||||
assert 403 == response.status_code
|
assert 403 == response.status_code
|
||||||
assert "You do not have permission to perform this action." == response.data['detail']
|
assert "You do not have permission to perform this action." == response.data['detail']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -233,6 +253,7 @@ def test_super_user_ok(hosts, fact_scans, get, user, team):
|
|||||||
|
|
||||||
assert 200 == response.status_code
|
assert 200 == response.status_code
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -244,6 +265,7 @@ def test_user_admin_ok(organization, hosts, fact_scans, get, user, team):
|
|||||||
|
|
||||||
assert 200 == response.status_code
|
assert 200 == response.status_code
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -255,4 +277,3 @@ def test_user_admin_403(organization, organizations, hosts, fact_scans, get, use
|
|||||||
response = _test_user_access_control(hosts, fact_scans, get, user_admin, team)
|
response = _test_user_access_control(hosts, fact_scans, get, user_admin, team)
|
||||||
|
|
||||||
assert 403 == response.status_code
|
assert 403 == response.status_code
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ from awx.main.utils import timestamp_apiformat
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_enabled(feature):
|
def mock_feature_enabled(feature):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_disabled(feature):
|
def mock_feature_disabled(feature):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# TODO: Consider making the fact_scan() fixture a Class, instead of a function, and move this method into it
|
# TODO: Consider making the fact_scan() fixture a Class, instead of a function, and move this method into it
|
||||||
def find_fact(facts, host_id, module_name, timestamp):
|
def find_fact(facts, host_id, module_name, timestamp):
|
||||||
for f in facts:
|
for f in facts:
|
||||||
@@ -19,6 +22,7 @@ def find_fact(facts, host_id, module_name, timestamp):
|
|||||||
return f
|
return f
|
||||||
raise RuntimeError('fact <%s, %s, %s> not found in %s', (host_id, module_name, timestamp, facts))
|
raise RuntimeError('fact <%s, %s, %s> not found in %s', (host_id, module_name, timestamp, facts))
|
||||||
|
|
||||||
|
|
||||||
def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), module_name='ansible', get_params={}):
|
def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), module_name='ansible', get_params={}):
|
||||||
hosts = hosts(host_count=1)
|
hosts = hosts(host_count=1)
|
||||||
facts = fact_scans(fact_scans=1, timestamp_epoch=epoch)
|
facts = fact_scans(fact_scans=1, timestamp_epoch=epoch)
|
||||||
@@ -29,10 +33,12 @@ def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), module_name
|
|||||||
fact_known = find_fact(facts, hosts[0].id, module_name, epoch)
|
fact_known = find_fact(facts, hosts[0].id, module_name, epoch)
|
||||||
return (fact_known, response)
|
return (fact_known, response)
|
||||||
|
|
||||||
|
|
||||||
def check_system_tracking_feature_forbidden(response):
|
def check_system_tracking_feature_forbidden(response):
|
||||||
assert 402 == response.status_code
|
assert 402 == response.status_code
|
||||||
assert 'Your license does not permit use of system tracking.' == response.data['detail']
|
assert 'Your license does not permit use of system tracking.' == response.data['detail']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -43,6 +49,7 @@ def test_system_tracking_license_get(hosts, get, user):
|
|||||||
|
|
||||||
check_system_tracking_feature_forbidden(response)
|
check_system_tracking_feature_forbidden(response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -53,6 +60,7 @@ def test_system_tracking_license_options(hosts, options, user):
|
|||||||
|
|
||||||
check_system_tracking_feature_forbidden(response)
|
check_system_tracking_feature_forbidden(response)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_no_fact_found(hosts, get, user):
|
def test_no_fact_found(hosts, get, user):
|
||||||
@@ -66,6 +74,7 @@ def test_no_fact_found(hosts, get, user):
|
|||||||
assert 404 == response.status_code
|
assert 404 == response.status_code
|
||||||
assert expected_response == response.data
|
assert expected_response == response.data
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_basic_fields(hosts, fact_scans, get, user):
|
def test_basic_fields(hosts, fact_scans, get, user):
|
||||||
@@ -88,6 +97,7 @@ def test_basic_fields(hosts, fact_scans, get, user):
|
|||||||
assert 'host' in response.data['related']
|
assert 'host' in response.data['related']
|
||||||
assert reverse('api:host_detail', args=(hosts[0].pk,)) == response.data['related']['host']
|
assert reverse('api:host_detail', args=(hosts[0].pk,)) == response.data['related']['host']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_content(hosts, fact_scans, get, user, fact_ansible_json):
|
def test_content(hosts, fact_scans, get, user, fact_ansible_json):
|
||||||
@@ -98,6 +108,7 @@ def test_content(hosts, fact_scans, get, user, fact_ansible_json):
|
|||||||
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
||||||
assert fact_known.module == response.data['module']
|
assert fact_known.module == response.data['module']
|
||||||
|
|
||||||
|
|
||||||
def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name):
|
def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name):
|
||||||
params = {
|
params = {
|
||||||
'module': module_name
|
'module': module_name
|
||||||
@@ -108,16 +119,19 @@ def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name)
|
|||||||
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
assert timestamp_apiformat(fact_known.timestamp) == response.data['timestamp']
|
||||||
assert module_name == response.data['module']
|
assert module_name == response.data['module']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_search_by_module_packages(hosts, fact_scans, get, user, fact_packages_json):
|
def test_search_by_module_packages(hosts, fact_scans, get, user, fact_packages_json):
|
||||||
_test_search_by_module(hosts, fact_scans, get, user, fact_packages_json, 'packages')
|
_test_search_by_module(hosts, fact_scans, get, user, fact_packages_json, 'packages')
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_search_by_module_services(hosts, fact_scans, get, user, fact_services_json):
|
def test_search_by_module_services(hosts, fact_scans, get, user, fact_services_json):
|
||||||
_test_search_by_module(hosts, fact_scans, get, user, fact_services_json, 'services')
|
_test_search_by_module(hosts, fact_scans, get, user, fact_services_json, 'services')
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_search_by_timestamp_and_module(hosts, fact_scans, get, user, fact_packages_json):
|
def test_search_by_timestamp_and_module(hosts, fact_scans, get, user, fact_packages_json):
|
||||||
@@ -128,6 +142,7 @@ def test_search_by_timestamp_and_module(hosts, fact_scans, get, user, fact_packa
|
|||||||
|
|
||||||
assert fact_known.id == response.data['id']
|
assert fact_known.id == response.data['id']
|
||||||
|
|
||||||
|
|
||||||
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
||||||
hosts = hosts(host_count=1)
|
hosts = hosts(host_count=1)
|
||||||
fact_scans(fact_scans=1)
|
fact_scans(fact_scans=1)
|
||||||
@@ -138,6 +153,7 @@ def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
|
|||||||
response = get(url, user_obj)
|
response = get(url, user_obj)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -148,6 +164,7 @@ def test_normal_user_403(hosts, fact_scans, get, user, team):
|
|||||||
assert 403 == response.status_code
|
assert 403 == response.status_code
|
||||||
assert "You do not have permission to perform this action." == response.data['detail']
|
assert "You do not have permission to perform this action." == response.data['detail']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -157,6 +174,7 @@ def test_super_user_ok(hosts, fact_scans, get, user, team):
|
|||||||
|
|
||||||
assert 200 == response.status_code
|
assert 200 == response.status_code
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -168,6 +186,7 @@ def test_user_admin_ok(organization, hosts, fact_scans, get, user, team):
|
|||||||
|
|
||||||
assert 200 == response.status_code
|
assert 200 == response.status_code
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.ac
|
@pytest.mark.ac
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -179,4 +198,3 @@ def test_user_admin_403(organization, organizations, hosts, fact_scans, get, use
|
|||||||
response = _test_user_access_control(hosts, fact_scans, get, user_admin, team)
|
response = _test_user_access_control(hosts, fact_scans, get, user_admin, team)
|
||||||
|
|
||||||
assert 403 == response.status_code
|
assert 403 == response.status_code
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import pytest
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_basic_fields(hosts, fact_scans, get, user):
|
def test_basic_fields(hosts, fact_scans, get, user):
|
||||||
hosts = hosts(host_count=1)
|
hosts = hosts(host_count=1)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_inventory_source_notification_on_cloud_only(get, post, group_factory, user, notification_template):
|
def test_inventory_source_notification_on_cloud_only(get, post, group_factory, user, notification_template):
|
||||||
u = user('admin', True)
|
u = user('admin', True)
|
||||||
@@ -48,6 +49,7 @@ def test_create_inventory_group(post, inventory, alice, role_field, expected_sta
|
|||||||
getattr(inventory, role_field).members.add(alice)
|
getattr(inventory, role_field).members.add(alice)
|
||||||
post(reverse('api:inventory_groups_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
|
post(reverse('api:inventory_groups_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("role_field,expected_status_code", [
|
@pytest.mark.parametrize("role_field,expected_status_code", [
|
||||||
(None, 403),
|
(None, 403),
|
||||||
('admin_role', 201),
|
('admin_role', 201),
|
||||||
@@ -106,6 +108,7 @@ def test_create_inventory_host(post, inventory, alice, role_field, expected_stat
|
|||||||
getattr(inventory, role_field).members.add(alice)
|
getattr(inventory, role_field).members.add(alice)
|
||||||
post(reverse('api:inventory_hosts_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
|
post(reverse('api:inventory_hosts_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("role_field,expected_status_code", [
|
@pytest.mark.parametrize("role_field,expected_status_code", [
|
||||||
(None, 403),
|
(None, 403),
|
||||||
('admin_role', 201),
|
('admin_role', 201),
|
||||||
@@ -149,6 +152,7 @@ def test_delete_inventory_host(delete, host, alice, role_field, expected_status_
|
|||||||
getattr(host.inventory, role_field).members.add(alice)
|
getattr(host.inventory, role_field).members.add(alice)
|
||||||
delete(reverse('api:host_detail', args=(host.id,)), alice, expect=expected_status_code)
|
delete(reverse('api:host_detail', args=(host.id,)), alice, expect=expected_status_code)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("role_field,expected_status_code", [
|
@pytest.mark.parametrize("role_field,expected_status_code", [
|
||||||
(None, 403),
|
(None, 403),
|
||||||
('admin_role', 202),
|
('admin_role', 202),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from awx.main.models.jobs import Job, JobTemplate
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def runtime_data(organization):
|
def runtime_data(organization):
|
||||||
cred_obj = Credential.objects.create(name='runtime-cred', kind='ssh', username='test_user2', password='pas4word2')
|
cred_obj = Credential.objects.create(name='runtime-cred', kind='ssh', username='test_user2', password='pas4word2')
|
||||||
@@ -22,10 +23,12 @@ def runtime_data(organization):
|
|||||||
credential=cred_obj.pk,
|
credential=cred_obj.pk,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_with_links(machine_credential, inventory):
|
def job_with_links(machine_credential, inventory):
|
||||||
return Job.objects.create(name='existing-job', credential=machine_credential, inventory=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):
|
||||||
@@ -45,6 +48,7 @@ def job_template_prompts(project, inventory, machine_credential):
|
|||||||
)
|
)
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_prompts_null(project):
|
def job_template_prompts_null(project):
|
||||||
return JobTemplate.objects.create(
|
return JobTemplate.objects.create(
|
||||||
@@ -62,6 +66,7 @@ def job_template_prompts_null(project):
|
|||||||
ask_credential_on_launch=True,
|
ask_credential_on_launch=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bad_scan_JT(job_template_prompts):
|
def bad_scan_JT(job_template_prompts):
|
||||||
job_template = job_template_prompts(True)
|
job_template = job_template_prompts(True)
|
||||||
@@ -69,6 +74,7 @@ def bad_scan_JT(job_template_prompts):
|
|||||||
job_template.save()
|
job_template.save()
|
||||||
return job_template
|
return job_template
|
||||||
|
|
||||||
|
|
||||||
# End of setup, tests start here
|
# End of setup, tests start here
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
@@ -98,6 +104,7 @@ def test_job_ignore_unprompted_vars(runtime_data, job_template_prompts, post, ad
|
|||||||
assert 'job_tags' in response.data['ignored_fields']
|
assert 'job_tags' in response.data['ignored_fields']
|
||||||
assert 'skip_tags' in response.data['ignored_fields']
|
assert 'skip_tags' in response.data['ignored_fields']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admin_user, mocker):
|
def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admin_user, mocker):
|
||||||
@@ -115,6 +122,7 @@ def test_job_accept_prompted_vars(runtime_data, job_template_prompts, post, admi
|
|||||||
|
|
||||||
mock_job.signal_start.assert_called_once_with(**runtime_data)
|
mock_job.signal_start.assert_called_once_with(**runtime_data)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_accept_null_tags(job_template_prompts, post, admin_user, mocker):
|
def test_job_accept_null_tags(job_template_prompts, post, admin_user, mocker):
|
||||||
@@ -129,6 +137,7 @@ def test_job_accept_null_tags(job_template_prompts, post, admin_user, mocker):
|
|||||||
|
|
||||||
mock_job.signal_start.assert_called_once_with(job_tags='', skip_tags='')
|
mock_job.signal_start.assert_called_once_with(job_tags='', skip_tags='')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, post, rando, mocker):
|
def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null, post, rando, mocker):
|
||||||
@@ -154,6 +163,7 @@ def test_job_accept_prompted_vars_null(runtime_data, job_template_prompts_null,
|
|||||||
assert job_id == 968
|
assert job_id == 968
|
||||||
mock_job.signal_start.assert_called_once_with(**runtime_data)
|
mock_job.signal_start.assert_called_once_with(**runtime_data)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, post, admin_user):
|
def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, post, admin_user):
|
||||||
@@ -168,6 +178,7 @@ def test_job_reject_invalid_prompted_vars(runtime_data, job_template_prompts, po
|
|||||||
assert response.data['inventory'] == [u'Invalid pk "87865" - object does not exist.']
|
assert response.data['inventory'] == [u'Invalid pk "87865" - object does not exist.']
|
||||||
assert response.data['credential'] == [u'Invalid pk "48474" - object does not exist.']
|
assert response.data['credential'] == [u'Invalid pk "48474" - object does not exist.']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_prompts, post, admin_user):
|
def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_prompts, post, admin_user):
|
||||||
@@ -179,6 +190,7 @@ def test_job_reject_invalid_prompted_extra_vars(runtime_data, job_template_promp
|
|||||||
|
|
||||||
assert response.data['extra_vars'] == ['Must be a valid JSON or YAML dictionary.']
|
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
|
||||||
def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, admin_user):
|
def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, admin_user):
|
||||||
@@ -190,6 +202,7 @@ def test_job_launch_fails_without_inventory(deploy_jobtemplate, post, admin_user
|
|||||||
|
|
||||||
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, post, rando):
|
def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime_data, post, rando):
|
||||||
@@ -202,6 +215,7 @@ def test_job_launch_fails_without_inventory_access(job_template_prompts, runtime
|
|||||||
|
|
||||||
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_launch_fails_without_credential_access(job_template_prompts, runtime_data, post, rando):
|
def test_job_launch_fails_without_credential_access(job_template_prompts, runtime_data, post, rando):
|
||||||
@@ -214,6 +228,7 @@ def test_job_launch_fails_without_credential_access(job_template_prompts, runtim
|
|||||||
|
|
||||||
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_block_scan_job_type_change(job_template_prompts, post, admin_user):
|
def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user):
|
||||||
@@ -225,6 +240,7 @@ def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user):
|
|||||||
|
|
||||||
assert 'job_type' in response.data
|
assert 'job_type' in response.data
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_block_scan_job_inv_change(mocker, bad_scan_JT, runtime_data, post, admin_user):
|
def test_job_block_scan_job_inv_change(mocker, bad_scan_JT, runtime_data, post, admin_user):
|
||||||
@@ -236,6 +252,7 @@ def test_job_block_scan_job_inv_change(mocker, bad_scan_JT, runtime_data, post,
|
|||||||
|
|
||||||
assert 'inventory' in response.data
|
assert 'inventory' in response.data
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_relaunch_copy_vars(job_with_links, machine_credential, inventory,
|
def test_job_relaunch_copy_vars(job_with_links, machine_credential, inventory,
|
||||||
@@ -251,6 +268,7 @@ def test_job_relaunch_copy_vars(job_with_links, machine_credential, inventory,
|
|||||||
assert second_job.inventory == job_with_links.inventory
|
assert second_job.inventory == job_with_links.inventory
|
||||||
assert second_job.limit == 'my_server'
|
assert second_job.limit == 'my_server'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_relaunch_resource_access(job_with_links, user):
|
def test_job_relaunch_resource_access(job_with_links, user):
|
||||||
@@ -271,6 +289,7 @@ def test_job_relaunch_resource_access(job_with_links, user):
|
|||||||
job_with_links.inventory.use_role.members.add(inventory_user)
|
job_with_links.inventory.use_role.members.add(inventory_user)
|
||||||
assert not inventory_user.can_access(Job, 'start', job_with_links)
|
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):
|
||||||
deploy_jobtemplate.extra_vars = '{"job_template_var": 3}'
|
deploy_jobtemplate.extra_vars = '{"job_template_var": 3}'
|
||||||
@@ -291,6 +310,7 @@ def test_job_launch_JT_with_validation(machine_credential, deploy_jobtemplate):
|
|||||||
assert 'job_launch_var' in final_job_extra_vars
|
assert 'job_launch_var' in final_job_extra_vars
|
||||||
assert job_obj.credential.id == machine_credential.id
|
assert job_obj.credential.id == machine_credential.id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.job_runtime_vars
|
@pytest.mark.job_runtime_vars
|
||||||
def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job_template_prompts, post, admin_user):
|
def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job_template_prompts, post, admin_user):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from awx.main.migrations import _save_password_keys as save_password_keys
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"grant_project, grant_credential, grant_inventory, expect", [
|
"grant_project, grant_credential, grant_inventory, expect", [
|
||||||
@@ -34,6 +35,7 @@ def test_create(post, project, machine_credential, inventory, alice, grant_proje
|
|||||||
'playbook': 'helloworld.yml',
|
'playbook': 'helloworld.yml',
|
||||||
}, alice, expect=expect)
|
}, alice, expect=expect)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"grant_project, grant_credential, grant_inventory, expect", [
|
"grant_project, grant_credential, grant_inventory, expect", [
|
||||||
@@ -62,6 +64,7 @@ def test_edit_sensitive_fields(patch, job_template_factory, alice, grant_project
|
|||||||
'playbook': 'alt-helloworld.yml',
|
'playbook': 'alt-helloworld.yml',
|
||||||
}, alice, expect=expect)
|
}, alice, expect=expect)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_edit_playbook(patch, job_template_factory, alice):
|
def test_edit_playbook(patch, job_template_factory, alice):
|
||||||
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')
|
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')
|
||||||
@@ -79,6 +82,7 @@ def test_edit_playbook(patch, job_template_factory, alice):
|
|||||||
'playbook': 'helloworld.yml',
|
'playbook': 'helloworld.yml',
|
||||||
}, alice, expect=403)
|
}, alice, expect=403)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_edit_nonsenstive(patch, job_template_factory, alice):
|
def test_edit_nonsenstive(patch, job_template_factory, alice):
|
||||||
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')
|
objs = job_template_factory('jt', organization='org1', project='prj', inventory='inv', credential='cred')
|
||||||
@@ -104,6 +108,8 @@ def test_edit_nonsenstive(patch, job_template_factory, alice):
|
|||||||
}, alice, expect=200)
|
}, alice, expect=200)
|
||||||
print(res.data)
|
print(res.data)
|
||||||
assert res.data['name'] == 'updated'
|
assert res.data['name'] == 'updated'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def jt_copy_edit(job_template_factory, project):
|
def jt_copy_edit(job_template_factory, project):
|
||||||
objects = job_template_factory(
|
objects = job_template_factory(
|
||||||
@@ -111,6 +117,7 @@ def jt_copy_edit(job_template_factory, project):
|
|||||||
project=project)
|
project=project)
|
||||||
return objects.job_template
|
return objects.job_template
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_job_template_role_user(post, organization_factory, job_template_factory):
|
def test_job_template_role_user(post, organization_factory, job_template_factory):
|
||||||
objects = organization_factory("org",
|
objects = organization_factory("org",
|
||||||
@@ -127,10 +134,8 @@ def test_job_template_role_user(post, organization_factory, job_template_factory
|
|||||||
assert response.status_code == 204
|
assert response.status_code == 204
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
|
def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
|
||||||
|
|
||||||
# Grant random user JT admin access only
|
# Grant random user JT admin access only
|
||||||
jt_copy_edit.admin_role.members.add(rando)
|
jt_copy_edit.admin_role.members.add(rando)
|
||||||
jt_copy_edit.save()
|
jt_copy_edit.save()
|
||||||
@@ -143,6 +148,7 @@ def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
|
|||||||
post_response = post(reverse('api:job_template_list', args=[]), user=rando, data=post_data)
|
post_response = post(reverse('api:job_template_list', args=[]), user=rando, data=post_data)
|
||||||
assert post_response.status_code == 403
|
assert post_response.status_code == 403
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_scan_jt_no_inventory(job_template_factory):
|
def test_scan_jt_no_inventory(job_template_factory):
|
||||||
# A user should be able to create a scan job without a project, but an inventory is required
|
# A user should be able to create a scan job without a project, but an inventory is required
|
||||||
@@ -175,6 +181,7 @@ def test_scan_jt_no_inventory(job_template_factory):
|
|||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
assert 'inventory' in serializer.errors
|
assert 'inventory' in serializer.errors
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_scan_jt_surveys(inventory):
|
def test_scan_jt_surveys(inventory):
|
||||||
serializer = JobTemplateSerializer(data={"name": "Test", "job_type": "scan",
|
serializer = JobTemplateSerializer(data={"name": "Test", "job_type": "scan",
|
||||||
@@ -183,6 +190,7 @@ def test_scan_jt_surveys(inventory):
|
|||||||
assert not serializer.is_valid()
|
assert not serializer.is_valid()
|
||||||
assert "survey_enabled" in serializer.errors
|
assert "survey_enabled" in serializer.errors
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_jt_without_project(inventory):
|
def test_jt_without_project(inventory):
|
||||||
data = dict(name="Test", job_type="run",
|
data = dict(name="Test", job_type="run",
|
||||||
@@ -198,6 +206,7 @@ def test_jt_without_project(inventory):
|
|||||||
serializer = JobTemplateSerializer(data=data)
|
serializer = JobTemplateSerializer(data=data)
|
||||||
assert serializer.is_valid()
|
assert serializer.is_valid()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user):
|
def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user):
|
||||||
objects = job_template_factory('jt',
|
objects = job_template_factory('jt',
|
||||||
@@ -210,6 +219,7 @@ def test_disallow_template_delete_on_running_job(job_template_factory, delete, a
|
|||||||
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
|
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
|
||||||
assert delete_response.status_code == 409
|
assert delete_response.status_code == 409
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_save_survey_passwords_to_job(job_template_with_survey_passwords):
|
def test_save_survey_passwords_to_job(job_template_with_survey_passwords):
|
||||||
"""Test that when a new job is created, the survey_passwords field is
|
"""Test that when a new job is created, the survey_passwords field is
|
||||||
@@ -217,6 +227,7 @@ def test_save_survey_passwords_to_job(job_template_with_survey_passwords):
|
|||||||
job = job_template_with_survey_passwords.create_unified_job()
|
job = job_template_with_survey_passwords.create_unified_job()
|
||||||
assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'}
|
assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_save_survey_passwords_on_migration(job_template_with_survey_passwords):
|
def test_save_survey_passwords_on_migration(job_template_with_survey_passwords):
|
||||||
"""Test that when upgrading to 3.0.2, the jobs connected to a JT that has
|
"""Test that when upgrading to 3.0.2, the jobs connected to a JT that has
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def organization_resource_creator(organization, user):
|
def organization_resource_creator(organization, user):
|
||||||
def rf(users, admins, job_templates, projects, inventories, teams):
|
def rf(users, admins, job_templates, projects, inventories, teams):
|
||||||
@@ -40,6 +41,7 @@ def organization_resource_creator(organization, user):
|
|||||||
return organization
|
return organization
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
COUNTS_PRIMES = {
|
COUNTS_PRIMES = {
|
||||||
'users': 11,
|
'users': 11,
|
||||||
'admins': 5,
|
'admins': 5,
|
||||||
@@ -57,10 +59,12 @@ COUNTS_ZEROS = {
|
|||||||
'teams': 0
|
'teams': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def resourced_organization(organization_resource_creator):
|
def resourced_organization(organization_resource_creator):
|
||||||
return organization_resource_creator(**COUNTS_PRIMES)
|
return organization_resource_creator(**COUNTS_PRIMES)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_counts_detail_admin(resourced_organization, user, get):
|
def test_org_counts_detail_admin(resourced_organization, user, get):
|
||||||
# Check that all types of resources are counted by a superuser
|
# Check that all types of resources are counted by a superuser
|
||||||
@@ -72,6 +76,7 @@ def test_org_counts_detail_admin(resourced_organization, user, get):
|
|||||||
counts = response.data['summary_fields']['related_field_counts']
|
counts = response.data['summary_fields']['related_field_counts']
|
||||||
assert counts == COUNTS_PRIMES
|
assert counts == COUNTS_PRIMES
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_counts_detail_member(resourced_organization, user, get):
|
def test_org_counts_detail_member(resourced_organization, user, get):
|
||||||
# Check that a non-admin org member can only see users / admin in detail view
|
# Check that a non-admin org member can only see users / admin in detail view
|
||||||
@@ -90,6 +95,7 @@ def test_org_counts_detail_member(resourced_organization, user, get):
|
|||||||
'teams': 0
|
'teams': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_counts_list_admin(resourced_organization, user, get):
|
def test_org_counts_list_admin(resourced_organization, user, get):
|
||||||
# Check that all types of resources are counted by a superuser
|
# Check that all types of resources are counted by a superuser
|
||||||
@@ -100,6 +106,7 @@ def test_org_counts_list_admin(resourced_organization, user, get):
|
|||||||
counts = response.data['results'][0]['summary_fields']['related_field_counts']
|
counts = response.data['results'][0]['summary_fields']['related_field_counts']
|
||||||
assert counts == COUNTS_PRIMES
|
assert counts == COUNTS_PRIMES
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_counts_list_member(resourced_organization, user, get):
|
def test_org_counts_list_member(resourced_organization, user, get):
|
||||||
# Check that a non-admin user can only see the full project and
|
# Check that a non-admin user can only see the full project and
|
||||||
@@ -119,6 +126,7 @@ def test_org_counts_list_member(resourced_organization, user, get):
|
|||||||
'teams': 0
|
'teams': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_new_org_zero_counts(user, post):
|
def test_new_org_zero_counts(user, post):
|
||||||
# Check that a POST to the organization list endpoint returns
|
# Check that a POST to the organization list endpoint returns
|
||||||
@@ -132,6 +140,7 @@ def test_new_org_zero_counts(user, post):
|
|||||||
counts_dict = new_org_list['summary_fields']['related_field_counts']
|
counts_dict = new_org_list['summary_fields']['related_field_counts']
|
||||||
assert counts_dict == COUNTS_ZEROS
|
assert counts_dict == COUNTS_ZEROS
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_two_organizations(resourced_organization, organizations, user, get):
|
def test_two_organizations(resourced_organization, organizations, user, get):
|
||||||
# Check correct results for two organizations are returned
|
# Check correct results for two organizations are returned
|
||||||
@@ -150,6 +159,7 @@ 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
|
@pytest.mark.django_db
|
||||||
def test_scan_JT_counted(resourced_organization, user, get):
|
def test_scan_JT_counted(resourced_organization, user, get):
|
||||||
admin_user = user('admin', True)
|
admin_user = user('admin', True)
|
||||||
@@ -170,6 +180,7 @@ def test_scan_JT_counted(resourced_organization, user, get):
|
|||||||
assert detail_response.status_code == 200
|
assert detail_response.status_code == 200
|
||||||
assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict
|
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
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ def mock_access_method(mocker):
|
|||||||
mock_method.__name__ = 'bars' # Required for a logging statement
|
mock_method.__name__ = 'bars' # Required for a logging statement
|
||||||
return mock_method
|
return mock_method
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestAccessListCapabilities:
|
class TestAccessListCapabilities:
|
||||||
"""
|
"""
|
||||||
@@ -240,6 +241,7 @@ def test_team_roles_unattach(mocker, team, team_member, inventory, mock_access_m
|
|||||||
inventory.admin_role, team.member_role, 'parents', skip_sub_obj_read_check=True, data={})
|
inventory.admin_role, team.member_role, 'parents', skip_sub_obj_read_check=True, data={})
|
||||||
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
|
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_method, get):
|
def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_method, get):
|
||||||
# Add to same organization so that alice and bob can see each other
|
# Add to same organization so that alice and bob can see each other
|
||||||
@@ -254,6 +256,7 @@ def test_user_roles_unattach(mocker, organization, alice, bob, mock_access_metho
|
|||||||
organization.member_role, alice, 'members', skip_sub_obj_read_check=True, data={})
|
organization.member_role, alice, 'members', skip_sub_obj_read_check=True, data={})
|
||||||
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
|
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_team_roles_unattach_functional(team, team_member, inventory, get):
|
def test_team_roles_unattach_functional(team, team_member, inventory, get):
|
||||||
team.member_role.children.add(inventory.admin_role)
|
team.member_role.children.add(inventory.admin_role)
|
||||||
@@ -262,6 +265,7 @@ def test_team_roles_unattach_functional(team, team_member, inventory, get):
|
|||||||
# the inventory admin_role grants that ability
|
# the inventory admin_role grants that ability
|
||||||
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
|
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_roles_unattach_functional(organization, alice, bob, get):
|
def test_user_roles_unattach_functional(organization, alice, bob, get):
|
||||||
organization.member_role.members.add(alice)
|
organization.member_role.members.add(alice)
|
||||||
@@ -278,6 +282,7 @@ def test_prefetch_jt_capabilities(job_template, rando):
|
|||||||
cache_list_capabilities(qs, ['admin', 'execute'], JobTemplate, rando)
|
cache_list_capabilities(qs, ['admin', 'execute'], JobTemplate, rando)
|
||||||
assert qs[0].capabilities_cache == {'edit': False, 'start': True}
|
assert qs[0].capabilities_cache == {'edit': False, 'start': True}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_prefetch_group_capabilities(group, rando):
|
def test_prefetch_group_capabilities(group, rando):
|
||||||
group.inventory.adhoc_role.members.add(rando)
|
group.inventory.adhoc_role.members.add(rando)
|
||||||
@@ -285,6 +290,7 @@ def test_prefetch_group_capabilities(group, rando):
|
|||||||
cache_list_capabilities(qs, ['inventory.admin', 'inventory.adhoc'], Group, rando)
|
cache_list_capabilities(qs, ['inventory.admin', 'inventory.adhoc'], Group, rando)
|
||||||
assert qs[0].capabilities_cache == {'edit': False, 'adhoc': True}
|
assert qs[0].capabilities_cache == {'edit': False, 'adhoc': True}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_credential, rando):
|
def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_credential, rando):
|
||||||
job_template.project = project
|
job_template.project = project
|
||||||
@@ -309,11 +315,13 @@ def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_c
|
|||||||
]}], JobTemplate, rando)
|
]}], JobTemplate, rando)
|
||||||
assert qs[0].capabilities_cache == {'copy': True}
|
assert qs[0].capabilities_cache == {'copy': True}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_manual_projects_no_update(project, get, admin_user):
|
def test_manual_projects_no_update(project, get, admin_user):
|
||||||
response = get(reverse('api:project_detail', args=[project.pk]), admin_user, expect=200)
|
response = get(reverse('api:project_detail', args=[project.pk]), admin_user, expect=200)
|
||||||
assert not response.data['summary_fields']['user_capabilities']['start']
|
assert not response.data['summary_fields']['user_capabilities']['start']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_group_update_capabilities_possible(group, inventory_source, admin_user):
|
def test_group_update_capabilities_possible(group, inventory_source, admin_user):
|
||||||
group.inventory_source = inventory_source
|
group.inventory_source = inventory_source
|
||||||
@@ -322,6 +330,7 @@ def test_group_update_capabilities_possible(group, inventory_source, admin_user)
|
|||||||
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
|
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
|
||||||
assert capabilities['start']
|
assert capabilities['start']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_group_update_capabilities_impossible(group, inventory_source, admin_user):
|
def test_group_update_capabilities_impossible(group, inventory_source, admin_user):
|
||||||
inventory_source.source = ""
|
inventory_source.source = ""
|
||||||
@@ -332,6 +341,7 @@ def test_group_update_capabilities_impossible(group, inventory_source, admin_use
|
|||||||
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
|
capabilities = get_user_capabilities(admin_user, group, method_list=['start'])
|
||||||
assert not capabilities['start']
|
assert not capabilities['start']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_license_check_not_called(mocker, job_template, project, org_admin, get):
|
def test_license_check_not_called(mocker, job_template, project, org_admin, get):
|
||||||
job_template.project = project
|
job_template.project = project
|
||||||
@@ -340,4 +350,3 @@ def test_license_check_not_called(mocker, job_template, project, org_admin, get)
|
|||||||
with mocker.patch('awx.main.access.BaseAccess.check_license', mock_license_check):
|
with mocker.patch('awx.main.access.BaseAccess.check_license', mock_license_check):
|
||||||
get(reverse('api:job_template_detail', args=[job_template.pk]), org_admin, expect=200)
|
get(reverse('api:job_template_detail', args=[job_template.pk]), org_admin, expect=200)
|
||||||
assert not mock_license_check.called
|
assert not mock_license_check.called
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import pytest
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from awx.main.models import Role
|
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):
|
||||||
project_admin = user('project_admin')
|
project_admin = user('project_admin')
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_admin_visible_to_orphaned_users(get, alice):
|
def test_admin_visible_to_orphaned_users(get, alice):
|
||||||
names = set()
|
names = set()
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ from django.core.urlresolvers import reverse
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.conf.models import Setting
|
from awx.conf.models import Setting
|
||||||
|
|
||||||
'''
|
|
||||||
Ensures that tests don't pick up dev container license file
|
|
||||||
'''
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_no_license_file(mocker):
|
def mock_no_license_file(mocker):
|
||||||
|
'''
|
||||||
|
Ensures that tests don't pick up dev container license file
|
||||||
|
'''
|
||||||
os.environ['AWX_LICENSE_FILE'] = '/does_not_exist'
|
os.environ['AWX_LICENSE_FILE'] = '/does_not_exist'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get, put, patch, delete, admin, enterprise_license):
|
def test_license_cannot_be_removed_via_system_settings(mock_no_license_file, get, put, patch, delete, admin, enterprise_license):
|
||||||
|
|
||||||
url = reverse('api:setting_singleton_detail', args=('system',))
|
url = reverse('api:setting_singleton_detail', args=('system',))
|
||||||
response = get(url, user=admin, expect=200)
|
response = get(url, user=admin, expect=200)
|
||||||
assert not response.data['LICENSE']
|
assert not response.data['LICENSE']
|
||||||
|
|||||||
@@ -16,11 +16,13 @@ def mock_no_surveys(self, add_host=False, feature=None, check_expiration=True):
|
|||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_with_survey(job_template_factory):
|
def job_template_with_survey(job_template_factory):
|
||||||
objects = job_template_factory('jt', project='prj', survey='submitted_email')
|
objects = job_template_factory('jt', project='prj', survey='submitted_email')
|
||||||
return objects.job_template
|
return objects.job_template
|
||||||
|
|
||||||
|
|
||||||
# Survey license-based denial tests
|
# Survey license-based denial tests
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -31,6 +33,7 @@ def test_survey_spec_view_denied(job_template_with_survey, get, admin_user):
|
|||||||
args=(job_template_with_survey.id,)), admin_user, expect=402)
|
args=(job_template_with_survey.id,)), admin_user, expect=402)
|
||||||
assert response.data['detail'] == 'Your license does not allow adding surveys.'
|
assert response.data['detail'] == 'Your license does not allow adding surveys.'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -39,6 +42,7 @@ def test_deny_enabling_survey(deploy_jobtemplate, patch, admin_user):
|
|||||||
data=dict(survey_enabled=True), user=admin_user, expect=402)
|
data=dict(survey_enabled=True), user=admin_user, expect=402)
|
||||||
assert response.data['detail'] == 'Feature surveys is not enabled in the active license.'
|
assert response.data['detail'] == 'Feature surveys is not enabled in the active license.'
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -48,6 +52,7 @@ def test_job_start_blocked_without_survey_license(job_template_with_survey, admi
|
|||||||
with pytest.raises(LicenseForbids):
|
with pytest.raises(LicenseForbids):
|
||||||
access.can_start(job_template_with_survey)
|
access.can_start(job_template_with_survey)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -65,6 +70,7 @@ def test_deny_creating_with_survey(project, post, admin_user):
|
|||||||
user=admin_user, expect=402)
|
user=admin_user, expect=402)
|
||||||
assert response.data['detail'] == 'Feature surveys is not enabled in the active license.'
|
assert response.data['detail'] == 'Feature surveys is not enabled in the active license.'
|
||||||
|
|
||||||
|
|
||||||
# Test normal operations with survey license work
|
# Test normal operations with survey license work
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -73,6 +79,7 @@ def test_survey_spec_view_allowed(deploy_jobtemplate, get, admin_user):
|
|||||||
get(reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)),
|
get(reverse('api:job_template_survey_spec', args=(deploy_jobtemplate.id,)),
|
||||||
admin_user, expect=200)
|
admin_user, expect=200)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -83,6 +90,7 @@ def test_survey_spec_sucessful_creation(survey_spec_factory, job_template, post,
|
|||||||
updated_jt = JobTemplate.objects.get(pk=job_template.pk)
|
updated_jt = JobTemplate.objects.get(pk=job_template.pk)
|
||||||
assert updated_jt.survey_spec == survey_input_data
|
assert updated_jt.survey_spec == survey_input_data
|
||||||
|
|
||||||
|
|
||||||
# Tests related to survey content validation
|
# Tests related to survey content validation
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -96,6 +104,7 @@ def test_survey_spec_non_dict_error(deploy_jobtemplate, post, admin_user):
|
|||||||
user=admin_user, expect=400)
|
user=admin_user, expect=400)
|
||||||
assert response.data['error'] == "Survey question 0 is not a json object."
|
assert response.data['error'] == "Survey question 0 is not a json object."
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -106,6 +115,7 @@ def test_survey_spec_dual_names_error(survey_spec_factory, deploy_jobtemplate, p
|
|||||||
user=user('admin', True), expect=400)
|
user=user('admin', True), expect=400)
|
||||||
assert response.data['error'] == "'variable' 'submitter_email' duplicated in survey question 1."
|
assert response.data['error'] == "'variable' 'submitter_email' duplicated in survey question 1."
|
||||||
|
|
||||||
|
|
||||||
# Test actions that should be allowed with non-survey license
|
# Test actions that should be allowed with non-survey license
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -115,6 +125,7 @@ def test_disable_survey_access_without_license(job_template_with_survey, admin_u
|
|||||||
access = JobTemplateAccess(admin_user)
|
access = JobTemplateAccess(admin_user)
|
||||||
assert access.can_change(job_template_with_survey, dict(survey_enabled=False))
|
assert access.can_change(job_template_with_survey, dict(survey_enabled=False))
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -124,6 +135,7 @@ def test_delete_survey_access_without_license(job_template_with_survey, admin_us
|
|||||||
assert access.can_change(job_template_with_survey, dict(survey_spec=None))
|
assert access.can_change(job_template_with_survey, dict(survey_spec=None))
|
||||||
assert access.can_change(job_template_with_survey, dict(survey_spec={}))
|
assert access.can_change(job_template_with_survey, dict(survey_spec={}))
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -137,6 +149,7 @@ def test_job_start_allowed_with_survey_spec(job_template_factory, admin_user):
|
|||||||
access = JobTemplateAccess(admin_user)
|
access = JobTemplateAccess(admin_user)
|
||||||
assert access.can_start(job_template_with_survey, {})
|
assert access.can_start(job_template_with_survey, {})
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
@@ -146,6 +159,7 @@ def test_job_template_delete_access_with_survey(job_template_with_survey, admin_
|
|||||||
access = JobTemplateAccess(admin_user)
|
access = JobTemplateAccess(admin_user)
|
||||||
assert access.can_delete(job_template_with_survey)
|
assert access.can_delete(job_template_with_survey)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
|
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -157,6 +171,7 @@ def test_delete_survey_spec_without_license(job_template_with_survey, delete, ad
|
|||||||
new_jt = JobTemplate.objects.get(pk=job_template_with_survey.pk)
|
new_jt = JobTemplate.objects.get(pk=job_template_with_survey.pk)
|
||||||
assert new_jt.survey_spec == {}
|
assert new_jt.survey_spec == {}
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', lambda self, **kwargs: True)
|
@mock.patch('awx.main.access.BaseAccess.check_license', lambda self, **kwargs: True)
|
||||||
@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
|
@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
|
||||||
lambda self, extra_vars: mock.MagicMock(spec=Job, id=968))
|
lambda self, extra_vars: mock.MagicMock(spec=Job, id=968))
|
||||||
@@ -174,6 +189,7 @@ def test_launch_survey_enabled_but_no_survey_spec(job_template_factory, post, ad
|
|||||||
dict(extra_vars=dict(survey_var=7)), admin_user, expect=201)
|
dict(extra_vars=dict(survey_var=7)), admin_user, expect=201)
|
||||||
assert 'survey_var' in response.data['ignored_fields']['extra_vars']
|
assert 'survey_var' in response.data['ignored_fields']['extra_vars']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
|
||||||
@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
|
@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
|
||||||
lambda self: mock.MagicMock(spec=Job, id=968))
|
lambda self: mock.MagicMock(spec=Job, id=968))
|
||||||
@@ -191,6 +207,7 @@ def test_launch_with_non_empty_survey_spec_no_license(job_template_factory, post
|
|||||||
obj.save()
|
obj.save()
|
||||||
post(reverse('api:job_template_launch', args=[obj.pk]), {}, admin_user, expect=201)
|
post(reverse('api:job_template_launch', args=[obj.pk]), {}, admin_user, expect=201)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
def test_redact_survey_passwords_in_activity_stream(job_template_with_survey_passwords):
|
def test_redact_survey_passwords_in_activity_stream(job_template_with_survey_passwords):
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ def test_cases(project):
|
|||||||
ret.append(e)
|
ret.append(e)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def negative_test_cases(job_factory):
|
def negative_test_cases(job_factory):
|
||||||
ret = []
|
ret = []
|
||||||
@@ -53,6 +54,7 @@ formats = [
|
|||||||
('html', 'text/html'),
|
('html', 'text/html'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("format,content_type", formats)
|
@pytest.mark.parametrize("format,content_type", formats)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_project_update_redaction_enabled(get, format, content_type, test_cases, admin):
|
def test_project_update_redaction_enabled(get, format, content_type, test_cases, admin):
|
||||||
@@ -66,6 +68,7 @@ def test_project_update_redaction_enabled(get, format, content_type, test_cases,
|
|||||||
assert test_data['uri'].password not in content
|
assert test_data['uri'].password not in content
|
||||||
assert content.count(test_data['uri'].host) == test_data['occurrences']
|
assert content.count(test_data['uri'].host) == test_data['occurrences']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("format,content_type", formats)
|
@pytest.mark.parametrize("format,content_type", formats)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_job_redaction_disabled(get, format, content_type, negative_test_cases, admin):
|
def test_job_redaction_disabled(get, format, content_type, negative_test_cases, admin):
|
||||||
@@ -80,7 +83,6 @@ def test_job_redaction_disabled(get, format, content_type, negative_test_cases,
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_options_fields_choices(instance, options, user):
|
def test_options_fields_choices(instance, options, user):
|
||||||
|
|
||||||
url = reverse('api:unified_job_list')
|
url = reverse('api:unified_job_list')
|
||||||
response = options(url, None, user('admin', True))
|
response = options(url, None, user('admin', True))
|
||||||
|
|
||||||
@@ -89,5 +91,3 @@ def test_options_fields_choices(instance, options, user):
|
|||||||
assert UnifiedJob.LAUNCH_TYPE_CHOICES == response.data['actions']['GET']['launch_type']['choices']
|
assert UnifiedJob.LAUNCH_TYPE_CHOICES == response.data['actions']['GET']['launch_type']['choices']
|
||||||
assert 'choice' == response.data['actions']['GET']['status']['type']
|
assert 'choice' == response.data['actions']['GET']['status']['type']
|
||||||
assert UnifiedJob.STATUS_CHOICES == response.data['actions']['GET']['status']['choices']
|
assert UnifiedJob.STATUS_CHOICES == response.data['actions']['GET']['status']['choices']
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django.core.urlresolvers import reverse
|
|||||||
# user creation
|
# user creation
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_user_create(post, admin):
|
def test_user_create(post, admin):
|
||||||
response = post(reverse('api:user_list'), {
|
response = post(reverse('api:user_list'), {
|
||||||
@@ -19,6 +20,7 @@ def test_user_create(post, admin):
|
|||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_fail_double_create_user(post, admin):
|
def test_fail_double_create_user(post, admin):
|
||||||
response = post(reverse('api:user_list'), {
|
response = post(reverse('api:user_list'), {
|
||||||
@@ -41,6 +43,7 @@ def test_fail_double_create_user(post, admin):
|
|||||||
}, admin)
|
}, admin)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_delete_create_user(post, delete, admin):
|
def test_create_delete_create_user(post, delete, admin):
|
||||||
response = post(reverse('api:user_list'), {
|
response = post(reverse('api:user_list'), {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import time
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_msg_base(inventory, hosts):
|
def fact_msg_base(inventory, hosts):
|
||||||
host_objs = hosts(1)
|
host_objs = hosts(1)
|
||||||
@@ -13,6 +14,7 @@ def fact_msg_base(inventory, hosts):
|
|||||||
'inventory_id': inventory.id
|
'inventory_id': inventory.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_msg_small(fact_msg_base):
|
def fact_msg_small(fact_msg_base):
|
||||||
fact_msg_base['facts'] = {
|
fact_msg_base['facts'] = {
|
||||||
@@ -92,18 +94,20 @@ key of 'ansible'
|
|||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_msg_ansible(fact_msg_base, fact_ansible_json):
|
def fact_msg_ansible(fact_msg_base, fact_ansible_json):
|
||||||
fact_msg_base['facts'] = fact_ansible_json
|
fact_msg_base['facts'] = fact_ansible_json
|
||||||
return fact_msg_base
|
return fact_msg_base
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_msg_packages(fact_msg_base, fact_packages_json):
|
def fact_msg_packages(fact_msg_base, fact_packages_json):
|
||||||
fact_msg_base['facts']['packages'] = fact_packages_json
|
fact_msg_base['facts']['packages'] = fact_packages_json
|
||||||
return fact_msg_base
|
return fact_msg_base
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_msg_services(fact_msg_base, fact_services_json):
|
def fact_msg_services(fact_msg_base, fact_services_json):
|
||||||
fact_msg_base['facts']['services'] = fact_services_json
|
fact_msg_base['facts']['services'] = fact_services_json
|
||||||
return fact_msg_base
|
return fact_msg_base
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,15 @@ from awx.main.management.commands.cleanup_facts import CleanupFacts, Command
|
|||||||
from awx.main.models.fact import Fact
|
from awx.main.models.fact import Fact
|
||||||
from awx.main.models.inventory import Host
|
from awx.main.models.inventory import Host
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_enabled(feature):
|
def mock_feature_enabled(feature):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def mock_feature_disabled(feature):
|
def mock_feature_disabled(feature):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cleanup_granularity(fact_scans, hosts):
|
def test_cleanup_granularity(fact_scans, hosts):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -35,11 +38,12 @@ def test_cleanup_granularity(fact_scans, hosts):
|
|||||||
deleted_count = cleanup_facts.cleanup(timestamp_future, granularity)
|
deleted_count = cleanup_facts.cleanup(timestamp_future, granularity)
|
||||||
assert 60 == deleted_count
|
assert 60 == deleted_count
|
||||||
|
|
||||||
'''
|
|
||||||
Delete half of the scans
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cleanup_older_than(fact_scans, hosts):
|
def test_cleanup_older_than(fact_scans, hosts):
|
||||||
|
'''
|
||||||
|
Delete half of the scans
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
hosts(5)
|
hosts(5)
|
||||||
fact_scans(28, timestamp_epoch=epoch)
|
fact_scans(28, timestamp_epoch=epoch)
|
||||||
@@ -51,6 +55,7 @@ def test_cleanup_older_than(fact_scans, hosts):
|
|||||||
deleted_count = cleanup_facts.cleanup(fact_middle.timestamp, granularity)
|
deleted_count = cleanup_facts.cleanup(fact_middle.timestamp, granularity)
|
||||||
assert 210 == deleted_count
|
assert 210 == deleted_count
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cleanup_older_than_granularity_module(fact_scans, hosts):
|
def test_cleanup_older_than_granularity_module(fact_scans, hosts):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -65,11 +70,11 @@ def test_cleanup_older_than_granularity_module(fact_scans, hosts):
|
|||||||
assert 20 == deleted_count
|
assert 20 == deleted_count
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
Reduce the granularity of half of the facts scans, by half.
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_cleanup_logic(fact_scans, hosts):
|
def test_cleanup_logic(fact_scans, hosts):
|
||||||
|
'''
|
||||||
|
Reduce the granularity of half of the facts scans, by half.
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
hosts = hosts(5)
|
hosts = hosts(5)
|
||||||
fact_scans(60, timestamp_epoch=epoch)
|
fact_scans(60, timestamp_epoch=epoch)
|
||||||
@@ -95,6 +100,7 @@ def test_cleanup_logic(fact_scans, hosts):
|
|||||||
timestamp_pivot -= granularity
|
timestamp_pivot -= granularity
|
||||||
assert fact.timestamp == timestamp_pivot
|
assert fact.timestamp == timestamp_pivot
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_disabled)
|
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_disabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.license_feature
|
@pytest.mark.license_feature
|
||||||
@@ -104,6 +110,7 @@ def test_system_tracking_feature_disabled(mocker):
|
|||||||
cmd.handle(None)
|
cmd.handle(None)
|
||||||
assert 'The System Tracking feature is not enabled for your Tower instance' in err.value
|
assert 'The System Tracking feature is not enabled for your Tower instance' in err.value
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_parameters_ok(mocker):
|
def test_parameters_ok(mocker):
|
||||||
@@ -117,6 +124,7 @@ def test_parameters_ok(mocker):
|
|||||||
cmd.handle(None, **kv)
|
cmd.handle(None, **kv)
|
||||||
run.assert_called_once_with(relativedelta(days=1), relativedelta(days=1), module=None)
|
run.assert_called_once_with(relativedelta(days=1), relativedelta(days=1), module=None)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_string_time_to_timestamp_ok():
|
def test_string_time_to_timestamp_ok():
|
||||||
kvs = [
|
kvs = [
|
||||||
@@ -146,6 +154,7 @@ def test_string_time_to_timestamp_ok():
|
|||||||
res = cmd.string_time_to_timestamp(kv['time'])
|
res = cmd.string_time_to_timestamp(kv['time'])
|
||||||
assert kv['timestamp'] == res
|
assert kv['timestamp'] == res
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_string_time_to_timestamp_invalid():
|
def test_string_time_to_timestamp_invalid():
|
||||||
kvs = [
|
kvs = [
|
||||||
@@ -175,6 +184,7 @@ def test_string_time_to_timestamp_invalid():
|
|||||||
res = cmd.string_time_to_timestamp(kv['time'])
|
res = cmd.string_time_to_timestamp(kv['time'])
|
||||||
assert res is None
|
assert res is None
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled)
|
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled)
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_parameters_fail(mocker):
|
def test_parameters_fail(mocker):
|
||||||
@@ -197,4 +207,3 @@ def test_parameters_fail(mocker):
|
|||||||
with pytest.raises(CommandError) as err:
|
with pytest.raises(CommandError) as err:
|
||||||
cmd.handle(None, older_than=kv['older_than'], granularity=kv['granularity'])
|
cmd.handle(None, older_than=kv['older_than'], granularity=kv['granularity'])
|
||||||
assert kv['msg'] in err.value
|
assert kv['msg'] in err.value
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.core.management import call_command
|
|||||||
|
|
||||||
from awx.main.management.commands.update_password import UpdatePassword
|
from awx.main.management.commands.update_password import UpdatePassword
|
||||||
|
|
||||||
|
|
||||||
def run_command(name, *args, **options):
|
def run_command(name, *args, **options):
|
||||||
command_runner = options.pop('command_runner', call_command)
|
command_runner = options.pop('command_runner', call_command)
|
||||||
stdin_fileobj = options.pop('stdin_fileobj', None)
|
stdin_fileobj = options.pop('stdin_fileobj', None)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from awx.main.management.commands.run_fact_cache_receiver import FactCacheReceiv
|
|||||||
from awx.main.models.fact import Fact
|
from awx.main.models.fact import Fact
|
||||||
from awx.main.models.inventory import Host
|
from awx.main.models.inventory import Host
|
||||||
|
|
||||||
|
|
||||||
# TODO: Check that timestamp and other attributes are as expected
|
# TODO: Check that timestamp and other attributes are as expected
|
||||||
def check_process_fact_message_module(fact_returned, data, module_name):
|
def check_process_fact_message_module(fact_returned, data, module_name):
|
||||||
date_key = data['date_key']
|
date_key = data['date_key']
|
||||||
@@ -36,6 +37,7 @@ def check_process_fact_message_module(fact_returned, data, module_name):
|
|||||||
assert timestamp == fact_returned.timestamp
|
assert timestamp == fact_returned.timestamp
|
||||||
assert module_name == fact_returned.module
|
assert module_name == fact_returned.module
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_process_fact_message_ansible(fact_msg_ansible):
|
def test_process_fact_message_ansible(fact_msg_ansible):
|
||||||
receiver = FactCacheReceiver()
|
receiver = FactCacheReceiver()
|
||||||
@@ -43,6 +45,7 @@ def test_process_fact_message_ansible(fact_msg_ansible):
|
|||||||
|
|
||||||
check_process_fact_message_module(fact_returned, fact_msg_ansible, 'ansible')
|
check_process_fact_message_module(fact_returned, fact_msg_ansible, 'ansible')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_process_fact_message_packages(fact_msg_packages):
|
def test_process_fact_message_packages(fact_msg_packages):
|
||||||
receiver = FactCacheReceiver()
|
receiver = FactCacheReceiver()
|
||||||
@@ -50,6 +53,7 @@ def test_process_fact_message_packages(fact_msg_packages):
|
|||||||
|
|
||||||
check_process_fact_message_module(fact_returned, fact_msg_packages, 'packages')
|
check_process_fact_message_module(fact_returned, fact_msg_packages, 'packages')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_process_fact_message_services(fact_msg_services):
|
def test_process_fact_message_services(fact_msg_services):
|
||||||
receiver = FactCacheReceiver()
|
receiver = FactCacheReceiver()
|
||||||
@@ -57,15 +61,16 @@ def test_process_fact_message_services(fact_msg_services):
|
|||||||
|
|
||||||
check_process_fact_message_module(fact_returned, fact_msg_services, 'services')
|
check_process_fact_message_module(fact_returned, fact_msg_services, 'services')
|
||||||
|
|
||||||
'''
|
|
||||||
We pickypack our fact sending onto the Ansible fact interface.
|
|
||||||
The interface is <hostname, facts>. Where facts is a json blob of all the facts.
|
|
||||||
This makes it hard to decipher what facts are new/changed.
|
|
||||||
Because of this, we handle the same fact module data being sent multiple times
|
|
||||||
and just keep the newest version.
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_process_facts_message_ansible_overwrite(fact_scans, fact_msg_ansible):
|
def test_process_facts_message_ansible_overwrite(fact_scans, fact_msg_ansible):
|
||||||
|
'''
|
||||||
|
We pickypack our fact sending onto the Ansible fact interface.
|
||||||
|
The interface is <hostname, facts>. Where facts is a json blob of all the facts.
|
||||||
|
This makes it hard to decipher what facts are new/changed.
|
||||||
|
Because of this, we handle the same fact module data being sent multiple times
|
||||||
|
and just keep the newest version.
|
||||||
|
'''
|
||||||
#epoch = timezone.now()
|
#epoch = timezone.now()
|
||||||
epoch = datetime.fromtimestamp(fact_msg_ansible['date_key'])
|
epoch = datetime.fromtimestamp(fact_msg_ansible['date_key'])
|
||||||
fact_scans(fact_scans=1, timestamp_epoch=epoch)
|
fact_scans(fact_scans=1, timestamp_epoch=epoch)
|
||||||
@@ -82,6 +87,7 @@ def test_process_facts_message_ansible_overwrite(fact_scans, fact_msg_ansible):
|
|||||||
assert key in fact_obj.facts
|
assert key in fact_obj.facts
|
||||||
assert fact_msg_ansible['facts'] == (json.loads(fact_obj.facts) if isinstance(fact_obj.facts, unicode) else fact_obj.facts) # TODO: Just make response.data['facts'] when we're only dealing with postgres, or if jsonfields ever fixes this bug
|
assert fact_msg_ansible['facts'] == (json.loads(fact_obj.facts) if isinstance(fact_obj.facts, unicode) else fact_obj.facts) # TODO: Just make response.data['facts'] when we're only dealing with postgres, or if jsonfields ever fixes this bug
|
||||||
|
|
||||||
|
|
||||||
# Ensure that the message flows from the socket through to process_fact_message()
|
# Ensure that the message flows from the socket through to process_fact_message()
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_run_receiver(mocker, fact_msg_ansible):
|
def test_run_receiver(mocker, fact_msg_ansible):
|
||||||
|
|||||||
@@ -43,24 +43,27 @@ from awx.main.models.notifications import (
|
|||||||
Notification
|
Notification
|
||||||
)
|
)
|
||||||
|
|
||||||
'''
|
|
||||||
Disable all django model signals.
|
|
||||||
'''
|
|
||||||
@pytest.fixture(scope="session", autouse=False)
|
@pytest.fixture(scope="session", autouse=False)
|
||||||
def disable_signals():
|
def disable_signals():
|
||||||
|
'''
|
||||||
|
Disable all django model signals.
|
||||||
|
'''
|
||||||
mocked = mock.patch('django.dispatch.Signal.send', autospec=True)
|
mocked = mock.patch('django.dispatch.Signal.send', autospec=True)
|
||||||
mocked.start()
|
mocked.start()
|
||||||
|
|
||||||
'''
|
|
||||||
FIXME: Not sure how "far" just setting the BROKER_URL will get us.
|
|
||||||
We may need to incluence CELERY's configuration like we do in the old unit tests (see base.py)
|
|
||||||
|
|
||||||
Allows django signal code to execute without the need for redis
|
|
||||||
'''
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def celery_memory_broker():
|
def celery_memory_broker():
|
||||||
|
'''
|
||||||
|
FIXME: Not sure how "far" just setting the BROKER_URL will get us.
|
||||||
|
We may need to incluence CELERY's configuration like we do in the old unit tests (see base.py)
|
||||||
|
|
||||||
|
Allows django signal code to execute without the need for redis
|
||||||
|
'''
|
||||||
settings.BROKER_URL='memory://localhost/'
|
settings.BROKER_URL='memory://localhost/'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user():
|
def user():
|
||||||
def u(name, is_superuser=False):
|
def u(name, is_superuser=False):
|
||||||
@@ -72,6 +75,7 @@ def user():
|
|||||||
return user
|
return user
|
||||||
return u
|
return u
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def check_jobtemplate(project, inventory, credential):
|
def check_jobtemplate(project, inventory, credential):
|
||||||
return \
|
return \
|
||||||
@@ -83,6 +87,7 @@ def check_jobtemplate(project, inventory, credential):
|
|||||||
name='check-job-template'
|
name='check-job-template'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def deploy_jobtemplate(project, inventory, credential):
|
def deploy_jobtemplate(project, inventory, credential):
|
||||||
return \
|
return \
|
||||||
@@ -94,10 +99,12 @@ def deploy_jobtemplate(project, inventory, credential):
|
|||||||
name='deploy-job-template'
|
name='deploy-job-template'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def team(organization):
|
def team(organization):
|
||||||
return organization.teams.create(name='test-team')
|
return organization.teams.create(name='test-team')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def team_member(user, team):
|
def team_member(user, team):
|
||||||
ret = user('team-member', False)
|
ret = user('team-member', False)
|
||||||
@@ -115,6 +122,7 @@ def project(instance, organization):
|
|||||||
)
|
)
|
||||||
return prj
|
return prj
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def project_factory(organization):
|
def project_factory(organization):
|
||||||
def factory(name):
|
def factory(name):
|
||||||
@@ -128,12 +136,14 @@ def project_factory(organization):
|
|||||||
return prj
|
return prj
|
||||||
return factory
|
return factory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_factory(job_template, admin):
|
def job_factory(job_template, admin):
|
||||||
def factory(job_template=job_template, initial_state='new', created_by=admin):
|
def factory(job_template=job_template, initial_state='new', created_by=admin):
|
||||||
return job_template.create_job(created_by=created_by, status=initial_state)
|
return job_template.create_job(created_by=created_by, status=initial_state)
|
||||||
return factory
|
return factory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def team_factory(organization):
|
def team_factory(organization):
|
||||||
def factory(name):
|
def factory(name):
|
||||||
@@ -146,35 +156,43 @@ def team_factory(organization):
|
|||||||
return t
|
return t
|
||||||
return factory
|
return factory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def user_project(user):
|
def user_project(user):
|
||||||
owner = user('owner')
|
owner = user('owner')
|
||||||
return Project.objects.create(name="test-user-project", created_by=owner, description="test-user-project-desc")
|
return Project.objects.create(name="test-user-project", created_by=owner, description="test-user-project-desc")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def instance(settings):
|
def instance(settings):
|
||||||
return Instance.objects.create(uuid=settings.SYSTEM_UUID, hostname="instance.example.org", capacity=100)
|
return Instance.objects.create(uuid=settings.SYSTEM_UUID, hostname="instance.example.org", capacity=100)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def organization(instance):
|
def organization(instance):
|
||||||
return Organization.objects.create(name="test-org", description="test-org-desc")
|
return Organization.objects.create(name="test-org", description="test-org-desc")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def credential():
|
def credential():
|
||||||
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret')
|
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def machine_credential():
|
def machine_credential():
|
||||||
return Credential.objects.create(name='machine-cred', kind='ssh', username='test_user', password='pas4word')
|
return Credential.objects.create(name='machine-cred', kind='ssh', username='test_user', password='pas4word')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def org_credential(organization):
|
def org_credential(organization):
|
||||||
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret', organization=organization)
|
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret', organization=organization)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def inventory(organization):
|
def inventory(organization):
|
||||||
return organization.inventories.create(name="test-inv")
|
return organization.inventories.create(name="test-inv")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def inventory_factory(organization):
|
def inventory_factory(organization):
|
||||||
def factory(name, org=organization):
|
def factory(name, org=organization):
|
||||||
@@ -185,10 +203,12 @@ def inventory_factory(organization):
|
|||||||
return inv
|
return inv
|
||||||
return factory
|
return factory
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def label(organization):
|
def label(organization):
|
||||||
return organization.labels.create(name="test-label", description="test-label-desc")
|
return organization.labels.create(name="test-label", description="test-label-desc")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def notification_template(organization):
|
def notification_template(organization):
|
||||||
return NotificationTemplate.objects.create(name='test-notification_template',
|
return NotificationTemplate.objects.create(name='test-notification_template',
|
||||||
@@ -197,6 +217,7 @@ def notification_template(organization):
|
|||||||
notification_configuration=dict(url="http://localhost",
|
notification_configuration=dict(url="http://localhost",
|
||||||
headers={"Test": "Header"}))
|
headers={"Test": "Header"}))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def notification(notification_template):
|
def notification(notification_template):
|
||||||
return Notification.objects.create(notification_template=notification_template,
|
return Notification.objects.create(notification_template=notification_template,
|
||||||
@@ -206,27 +227,33 @@ def notification(notification_template):
|
|||||||
recipients='admin@redhat.com',
|
recipients='admin@redhat.com',
|
||||||
subject='email subject')
|
subject='email subject')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_with_survey_passwords(job_template_with_survey_passwords_factory):
|
def job_template_with_survey_passwords(job_template_with_survey_passwords_factory):
|
||||||
return job_template_with_survey_passwords_factory(persisted=True)
|
return job_template_with_survey_passwords_factory(persisted=True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def admin(user):
|
def admin(user):
|
||||||
return user('admin', True)
|
return user('admin', True)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def alice(user):
|
def alice(user):
|
||||||
return user('alice', False)
|
return user('alice', False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def bob(user):
|
def bob(user):
|
||||||
return user('bob', False)
|
return user('bob', False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def rando(user):
|
def rando(user):
|
||||||
"Rando, the random user that doesn't have access to anything"
|
"Rando, the random user that doesn't have access to anything"
|
||||||
return user('rando', False)
|
return user('rando', False)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def org_admin(user, organization):
|
def org_admin(user, organization):
|
||||||
ret = user('org-admin', False)
|
ret = user('org-admin', False)
|
||||||
@@ -234,6 +261,7 @@ def org_admin(user, organization):
|
|||||||
organization.member_role.members.add(ret)
|
organization.member_role.members.add(ret)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def org_auditor(user, organization):
|
def org_auditor(user, organization):
|
||||||
ret = user('org-auditor', False)
|
ret = user('org-auditor', False)
|
||||||
@@ -241,12 +269,14 @@ def org_auditor(user, organization):
|
|||||||
organization.member_role.members.add(ret)
|
organization.member_role.members.add(ret)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def org_member(user, organization):
|
def org_member(user, organization):
|
||||||
ret = user('org-member', False)
|
ret = user('org-member', False)
|
||||||
organization.member_role.members.add(ret)
|
organization.member_role.members.add(ret)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def organizations(instance):
|
def organizations(instance):
|
||||||
def rf(organization_count=1):
|
def rf(organization_count=1):
|
||||||
@@ -257,6 +287,7 @@ def organizations(instance):
|
|||||||
return orgs
|
return orgs
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def group_factory(inventory):
|
def group_factory(inventory):
|
||||||
def g(name):
|
def g(name):
|
||||||
@@ -266,6 +297,7 @@ def group_factory(inventory):
|
|||||||
return Group.objects.create(inventory=inventory, name=name)
|
return Group.objects.create(inventory=inventory, name=name)
|
||||||
return g
|
return g
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def hosts(group_factory):
|
def hosts(group_factory):
|
||||||
group1 = group_factory('group-1')
|
group1 = group_factory('group-1')
|
||||||
@@ -281,23 +313,28 @@ def hosts(group_factory):
|
|||||||
return hosts
|
return hosts
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def group(inventory):
|
def group(inventory):
|
||||||
return inventory.groups.create(name='single-group')
|
return inventory.groups.create(name='single-group')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def inventory_source(group, inventory):
|
def inventory_source(group, inventory):
|
||||||
return InventorySource.objects.create(name=group.name, group=group,
|
return InventorySource.objects.create(name=group.name, group=group,
|
||||||
inventory=inventory, source='gce')
|
inventory=inventory, source='gce')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def inventory_update(inventory_source):
|
def inventory_update(inventory_source):
|
||||||
return InventoryUpdate.objects.create(inventory_source=inventory_source)
|
return InventoryUpdate.objects.create(inventory_source=inventory_source)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def host(group, inventory):
|
def host(group, inventory):
|
||||||
return group.hosts.create(name='single-host', inventory=inventory)
|
return group.hosts.create(name='single-host', inventory=inventory)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def permissions():
|
def permissions():
|
||||||
return {
|
return {
|
||||||
@@ -339,36 +376,42 @@ def _request(verb):
|
|||||||
return response
|
return response
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def post():
|
def post():
|
||||||
return _request('post')
|
return _request('post')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def get():
|
def get():
|
||||||
return _request('get')
|
return _request('get')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def put():
|
def put():
|
||||||
return _request('put')
|
return _request('put')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def patch():
|
def patch():
|
||||||
return _request('patch')
|
return _request('patch')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def delete():
|
def delete():
|
||||||
return _request('delete')
|
return _request('delete')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def head():
|
def head():
|
||||||
return _request('head')
|
return _request('head')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def options():
|
def options():
|
||||||
return _request('options')
|
return _request('options')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_scans(group_factory, fact_ansible_json, fact_packages_json, fact_services_json):
|
def fact_scans(group_factory, fact_ansible_json, fact_packages_json, fact_services_json):
|
||||||
group1 = group_factory('group-1')
|
group1 = group_factory('group-1')
|
||||||
@@ -391,27 +434,33 @@ def fact_scans(group_factory, fact_ansible_json, fact_packages_json, fact_servic
|
|||||||
return facts
|
return facts
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
|
|
||||||
def _fact_json(module_name):
|
def _fact_json(module_name):
|
||||||
current_dir = os.path.dirname(os.path.realpath(__file__))
|
current_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
with open('%s/%s.json' % (current_dir, module_name)) as f:
|
with open('%s/%s.json' % (current_dir, module_name)) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_ansible_json():
|
def fact_ansible_json():
|
||||||
return _fact_json('ansible')
|
return _fact_json('ansible')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_packages_json():
|
def fact_packages_json():
|
||||||
return _fact_json('packages')
|
return _fact_json('packages')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fact_services_json():
|
def fact_services_json():
|
||||||
return _fact_json('services')
|
return _fact_json('services')
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def permission_inv_read(organization, inventory, team):
|
def permission_inv_read(organization, inventory, team):
|
||||||
return Permission.objects.create(inventory=inventory, team=team, permission_type=PERM_INVENTORY_READ)
|
return Permission.objects.create(inventory=inventory, team=team, permission_type=PERM_INVENTORY_READ)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template(organization):
|
def job_template(organization):
|
||||||
jt = JobTemplate(name='test-job_template')
|
jt = JobTemplate(name='test-job_template')
|
||||||
@@ -419,6 +468,7 @@ def job_template(organization):
|
|||||||
|
|
||||||
return jt
|
return jt
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_template_labels(organization, job_template):
|
def job_template_labels(organization, job_template):
|
||||||
job_template.labels.create(name="label-1", organization=organization)
|
job_template.labels.create(name="label-1", organization=organization)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from datetime import datetime
|
|||||||
from awx.main.models import Host
|
from awx.main.models import Host
|
||||||
from awx.main.task_engine import TaskEnhancer
|
from awx.main.task_engine import TaskEnhancer
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_license_writer(inventory, admin):
|
def test_license_writer(inventory, admin):
|
||||||
task_enhancer = TaskEnhancer(
|
task_enhancer = TaskEnhancer(
|
||||||
@@ -50,6 +51,7 @@ def test_license_writer(inventory, admin):
|
|||||||
assert vdata['compliant'] is False
|
assert vdata['compliant'] is False
|
||||||
assert vdata['subscription_name']
|
assert vdata['subscription_name']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_expired_licenses():
|
def test_expired_licenses():
|
||||||
task_enhancer = TaskEnhancer(
|
task_enhancer = TaskEnhancer(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.utils import timezone
|
|||||||
|
|
||||||
from awx.main.models import Fact
|
from awx.main.models import Fact
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_newest_scan_exact(hosts, fact_scans):
|
def test_newest_scan_exact(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -20,18 +21,19 @@ def test_newest_scan_exact(hosts, fact_scans):
|
|||||||
|
|
||||||
assert fact_found == fact_known
|
assert fact_found == fact_known
|
||||||
|
|
||||||
'''
|
|
||||||
Show me the most recent state of the sytem at any point of time.
|
|
||||||
or, said differently
|
|
||||||
For any timestamp, get the first scan that is <= the timestamp.
|
|
||||||
'''
|
|
||||||
|
|
||||||
'''
|
|
||||||
Ensure most recent scan run is the scan returned.
|
|
||||||
Query by future date.
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_newest_scan_less_than(hosts, fact_scans):
|
def test_newest_scan_less_than(hosts, fact_scans):
|
||||||
|
'''
|
||||||
|
Show me the most recent state of the sytem at any point of time.
|
||||||
|
or, said differently
|
||||||
|
For any timestamp, get the first scan that is <= the timestamp.
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
Ensure most recent scan run is the scan returned.
|
||||||
|
Query by future date.
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
timestamp_future = epoch + timedelta(days=10)
|
timestamp_future = epoch + timedelta(days=10)
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
@@ -48,11 +50,12 @@ def test_newest_scan_less_than(hosts, fact_scans):
|
|||||||
|
|
||||||
assert fact_found == fact_known
|
assert fact_found == fact_known
|
||||||
|
|
||||||
'''
|
|
||||||
Tests query Fact that is in the middle of the fact scan timeline, but not an exact timestamp.
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_query_middle_of_timeline(hosts, fact_scans):
|
def test_query_middle_of_timeline(hosts, fact_scans):
|
||||||
|
'''
|
||||||
|
Tests query Fact that is in the middle of the fact scan timeline, but not an exact timestamp.
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
timestamp_middle = epoch + timedelta(days=1, hours=3)
|
timestamp_middle = epoch + timedelta(days=1, hours=3)
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
@@ -69,11 +72,12 @@ def test_query_middle_of_timeline(hosts, fact_scans):
|
|||||||
|
|
||||||
assert fact_found == fact_known
|
assert fact_found == fact_known
|
||||||
|
|
||||||
'''
|
|
||||||
Query time less than any fact scan. Should return None
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_query_result_empty(hosts, fact_scans):
|
def test_query_result_empty(hosts, fact_scans):
|
||||||
|
'''
|
||||||
|
Query time less than any fact scan. Should return None
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
timestamp_less = epoch - timedelta(days=1)
|
timestamp_less = epoch - timedelta(days=1)
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
@@ -83,11 +87,12 @@ def test_query_result_empty(hosts, fact_scans):
|
|||||||
|
|
||||||
assert fact_found is None
|
assert fact_found is None
|
||||||
|
|
||||||
'''
|
|
||||||
Query by fact module other than 'ansible'
|
|
||||||
'''
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_by_module(hosts, fact_scans):
|
def test_by_module(hosts, fact_scans):
|
||||||
|
'''
|
||||||
|
Query by fact module other than 'ansible'
|
||||||
|
'''
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
facts = fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
facts = fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
||||||
@@ -108,4 +113,3 @@ def test_by_module(hosts, fact_scans):
|
|||||||
|
|
||||||
assert fact_found_services == fact_known_services
|
assert fact_found_services == fact_known_services
|
||||||
assert fact_found_packages == fact_known_packages
|
assert fact_found_packages == fact_known_packages
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from django.utils import timezone
|
|||||||
|
|
||||||
from awx.main.models import Fact
|
from awx.main.models import Fact
|
||||||
|
|
||||||
|
|
||||||
def setup_common(hosts, fact_scans, ts_from=None, ts_to=None, epoch=timezone.now(), module_name='ansible', ts_known=None):
|
def setup_common(hosts, fact_scans, ts_from=None, ts_to=None, epoch=timezone.now(), module_name='ansible', ts_known=None):
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
facts = fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
facts = fact_scans(fact_scans=3, timestamp_epoch=epoch)
|
||||||
@@ -20,6 +21,7 @@ def setup_common(hosts, fact_scans, ts_from=None, ts_to=None, epoch=timezone.now
|
|||||||
fact_objs = Fact.get_timeline(hosts[0].id, module=module_name, ts_from=ts_from, ts_to=ts_to)
|
fact_objs = Fact.get_timeline(hosts[0].id, module=module_name, ts_from=ts_from, ts_to=ts_to)
|
||||||
return (facts_known, fact_objs)
|
return (facts_known, fact_objs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_all(hosts, fact_scans):
|
def test_all(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -30,6 +32,7 @@ def test_all(hosts, fact_scans):
|
|||||||
assert 9 == len(facts_known)
|
assert 9 == len(facts_known)
|
||||||
assert 9 == len(fact_objs)
|
assert 9 == len(fact_objs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_all_ansible(hosts, fact_scans):
|
def test_all_ansible(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -43,6 +46,7 @@ def test_all_ansible(hosts, fact_scans):
|
|||||||
for i in xrange(len(facts_known) - 1, 0):
|
for i in xrange(len(facts_known) - 1, 0):
|
||||||
assert facts_known[i].id == fact_objs[i].id
|
assert facts_known[i].id == fact_objs[i].id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_empty_db(hosts, fact_scans):
|
def test_empty_db(hosts, fact_scans):
|
||||||
hosts = hosts(host_count=2)
|
hosts = hosts(host_count=2)
|
||||||
@@ -54,6 +58,7 @@ def test_empty_db(hosts, fact_scans):
|
|||||||
|
|
||||||
assert 0 == len(fact_objs)
|
assert 0 == len(fact_objs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_no_results(hosts, fact_scans):
|
def test_no_results(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -63,6 +68,7 @@ def test_no_results(hosts, fact_scans):
|
|||||||
(facts_known, fact_objs) = setup_common(hosts, fact_scans, ts_from, ts_to, epoch=epoch)
|
(facts_known, fact_objs) = setup_common(hosts, fact_scans, ts_from, ts_to, epoch=epoch)
|
||||||
assert 0 == len(fact_objs)
|
assert 0 == len(fact_objs)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_exact_same_equal(hosts, fact_scans):
|
def test_exact_same_equal(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -74,6 +80,7 @@ def test_exact_same_equal(hosts, fact_scans):
|
|||||||
|
|
||||||
assert facts_known[0].id == fact_objs[0].id
|
assert facts_known[0].id == fact_objs[0].id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_exact_from_exclusive_to_inclusive(hosts, fact_scans):
|
def test_exact_from_exclusive_to_inclusive(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -87,6 +94,7 @@ def test_exact_from_exclusive_to_inclusive(hosts, fact_scans):
|
|||||||
|
|
||||||
assert facts_known[0].id == fact_objs[0].id
|
assert facts_known[0].id == fact_objs[0].id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_to_lte(hosts, fact_scans):
|
def test_to_lte(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -101,6 +109,7 @@ def test_to_lte(hosts, fact_scans):
|
|||||||
for i in xrange(0, len(fact_objs)):
|
for i in xrange(0, len(fact_objs)):
|
||||||
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
|
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_from_gt(hosts, fact_scans):
|
def test_from_gt(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -115,6 +124,7 @@ def test_from_gt(hosts, fact_scans):
|
|||||||
for i in xrange(0, len(fact_objs)):
|
for i in xrange(0, len(fact_objs)):
|
||||||
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
|
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_no_ts(hosts, fact_scans):
|
def test_no_ts(hosts, fact_scans):
|
||||||
epoch = timezone.now()
|
epoch = timezone.now()
|
||||||
@@ -125,5 +135,3 @@ def test_no_ts(hosts, fact_scans):
|
|||||||
|
|
||||||
for i in xrange(len(facts_known) - 1, 0):
|
for i in xrange(len(facts_known) - 1, 0):
|
||||||
assert facts_known[i].id == fact_objs[i].id
|
assert facts_known[i].id == fact_objs[i].id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from django.test import TransactionTestCase
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestWorkflowDAGFunctional(TransactionTestCase):
|
class TestWorkflowDAGFunctional(TransactionTestCase):
|
||||||
|
|
||||||
def workflow_job(self):
|
def workflow_job(self):
|
||||||
wfj = WorkflowJob.objects.create()
|
wfj = WorkflowJob.objects.create()
|
||||||
nodes = [WorkflowJobNode.objects.create(workflow_job=wfj) for i in range(0, 5)]
|
nodes = [WorkflowJobNode.objects.create(workflow_job=wfj) for i in range(0, 5)]
|
||||||
@@ -35,6 +34,7 @@ class TestWorkflowDAGFunctional(TransactionTestCase):
|
|||||||
with self.assertNumQueries(4):
|
with self.assertNumQueries(4):
|
||||||
dag._init_graph(wfj)
|
dag._init_graph(wfj)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestWorkflowJob:
|
class TestWorkflowJob:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -95,9 +95,9 @@ class TestWorkflowJob:
|
|||||||
assert queued_node.get_job_kwargs()['extra_vars'] == {'a': 42, 'b': 43}
|
assert queued_node.get_job_kwargs()['extra_vars'] == {'a': 42, 'b': 43}
|
||||||
assert queued_node.ancestor_artifacts == {'a': 42, 'b': 43}
|
assert queued_node.ancestor_artifacts == {'a': 42, 'b': 43}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestWorkflowJobTemplate:
|
class TestWorkflowJobTemplate:
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def wfjt(self, workflow_job_template_factory):
|
def wfjt(self, workflow_job_template_factory):
|
||||||
wfjt = workflow_job_template_factory('test').workflow_job_template
|
wfjt = workflow_job_template_factory('test').workflow_job_template
|
||||||
@@ -134,6 +134,7 @@ class TestWorkflowJobTemplate:
|
|||||||
assert (test_view.is_valid_relation(nodes[2], node_assoc_1) ==
|
assert (test_view.is_valid_relation(nodes[2], node_assoc_1) ==
|
||||||
{'Error': 'Cannot associate failure_nodes when always_nodes have been associated.'})
|
{'Error': 'Cannot associate failure_nodes when always_nodes have been associated.'})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestWorkflowJobFailure:
|
class TestWorkflowJobFailure:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user