diff --git a/awx/__init__.py b/awx/__init__.py
index 45abf0e7fa..f5457b5caa 100644
--- a/awx/__init__.py
+++ b/awx/__init__.py
@@ -17,6 +17,7 @@ try:
except ImportError: # pragma: no cover
MODE = 'production'
+
def find_commands(management_dir):
# Modified version of function from django/core/management/__init__.py.
command_dir = os.path.join(management_dir, 'commands')
@@ -33,6 +34,7 @@ def find_commands(management_dir):
pass
return commands
+
def prepare_env():
# Update the default settings environment variable based on current mode.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE)
diff --git a/awx/api/authentication.py b/awx/api/authentication.py
index f00950ed85..5f410c8b11 100644
--- a/awx/api/authentication.py
+++ b/awx/api/authentication.py
@@ -21,6 +21,7 @@ from awx.main.models import UnifiedJob, AuthToken
logger = logging.getLogger('awx.api.authentication')
+
class TokenAuthentication(authentication.TokenAuthentication):
'''
Custom token authentication using tokens that expire and are associated
diff --git a/awx/api/filters.py b/awx/api/filters.py
index 08a26735d2..d861303f1e 100644
--- a/awx/api/filters.py
+++ b/awx/api/filters.py
@@ -20,12 +20,14 @@ from rest_framework.filters import BaseFilterBackend
# Ansible Tower
from awx.main.utils import get_type_for_model, to_python_boolean
+
class MongoFilterBackend(BaseFilterBackend):
# FIX: Note that MongoEngine can't use the filter backends from DRF
def filter_queryset(self, request, queryset, view):
return queryset
+
class TypeFilterBackend(BaseFilterBackend):
'''
Filter on type field now returned with all objects.
@@ -62,6 +64,7 @@ class TypeFilterBackend(BaseFilterBackend):
# Return a 400 for invalid field names.
raise ParseError(*e.args)
+
class FieldLookupBackend(BaseFilterBackend):
'''
Filter using field lookups provided via query string parameters.
@@ -229,6 +232,7 @@ class FieldLookupBackend(BaseFilterBackend):
except ValidationError as e:
raise ParseError(e.messages)
+
class OrderByBackend(BaseFilterBackend):
'''
Filter to apply ordering based on query string parameters.
diff --git a/awx/api/generics.py b/awx/api/generics.py
index 6329b3fd62..652e5e0588 100644
--- a/awx/api/generics.py
+++ b/awx/api/generics.py
@@ -42,6 +42,7 @@ __all__ = ['APIView', 'GenericAPIView', 'ListAPIView', 'SimpleListAPIView',
logger = logging.getLogger('awx.api.generics')
+
def get_view_name(cls, suffix=None):
'''
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 views.get_view_name(cls, suffix=None)
+
def get_view_description(cls, html=False):
'''
Wrapper around REST framework get_view_description() to support
@@ -78,6 +80,7 @@ def get_view_description(cls, html=False):
desc = '
%s
' % desc
return mark_safe(desc)
+
class APIView(views.APIView):
def initialize_request(self, request, *args, **kwargs):
@@ -227,11 +230,13 @@ class GenericAPIView(generics.GenericAPIView, APIView):
d['settings'] = settings
return d
+
class SimpleListAPIView(generics.ListAPIView, GenericAPIView):
def get_queryset(self):
return self.request.user.get_queryset(self.model)
+
class ListAPIView(generics.ListAPIView, GenericAPIView):
# Base class for a read-only list view.
@@ -266,10 +271,12 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
fields.append(field.name)
return fields
+
class ListCreateAPIView(ListAPIView, generics.ListCreateAPIView):
# Base class for a list view that allows creating new objects.
pass
+
class ParentMixin(object):
def get_parent_object(self):
@@ -288,6 +295,7 @@ class ParentMixin(object):
if not self.request.user.can_access(*args):
raise PermissionDenied()
+
class SubListAPIView(ListAPIView, ParentMixin):
# Base class for a read-only sublist view.
@@ -315,6 +323,7 @@ class SubListAPIView(ListAPIView, ParentMixin):
sublist_qs = getattrd(parent, self.relationship).distinct()
return qs & sublist_qs
+
class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
# Base class for a sublist view that allows for creating subobjects
# associated with the parent object.
@@ -367,6 +376,7 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
headers = {'Location': obj.get_absolute_url()}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+
class SubListCreateAttachDetachAPIView(SubListCreateAPIView):
# Base class for a sublist view that allows for creating subobjects and
# attaching/detaching them from the parent.
@@ -490,12 +500,15 @@ class DeleteLastUnattachLabelMixin(object):
return res
+
class SubDetailAPIView(generics.RetrieveAPIView, GenericAPIView, ParentMixin):
pass
+
class RetrieveAPIView(generics.RetrieveAPIView, GenericAPIView):
pass
+
class RetrieveUpdateAPIView(RetrieveAPIView, generics.RetrieveUpdateAPIView):
def update(self, request, *args, **kwargs):
@@ -510,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 '''
pass
+
class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
def destroy(self, request, *args, **kwargs):
@@ -520,9 +534,11 @@ class RetrieveDestroyAPIView(RetrieveAPIView, generics.RetrieveDestroyAPIView):
obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+
class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, RetrieveDestroyAPIView):
pass
+
class DestroyAPIView(GenericAPIView, generics.DestroyAPIView):
pass
diff --git a/awx/api/metadata.py b/awx/api/metadata.py
index 54850f285e..6dd186c9ef 100644
--- a/awx/api/metadata.py
+++ b/awx/api/metadata.py
@@ -184,6 +184,7 @@ class Metadata(metadata.SimpleMetadata):
return metadata
+
class RoleMetadata(Metadata):
def determine_metadata(self, request, view):
metadata = super(RoleMetadata, self).determine_metadata(request, view)
diff --git a/awx/api/permissions.py b/awx/api/permissions.py
index cda66ff2ec..a655360dc8 100644
--- a/awx/api/permissions.py
+++ b/awx/api/permissions.py
@@ -21,6 +21,7 @@ logger = logging.getLogger('awx.api.permissions')
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
'TaskPermission', 'ProjectUpdatePermission', 'UserPermission']
+
class ModelAccessPermission(permissions.BasePermission):
'''
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):
return self.has_permission(request, view, obj)
+
class JobTemplateCallbackPermission(ModelAccessPermission):
'''
Permission check used by job template callback view for requests from
@@ -165,6 +167,7 @@ class JobTemplateCallbackPermission(ModelAccessPermission):
else:
return True
+
class TaskPermission(ModelAccessPermission):
'''
Permission checks used for API callbacks from running a task.
@@ -192,6 +195,7 @@ class TaskPermission(ModelAccessPermission):
else:
return False
+
class ProjectUpdatePermission(ModelAccessPermission):
'''
Permission check used by ProjectUpdateView to determine who can update projects
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index 0f4fb338b1..434feb6d0c 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -496,6 +496,7 @@ class BaseSerializer(serializers.ModelSerializer):
class EmptySerializer(serializers.Serializer):
pass
+
class BaseFactSerializer(BaseSerializer):
__metaclass__ = BaseSerializerMetaclass
@@ -509,6 +510,7 @@ class BaseFactSerializer(BaseSerializer):
ret['module'] = serializers.ChoiceField(choices=choices, read_only=True, required=False)
return ret
+
class UnifiedJobTemplateSerializer(BaseSerializer):
class Meta:
@@ -1496,7 +1498,6 @@ class TeamSerializer(BaseSerializer):
return ret
-
class RoleSerializer(BaseSerializer):
class Meta:
@@ -1804,6 +1805,7 @@ class LabelsListMixin(object):
res['labels'] = self._summary_field_labels(obj)
return res
+
class JobOptionsSerializer(LabelsListMixin, BaseSerializer):
class Meta:
@@ -2068,6 +2070,7 @@ class JobRelaunchSerializer(JobSerializer):
attrs = super(JobRelaunchSerializer, self).validate(attrs)
return attrs
+
class AdHocCommandSerializer(UnifiedJobSerializer):
class Meta:
@@ -2168,6 +2171,7 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer):
))
return res
+
class SystemJobSerializer(UnifiedJobSerializer):
class Meta:
@@ -2184,6 +2188,7 @@ class SystemJobSerializer(UnifiedJobSerializer):
res['cancel'] = reverse('api:system_job_cancel', args=(obj.pk,))
return res
+
class SystemJobCancelSerializer(SystemJobSerializer):
can_cancel = serializers.BooleanField(read_only=True)
@@ -2191,6 +2196,7 @@ class SystemJobCancelSerializer(SystemJobSerializer):
class Meta:
fields = ('can_cancel',)
+
class WorkflowJobTemplateSerializer(LabelsListMixin, UnifiedJobTemplateSerializer):
show_capabilities = ['start', 'edit', 'delete']
@@ -2216,10 +2222,12 @@ class WorkflowJobTemplateSerializer(LabelsListMixin, UnifiedJobTemplateSerialize
def validate_extra_vars(self, value):
return vars_validate_or_raise(value)
+
# TODO:
class WorkflowJobTemplateListSerializer(WorkflowJobTemplateSerializer):
pass
+
# TODO:
class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
@@ -2247,10 +2255,12 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
ret['extra_vars'] = obj.display_extra_vars()
return ret
+
# TODO:
class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer):
pass
+
class WorkflowJobCancelSerializer(WorkflowJobSerializer):
can_cancel = serializers.BooleanField(read_only=True)
@@ -2346,6 +2356,7 @@ class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer):
"unified_job_template": _("Can not nest a %s inside a WorkflowJobTemplate") % ujt_obj.__class__.__name__})
return super(WorkflowJobTemplateNodeSerializer, self).validate(attrs)
+
class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
class Meta:
model = WorkflowJobNode
@@ -2362,14 +2373,16 @@ class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer):
res['workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job.pk,))
return res
+
class WorkflowJobNodeListSerializer(WorkflowJobNodeSerializer):
pass
+
class WorkflowJobNodeDetailSerializer(WorkflowJobNodeSerializer):
pass
-class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer):
+class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer):
'''
Influence the api browser sample data to not include workflow_job_template
when editing a WorkflowNode.
@@ -2384,18 +2397,23 @@ class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer)
field_kwargs.pop('queryset', None)
return field_class, field_kwargs
+
class WorkflowJobTemplateNodeListSerializer(WorkflowJobTemplateNodeSerializer):
pass
+
class JobListSerializer(JobSerializer, UnifiedJobListSerializer):
pass
+
class AdHocCommandListSerializer(AdHocCommandSerializer, UnifiedJobListSerializer):
pass
+
class SystemJobListSerializer(SystemJobSerializer, UnifiedJobListSerializer):
pass
+
class JobHostSummarySerializer(BaseSerializer):
class Meta:
@@ -2610,6 +2628,7 @@ class JobLaunchSerializer(BaseSerializer):
obj.credential = JT_credential
return attrs
+
class WorkflowJobLaunchSerializer(BaseSerializer):
can_start_without_user_input = serializers.BooleanField(read_only=True)
@@ -2668,6 +2687,7 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
obj.extra_vars = WFJT_extra_vars
return attrs
+
class NotificationTemplateSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
@@ -2757,6 +2777,7 @@ class NotificationTemplateSerializer(BaseSerializer):
raise serializers.ValidationError(error_list)
return attrs
+
class NotificationSerializer(BaseSerializer):
class Meta:
@@ -2771,6 +2792,7 @@ class NotificationSerializer(BaseSerializer):
))
return res
+
class LabelSerializer(BaseSerializer):
class Meta:
@@ -2783,6 +2805,7 @@ class LabelSerializer(BaseSerializer):
res['organization'] = reverse('api:organization_detail', args=(obj.organization.pk,))
return res
+
class ScheduleSerializer(BaseSerializer):
show_capabilities = ['edit', 'delete']
@@ -2856,6 +2879,7 @@ class ScheduleSerializer(BaseSerializer):
raise serializers.ValidationError(_("rrule parsing failed validation."))
return value
+
class ActivityStreamSerializer(BaseSerializer):
changes = serializers.SerializerMethodField()
@@ -2999,6 +3023,7 @@ class FactVersionSerializer(BaseFactSerializer):
res['fact_view'] = build_url('api:host_fact_compare_view', args=(obj.host.pk,), get=params)
return res
+
class FactSerializer(BaseFactSerializer):
class Meta:
diff --git a/awx/api/views.py b/awx/api/views.py
index ee219894f8..44d8f8b419 100644
--- a/awx/api/views.py
+++ b/awx/api/views.py
@@ -76,6 +76,7 @@ from awx.main.scheduler.tasks import run_job_complete
logger = logging.getLogger('awx.api.views')
+
def api_exception_handler(exc, context):
'''
Override default API exception handler to catch IntegrityError exceptions.
@@ -105,6 +106,7 @@ class ApiRootView(APIView):
)
return Response(data)
+
class ApiV1RootView(APIView):
authentication_classes = []
@@ -386,6 +388,7 @@ class DashboardView(APIView):
'total': job_template_list.count()}
return Response(data)
+
class DashboardJobsGraphView(APIView):
view_name = _("Dashboard Jobs Graphs")
@@ -443,12 +446,14 @@ class ScheduleList(ListAPIView):
serializer_class = ScheduleSerializer
new_in_148 = True
+
class ScheduleDetail(RetrieveUpdateDestroyAPIView):
model = Schedule
serializer_class = ScheduleSerializer
new_in_148 = True
+
class ScheduleUnifiedJobsList(SubListAPIView):
model = UnifiedJob
@@ -458,6 +463,7 @@ class ScheduleUnifiedJobsList(SubListAPIView):
view_name = _('Schedule Jobs List')
new_in_148 = True
+
class AuthView(APIView):
authentication_classes = []
@@ -497,6 +503,7 @@ class AuthView(APIView):
data[name] = backend_data
return Response(data)
+
class AuthTokenView(APIView):
authentication_classes = []
@@ -554,6 +561,7 @@ class AuthTokenView(APIView):
logger.warning(smart_text(u"Login failed for user {}".format(request.data['username'])))
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
class OrganizationCountsMixin(object):
def get_serializer_context(self, *args, **kwargs):
@@ -635,6 +643,7 @@ class OrganizationCountsMixin(object):
return full_context
+
class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
model = Organization
@@ -663,6 +672,7 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
# Okay, create the organization as usual.
return super(OrganizationList, self).create(request, *args, **kwargs)
+
class OrganizationDetail(RetrieveUpdateDestroyAPIView):
model = Organization
@@ -702,6 +712,7 @@ class OrganizationDetail(RetrieveUpdateDestroyAPIView):
return full_context
+
class OrganizationInventoriesList(SubListAPIView):
model = Inventory
@@ -726,6 +737,7 @@ class BaseUsersList(SubListCreateAttachDetachAPIView):
pass
return ret
+
class OrganizationUsersList(BaseUsersList):
model = User
@@ -733,6 +745,7 @@ class OrganizationUsersList(BaseUsersList):
parent_model = Organization
relationship = 'member_role.members'
+
class OrganizationAdminsList(BaseUsersList):
model = User
@@ -740,6 +753,7 @@ class OrganizationAdminsList(BaseUsersList):
parent_model = Organization
relationship = 'admin_role.members'
+
class OrganizationProjectsList(SubListCreateAttachDetachAPIView):
model = Project
@@ -748,6 +762,7 @@ class OrganizationProjectsList(SubListCreateAttachDetachAPIView):
relationship = 'projects'
parent_key = 'organization'
+
class OrganizationTeamsList(SubListCreateAttachDetachAPIView):
model = Team
@@ -756,6 +771,7 @@ class OrganizationTeamsList(SubListCreateAttachDetachAPIView):
relationship = 'teams'
parent_key = 'organization'
+
class OrganizationActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -774,6 +790,7 @@ class OrganizationActivityStreamList(SubListAPIView):
# Okay, let it through.
return super(OrganizationActivityStreamList, self).get(request, *args, **kwargs)
+
class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -782,6 +799,7 @@ class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView):
relationship = 'notification_templates'
parent_key = 'organization'
+
class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -789,6 +807,7 @@ class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView)
parent_model = Organization
relationship = 'notification_templates_any'
+
class OrganizationNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -796,6 +815,7 @@ class OrganizationNotificationTemplatesErrorList(SubListCreateAttachDetachAPIVie
parent_model = Organization
relationship = 'notification_templates_error'
+
class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -803,12 +823,14 @@ class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIV
parent_model = Organization
relationship = 'notification_templates_success'
+
class OrganizationAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = Organization
new_in_300 = True
+
class OrganizationObjectRolesList(SubListAPIView):
model = Role
@@ -821,6 +843,7 @@ class OrganizationObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class TeamList(ListCreateAPIView):
model = Team
@@ -831,11 +854,13 @@ class TeamList(ListCreateAPIView):
qs = qs.select_related('admin_role', 'read_role', 'member_role', 'organization')
return qs
+
class TeamDetail(RetrieveUpdateDestroyAPIView):
model = Team
serializer_class = TeamSerializer
+
class TeamUsersList(BaseUsersList):
model = User
@@ -880,6 +905,7 @@ class TeamRolesList(SubListCreateAttachDetachAPIView):
return super(TeamRolesList, self).post(request, *args, **kwargs)
+
class TeamObjectRolesList(SubListAPIView):
model = Role
@@ -892,6 +918,7 @@ class TeamObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class TeamProjectsList(SubListAPIView):
model = Project
@@ -909,6 +936,7 @@ class TeamProjectsList(SubListAPIView):
)
return self.model.accessible_objects(self.request.user, 'read_role').filter(pk__in=[t.content_object.pk for t in proj_roles])
+
class TeamActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -936,12 +964,14 @@ class TeamActivityStreamList(SubListAPIView):
Q(project__in=Project.accessible_objects(parent, 'read_role')) |
Q(credential__in=Credential.accessible_objects(parent, 'read_role')))
+
class TeamAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = Team
new_in_300 = True
+
class ProjectList(ListCreateAPIView):
model = Project
@@ -959,6 +989,7 @@ class ProjectList(ListCreateAPIView):
)
return projects_qs
+
class ProjectDetail(RetrieveUpdateDestroyAPIView):
model = Project
@@ -973,11 +1004,13 @@ class ProjectDetail(RetrieveUpdateDestroyAPIView):
pu.cancel()
return super(ProjectDetail, self).destroy(request, *args, **kwargs)
+
class ProjectPlaybooks(RetrieveAPIView):
model = Project
serializer_class = ProjectPlaybooksSerializer
+
class ProjectTeamsList(ListAPIView):
model = Team
@@ -992,6 +1025,7 @@ class ProjectTeamsList(ListAPIView):
all_roles = Role.objects.filter(Q(descendents__content_type=project_ct) & Q(descendents__object_id=p.pk), content_type=team_ct)
return self.model.accessible_objects(self.request.user, 'read_role').filter(pk__in=[t.content_object.pk for t in all_roles])
+
class ProjectSchedulesList(SubListCreateAttachDetachAPIView):
view_name = _("Project Schedules")
@@ -1003,6 +1037,7 @@ class ProjectSchedulesList(SubListCreateAttachDetachAPIView):
parent_key = 'unified_job_template'
new_in_148 = True
+
class ProjectActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1031,6 +1066,7 @@ class ProjectActivityStreamList(SubListAPIView):
return qs.filter(project=parent)
return qs.filter(Q(project=parent) | Q(credential=parent.credential))
+
class ProjectNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -1038,6 +1074,7 @@ class ProjectNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
parent_model = Project
relationship = 'notification_templates_any'
+
class ProjectNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -1045,6 +1082,7 @@ class ProjectNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
parent_model = Project
relationship = 'notification_templates_error'
+
class ProjectNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -1052,6 +1090,7 @@ class ProjectNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
parent_model = Project
relationship = 'notification_templates_success'
+
class ProjectUpdatesList(SubListAPIView):
model = ProjectUpdate
@@ -1060,6 +1099,7 @@ class ProjectUpdatesList(SubListAPIView):
relationship = 'project_updates'
new_in_13 = True
+
class ProjectUpdateView(RetrieveAPIView):
model = Project
@@ -1081,11 +1121,13 @@ class ProjectUpdateView(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class ProjectUpdateList(ListAPIView):
model = ProjectUpdate
serializer_class = ProjectUpdateListSerializer
+
class ProjectUpdateDetail(RetrieveDestroyAPIView):
model = ProjectUpdate
@@ -1098,6 +1140,7 @@ class ProjectUpdateDetail(RetrieveDestroyAPIView):
raise PermissionDenied(detail=_('Can not delete job resource when associated workflow job is running.'))
return super(ProjectUpdateDetail, self).destroy(request, *args, **kwargs)
+
class ProjectUpdateCancel(RetrieveAPIView):
model = ProjectUpdate
@@ -1113,6 +1156,7 @@ class ProjectUpdateCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class ProjectUpdateNotificationsList(SubListAPIView):
model = Notification
@@ -1120,12 +1164,14 @@ class ProjectUpdateNotificationsList(SubListAPIView):
parent_model = ProjectUpdate
relationship = 'notifications'
+
class ProjectAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = Project
new_in_300 = True
+
class ProjectObjectRolesList(SubListAPIView):
model = Role
@@ -1138,6 +1184,7 @@ class ProjectObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class UserList(ListCreateAPIView):
model = User
@@ -1159,6 +1206,7 @@ class UserList(ListCreateAPIView):
pass
return ret
+
class UserMeList(ListAPIView):
model = User
@@ -1168,6 +1216,7 @@ class UserMeList(ListAPIView):
def get_queryset(self):
return self.model.objects.filter(pk=self.request.user.pk)
+
class UserTeamsList(ListAPIView):
model = User
@@ -1179,6 +1228,7 @@ class UserTeamsList(ListAPIView):
raise PermissionDenied()
return Team.accessible_objects(self.request.user, 'read_role').filter(member_role__members=u)
+
class UserRolesList(SubListCreateAttachDetachAPIView):
model = Role
@@ -1230,6 +1280,7 @@ class UserRolesList(SubListCreateAttachDetachAPIView):
# We hide roles that shouldn't be seen in our queryset
return True
+
class UserProjectsList(SubListAPIView):
model = Project
@@ -1243,6 +1294,7 @@ class UserProjectsList(SubListAPIView):
user_qs = Project.accessible_objects(parent, 'read_role')
return my_qs & user_qs
+
class UserOrganizationsList(OrganizationCountsMixin, SubListAPIView):
model = Organization
@@ -1257,6 +1309,7 @@ class UserOrganizationsList(OrganizationCountsMixin, SubListAPIView):
user_qs = Organization.objects.filter(member_role__members=parent)
return my_qs & user_qs
+
class UserAdminOfOrganizationsList(OrganizationCountsMixin, SubListAPIView):
model = Organization
@@ -1271,6 +1324,7 @@ class UserAdminOfOrganizationsList(OrganizationCountsMixin, SubListAPIView):
user_qs = Organization.objects.filter(admin_role__members=parent)
return my_qs & user_qs
+
class UserActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1333,6 +1387,7 @@ class UserDetail(RetrieveUpdateDestroyAPIView):
raise PermissionDenied(_('Cannot delete user.'))
return super(UserDetail, self).destroy(request, *args, **kwargs)
+
class UserAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
@@ -1430,6 +1485,7 @@ class CredentialDetail(RetrieveUpdateDestroyAPIView):
model = Credential
serializer_class = CredentialSerializer
+
class CredentialActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1448,12 +1504,14 @@ class CredentialActivityStreamList(SubListAPIView):
# Okay, let it through.
return super(CredentialActivityStreamList, self).get(request, *args, **kwargs)
+
class CredentialAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = Credential
new_in_300 = True
+
class CredentialObjectRolesList(SubListAPIView):
model = Role
@@ -1466,11 +1524,13 @@ class CredentialObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class InventoryScriptList(ListCreateAPIView):
model = CustomInventoryScript
serializer_class = CustomInventoryScriptSerializer
+
class InventoryScriptDetail(RetrieveUpdateDestroyAPIView):
model = CustomInventoryScript
@@ -1486,6 +1546,7 @@ class InventoryScriptDetail(RetrieveUpdateDestroyAPIView):
inv_src.save()
return super(InventoryScriptDetail, self).destroy(request, *args, **kwargs)
+
class InventoryScriptObjectRolesList(SubListAPIView):
model = Role
@@ -1498,6 +1559,7 @@ class InventoryScriptObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class InventoryList(ListCreateAPIView):
model = Inventory
@@ -1509,6 +1571,7 @@ class InventoryList(ListCreateAPIView):
qs = qs.select_related('admin_role', 'read_role', 'update_role', 'use_role', 'adhoc_role')
return qs
+
class InventoryDetail(RetrieveUpdateDestroyAPIView):
model = Inventory
@@ -1519,6 +1582,7 @@ class InventoryDetail(RetrieveUpdateDestroyAPIView):
with ignore_inventory_group_removal():
return super(InventoryDetail, self).destroy(request, *args, **kwargs)
+
class InventoryActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1543,12 +1607,14 @@ class InventoryActivityStreamList(SubListAPIView):
qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(inventory=parent) | Q(host__in=parent.hosts.all()) | Q(group__in=parent.groups.all()))
+
class InventoryAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = Inventory
new_in_300 = True
+
class InventoryObjectRolesList(SubListAPIView):
model = Role
@@ -1561,6 +1627,7 @@ class InventoryObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class InventoryJobTemplateList(SubListAPIView):
model = JobTemplate
@@ -1575,6 +1642,7 @@ class InventoryJobTemplateList(SubListAPIView):
qs = self.request.user.get_queryset(self.model)
return qs.filter(inventory=parent)
+
class InventoryScanJobTemplateList(SubListAPIView):
model = JobTemplate
@@ -1589,17 +1657,20 @@ class InventoryScanJobTemplateList(SubListAPIView):
qs = self.request.user.get_queryset(self.model)
return qs.filter(job_type=PERM_INVENTORY_SCAN, inventory=parent)
+
class HostList(ListCreateAPIView):
always_allow_superuser = False
model = Host
serializer_class = HostSerializer
+
class HostDetail(RetrieveUpdateDestroyAPIView):
model = Host
serializer_class = HostSerializer
+
class InventoryHostsList(SubListCreateAttachDetachAPIView):
model = Host
@@ -1608,6 +1679,7 @@ class InventoryHostsList(SubListCreateAttachDetachAPIView):
relationship = 'hosts'
parent_key = 'inventory'
+
class HostGroupsList(SubListCreateAttachDetachAPIView):
''' the list of groups a host is directly a member of '''
@@ -1629,6 +1701,7 @@ class HostGroupsList(SubListCreateAttachDetachAPIView):
data['inventory'] = self.get_parent_object().inventory_id
return super(HostGroupsList, self).create(request, *args, **kwargs)
+
class HostAllGroupsList(SubListAPIView):
''' the list of all groups of which the host is directly or indirectly a member '''
@@ -1644,6 +1717,7 @@ class HostAllGroupsList(SubListAPIView):
sublist_qs = parent.all_groups.distinct()
return qs & sublist_qs
+
class HostInventorySourcesList(SubListAPIView):
model = InventorySource
@@ -1652,6 +1726,7 @@ class HostInventorySourcesList(SubListAPIView):
relationship = 'inventory_sources'
new_in_148 = True
+
class HostActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1676,6 +1751,7 @@ class HostActivityStreamList(SubListAPIView):
qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(host=parent) | Q(inventory=parent.inventory))
+
class SystemTrackingEnforcementMixin(APIView):
'''
Use check_permissions instead of initial() because it's in the OPTION's path as well
@@ -1686,6 +1762,7 @@ class SystemTrackingEnforcementMixin(APIView):
"of system tracking."))
return super(SystemTrackingEnforcementMixin, self).check_permissions(request)
+
class HostFactVersionsList(ListAPIView, ParentMixin, SystemTrackingEnforcementMixin):
model = Fact
@@ -1711,6 +1788,7 @@ class HostFactVersionsList(ListAPIView, ParentMixin, SystemTrackingEnforcementMi
queryset = self.get_queryset() or []
return Response(dict(results=self.serializer_class(queryset, many=True).data))
+
class HostFactCompareView(SubDetailAPIView, SystemTrackingEnforcementMixin):
model = Fact
@@ -1730,6 +1808,7 @@ class HostFactCompareView(SubDetailAPIView, SystemTrackingEnforcementMixin):
return Response({'detail': _('Fact not found.')}, status=status.HTTP_404_NOT_FOUND)
return Response(self.serializer_class(instance=fact_entry).data)
+
class GroupList(ListCreateAPIView):
model = Group
@@ -1765,6 +1844,7 @@ class EnforceParentRelationshipMixin(object):
data[self.enforce_parent_relationship] = getattr(self.get_parent_object(), '%s_id' % self.enforce_parent_relationship)
return super(EnforceParentRelationshipMixin, self).create(request, *args, **kwargs)
+
class GroupChildrenList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
model = Group
@@ -1773,6 +1853,7 @@ class GroupChildrenList(EnforceParentRelationshipMixin, SubListCreateAttachDetac
relationship = 'children'
enforce_parent_relationship = 'inventory'
+
class GroupPotentialChildrenList(SubListAPIView):
model = Group
@@ -1790,6 +1871,7 @@ class GroupPotentialChildrenList(SubListAPIView):
except_pks.update(parent.all_children.values_list('pk', flat=True))
return qs.exclude(pk__in=except_pks)
+
class GroupHostsList(SubListCreateAttachDetachAPIView):
''' the list of hosts directly below a group '''
@@ -1815,6 +1897,7 @@ class GroupHostsList(SubListCreateAttachDetachAPIView):
return self.attach(request, *args, **kwargs)
return super(GroupHostsList, self).create(request, *args, **kwargs)
+
class GroupAllHostsList(SubListAPIView):
''' the list of all hosts below a group, even including subgroups '''
@@ -1830,6 +1913,7 @@ class GroupAllHostsList(SubListAPIView):
sublist_qs = parent.all_hosts.distinct()
return qs & sublist_qs
+
class GroupInventorySourcesList(SubListAPIView):
model = InventorySource
@@ -1838,6 +1922,7 @@ class GroupInventorySourcesList(SubListAPIView):
relationship = 'inventory_sources'
new_in_148 = True
+
class GroupActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -1862,6 +1947,7 @@ class GroupActivityStreamList(SubListAPIView):
qs = self.request.user.get_queryset(self.model)
return qs.filter(Q(group=parent) | Q(host__in=parent.hosts.all()))
+
class GroupDetail(RetrieveUpdateDestroyAPIView):
model = Group
@@ -1874,6 +1960,7 @@ class GroupDetail(RetrieveUpdateDestroyAPIView):
obj.delete_recursive()
return Response(status=status.HTTP_204_NO_CONTENT)
+
class InventoryGroupsList(SubListCreateAttachDetachAPIView):
model = Group
@@ -1882,6 +1969,7 @@ class InventoryGroupsList(SubListCreateAttachDetachAPIView):
relationship = 'groups'
parent_key = 'inventory'
+
class InventoryRootGroupsList(SubListCreateAttachDetachAPIView):
model = Group
@@ -1896,27 +1984,32 @@ class InventoryRootGroupsList(SubListCreateAttachDetachAPIView):
qs = self.request.user.get_queryset(self.model).distinct() # need distinct for '&' operator
return qs & parent.root_groups
+
class BaseVariableData(RetrieveUpdateAPIView):
parser_classes = api_settings.DEFAULT_PARSER_CLASSES + [YAMLParser]
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [YAMLRenderer]
is_variable_data = True # Special flag for permissions check.
+
class InventoryVariableData(BaseVariableData):
model = Inventory
serializer_class = InventoryVariableDataSerializer
+
class HostVariableData(BaseVariableData):
model = Host
serializer_class = HostVariableDataSerializer
+
class GroupVariableData(BaseVariableData):
model = Group
serializer_class = GroupVariableDataSerializer
+
class InventoryScriptView(RetrieveAPIView):
model = Inventory
@@ -2002,6 +2095,7 @@ class InventoryScriptView(RetrieveAPIView):
return Response(data)
+
class InventoryTreeView(RetrieveAPIView):
model = Inventory
@@ -2034,6 +2128,7 @@ class InventoryTreeView(RetrieveAPIView):
group_children_map)
return Response(tree_data)
+
class InventoryInventorySourcesList(SubListAPIView):
model = InventorySource
@@ -2050,12 +2145,14 @@ class InventoryInventorySourcesList(SubListAPIView):
return qs.filter(Q(inventory__pk=parent.pk) |
Q(group__inventory__pk=parent.pk))
+
class InventorySourceList(ListAPIView):
model = InventorySource
serializer_class = InventorySourceSerializer
new_in_14 = True
+
class InventorySourceDetail(RetrieveUpdateAPIView):
model = InventorySource
@@ -2071,6 +2168,7 @@ class InventorySourceDetail(RetrieveUpdateAPIView):
pu.cancel()
return super(InventorySourceDetail, self).destroy(request, *args, **kwargs)
+
class InventorySourceSchedulesList(SubListCreateAttachDetachAPIView):
view_name = _("Inventory Source Schedules")
@@ -2082,6 +2180,7 @@ class InventorySourceSchedulesList(SubListCreateAttachDetachAPIView):
parent_key = 'unified_job_template'
new_in_148 = True
+
class InventorySourceActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -2100,6 +2199,7 @@ class InventorySourceActivityStreamList(SubListAPIView):
# Okay, let it through.
return super(InventorySourceActivityStreamList, self).get(request, *args, **kwargs)
+
class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2115,14 +2215,17 @@ class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIVi
status=status.HTTP_400_BAD_REQUEST)
return super(InventorySourceNotificationTemplatesAnyList, self).post(request, *args, **kwargs)
+
class InventorySourceNotificationTemplatesErrorList(InventorySourceNotificationTemplatesAnyList):
relationship = 'notification_templates_error'
+
class InventorySourceNotificationTemplatesSuccessList(InventorySourceNotificationTemplatesAnyList):
relationship = 'notification_templates_success'
+
class InventorySourceHostsList(SubListAPIView):
model = Host
@@ -2131,6 +2234,7 @@ class InventorySourceHostsList(SubListAPIView):
relationship = 'hosts'
new_in_148 = True
+
class InventorySourceGroupsList(SubListAPIView):
model = Group
@@ -2139,6 +2243,7 @@ class InventorySourceGroupsList(SubListAPIView):
relationship = 'groups'
new_in_148 = True
+
class InventorySourceUpdatesList(SubListAPIView):
model = InventoryUpdate
@@ -2147,6 +2252,7 @@ class InventorySourceUpdatesList(SubListAPIView):
relationship = 'inventory_updates'
new_in_14 = True
+
class InventorySourceUpdateView(RetrieveAPIView):
model = InventorySource
@@ -2166,11 +2272,13 @@ class InventorySourceUpdateView(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class InventoryUpdateList(ListAPIView):
model = InventoryUpdate
serializer_class = InventoryUpdateListSerializer
+
class InventoryUpdateDetail(RetrieveDestroyAPIView):
model = InventoryUpdate
@@ -2183,6 +2291,7 @@ class InventoryUpdateDetail(RetrieveDestroyAPIView):
raise PermissionDenied(detail=_('Can not delete job resource when associated workflow job is running.'))
return super(InventoryUpdateDetail, self).destroy(request, *args, **kwargs)
+
class InventoryUpdateCancel(RetrieveAPIView):
model = InventoryUpdate
@@ -2198,6 +2307,7 @@ class InventoryUpdateCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class InventoryUpdateNotificationsList(SubListAPIView):
model = Notification
@@ -2205,6 +2315,7 @@ class InventoryUpdateNotificationsList(SubListAPIView):
parent_model = InventoryUpdate
relationship = 'notifications'
+
class JobTemplateList(ListCreateAPIView):
model = JobTemplate
@@ -2222,12 +2333,14 @@ class JobTemplateList(ListCreateAPIView):
job_template.admin_role.members.add(request.user)
return ret
+
class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
model = JobTemplate
serializer_class = JobTemplateSerializer
always_allow_superuser = False
+
class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
model = JobTemplate
@@ -2298,6 +2411,7 @@ class JobTemplateLaunch(RetrieveAPIView, GenericAPIView):
data['job'] = new_job.id
return Response(data, status=status.HTTP_201_CREATED)
+
class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
view_name = _("Job Template Schedules")
@@ -2309,6 +2423,7 @@ class JobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
parent_key = 'unified_job_template'
new_in_148 = True
+
class JobTemplateSurveySpec(GenericAPIView):
model = JobTemplate
@@ -2375,11 +2490,13 @@ class JobTemplateSurveySpec(GenericAPIView):
obj.save()
return Response()
+
class WorkflowJobTemplateSurveySpec(JobTemplateSurveySpec):
model = WorkflowJobTemplate
parent_model = WorkflowJobTemplate
+
class JobTemplateActivityStreamList(SubListAPIView):
model = ActivityStream
@@ -2398,6 +2515,7 @@ class JobTemplateActivityStreamList(SubListAPIView):
# Okay, let it through.
return super(JobTemplateActivityStreamList, self).get(request, *args, **kwargs)
+
class JobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2405,6 +2523,7 @@ class JobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
parent_model = JobTemplate
relationship = 'notification_templates_any'
+
class JobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2412,6 +2531,7 @@ class JobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView
parent_model = JobTemplate
relationship = 'notification_templates_error'
+
class JobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2419,6 +2539,7 @@ class JobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIVi
parent_model = JobTemplate
relationship = 'notification_templates_success'
+
class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView):
model = Label
@@ -2439,6 +2560,7 @@ class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDeta
del request.data['organization']
return super(JobTemplateLabelList, self).post(request, *args, **kwargs)
+
class JobTemplateCallback(GenericAPIView):
model = JobTemplate
@@ -2605,12 +2727,14 @@ class JobTemplateJobsList(SubListCreateAPIView):
relationship = 'jobs'
parent_key = 'job_template'
+
class JobTemplateAccessList(ResourceAccessList):
model = User # needs to be User for AccessLists's
resource_model = JobTemplate
new_in_300 = True
+
class JobTemplateObjectRolesList(SubListAPIView):
model = Role
@@ -2623,24 +2747,28 @@ class JobTemplateObjectRolesList(SubListAPIView):
content_type = ContentType.objects.get_for_model(self.parent_model)
return Role.objects.filter(content_type=content_type, object_id=po.pk)
+
class WorkflowJobNodeList(ListAPIView):
model = WorkflowJobNode
serializer_class = WorkflowJobNodeListSerializer
new_in_310 = True
+
class WorkflowJobNodeDetail(RetrieveAPIView):
model = WorkflowJobNode
serializer_class = WorkflowJobNodeDetailSerializer
new_in_310 = True
+
class WorkflowJobTemplateNodeList(ListCreateAPIView):
model = WorkflowJobTemplateNode
serializer_class = WorkflowJobTemplateNodeListSerializer
new_in_310 = True
+
class WorkflowJobTemplateNodeDetail(RetrieveUpdateDestroyAPIView):
model = WorkflowJobTemplateNode
@@ -2717,6 +2845,7 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su
return None
+
class WorkflowJobTemplateNodeSuccessNodesList(WorkflowJobTemplateNodeChildrenBaseList):
relationship = 'success_nodes'
@@ -2743,6 +2872,7 @@ class WorkflowJobNodeChildrenBaseList(SubListAPIView):
self.check_parent_access(parent)
return getattr(parent, self.relationship).all()
+
class WorkflowJobNodeSuccessNodesList(WorkflowJobNodeChildrenBaseList):
relationship = 'success_nodes'
@@ -2771,6 +2901,7 @@ class WorkflowJobTemplateList(ListCreateAPIView):
return ret
'''
+
# TODO:
class WorkflowJobTemplateDetail(RetrieveUpdateDestroyAPIView):
@@ -2787,6 +2918,7 @@ class WorkflowJobTemplateLabelList(JobTemplateLabelList):
class WorkflowJobTemplateLaunch(RetrieveAPIView):
+
model = WorkflowJobTemplate
serializer_class = WorkflowJobLaunchSerializer
new_in_310 = True
@@ -2823,6 +2955,7 @@ class WorkflowJobTemplateLaunch(RetrieveAPIView):
data['workflow_job'] = new_job.id
return Response(data, status=status.HTTP_201_CREATED)
+
# TODO:
class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView):
@@ -2838,6 +2971,7 @@ class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView):
data[fd] = None
return super(WorkflowJobTemplateWorkflowNodesList, self).update_raw_data(data)
+
# TODO:
class WorkflowJobTemplateJobsList(SubListAPIView):
@@ -2847,6 +2981,7 @@ class WorkflowJobTemplateJobsList(SubListAPIView):
relationship = 'jobs'
parent_key = 'workflow_job_template'
+
class WorkflowJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
view_name = _("Workflow Job Template Schedules")
@@ -2857,6 +2992,7 @@ class WorkflowJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
relationship = 'schedules'
parent_key = 'unified_job_template'
+
class WorkflowJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2865,6 +3001,7 @@ class WorkflowJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachA
relationship = 'notification_templates_any'
new_in_310 = True
+
class WorkflowJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2873,6 +3010,7 @@ class WorkflowJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetac
relationship = 'notification_templates_error'
new_in_310 = True
+
class WorkflowJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2881,6 +3019,7 @@ class WorkflowJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDet
relationship = 'notification_templates_success'
new_in_310 = True
+
# TODO:
class WorkflowJobList(ListCreateAPIView):
@@ -2888,6 +3027,7 @@ class WorkflowJobList(ListCreateAPIView):
serializer_class = WorkflowJobListSerializer
new_in_310 = True
+
# TODO:
class WorkflowJobDetail(RetrieveDestroyAPIView):
@@ -2895,6 +3035,7 @@ class WorkflowJobDetail(RetrieveDestroyAPIView):
serializer_class = WorkflowJobSerializer
new_in_310 = True
+
class WorkflowJobWorkflowNodesList(SubListAPIView):
model = WorkflowJobNode
@@ -2905,6 +3046,7 @@ class WorkflowJobWorkflowNodesList(SubListAPIView):
parent_key = 'workflow_job'
new_in_310 = True
+
class WorkflowJobCancel(RetrieveAPIView):
model = WorkflowJob
@@ -2922,6 +3064,7 @@ class WorkflowJobCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class WorkflowJobNotificationsList(SubListAPIView):
model = Notification
@@ -2930,6 +3073,7 @@ class WorkflowJobNotificationsList(SubListAPIView):
relationship = 'notifications'
new_in_310 = True
+
class SystemJobTemplateList(ListAPIView):
model = SystemJobTemplate
@@ -2940,11 +3084,13 @@ class SystemJobTemplateList(ListAPIView):
raise PermissionDenied(_("Superuser privileges needed."))
return super(SystemJobTemplateList, self).get(request, *args, **kwargs)
+
class SystemJobTemplateDetail(RetrieveAPIView):
model = SystemJobTemplate
serializer_class = SystemJobTemplateSerializer
+
class SystemJobTemplateLaunch(GenericAPIView):
model = SystemJobTemplate
@@ -2962,6 +3108,7 @@ class SystemJobTemplateLaunch(GenericAPIView):
data = dict(system_job=new_job.id)
return Response(data, status=status.HTTP_201_CREATED)
+
class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
view_name = _("System Job Template Schedules")
@@ -2972,6 +3119,7 @@ class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
relationship = 'schedules'
parent_key = 'unified_job_template'
+
class SystemJobTemplateJobsList(SubListAPIView):
model = SystemJob
@@ -2980,6 +3128,7 @@ class SystemJobTemplateJobsList(SubListAPIView):
relationship = 'jobs'
parent_key = 'system_job_template'
+
class SystemJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2987,6 +3136,7 @@ class SystemJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPI
parent_model = SystemJobTemplate
relationship = 'notification_templates_any'
+
class SystemJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -2994,6 +3144,7 @@ class SystemJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachA
parent_model = SystemJobTemplate
relationship = 'notification_templates_error'
+
class SystemJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
model = NotificationTemplate
@@ -3001,11 +3152,13 @@ class SystemJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetac
parent_model = SystemJobTemplate
relationship = 'notification_templates_success'
+
class JobList(ListCreateAPIView):
model = Job
serializer_class = JobListSerializer
+
class JobDetail(RetrieveUpdateDestroyAPIView):
model = Job
@@ -3024,6 +3177,7 @@ class JobDetail(RetrieveUpdateDestroyAPIView):
raise PermissionDenied(detail=_('Can not delete job resource when associated workflow job is running.'))
return super(JobDetail, self).destroy(request, *args, **kwargs)
+
class JobLabelList(SubListAPIView):
model = Label
@@ -3032,6 +3186,7 @@ class JobLabelList(SubListAPIView):
relationship = 'labels'
parent_key = 'job'
+
class WorkflowJobLabelList(JobLabelList):
parent_model = WorkflowJob
@@ -3053,6 +3208,7 @@ class JobActivityStreamList(SubListAPIView):
# Okay, let it through.
return super(JobActivityStreamList, self).get(request, *args, **kwargs)
+
class JobStart(GenericAPIView):
model = Job
@@ -3081,6 +3237,7 @@ class JobStart(GenericAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class JobCancel(RetrieveAPIView):
model = Job
@@ -3095,6 +3252,7 @@ class JobCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class JobRelaunch(RetrieveAPIView, GenericAPIView):
model = Job
@@ -3128,6 +3286,7 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView):
headers = {'Location': new_job.get_absolute_url()}
return Response(data, status=status.HTTP_201_CREATED, headers=headers)
+
class JobNotificationsList(SubListAPIView):
model = Notification
@@ -3135,6 +3294,7 @@ class JobNotificationsList(SubListAPIView):
parent_model = Job
relationship = 'notifications'
+
class BaseJobHostSummariesList(SubListAPIView):
model = JobHostSummary
@@ -3143,33 +3303,40 @@ class BaseJobHostSummariesList(SubListAPIView):
relationship = 'job_host_summaries'
view_name = _('Job Host Summaries List')
+
class HostJobHostSummariesList(BaseJobHostSummariesList):
parent_model = Host
+
class GroupJobHostSummariesList(BaseJobHostSummariesList):
parent_model = Group
+
class JobJobHostSummariesList(BaseJobHostSummariesList):
parent_model = Job
+
class JobHostSummaryDetail(RetrieveAPIView):
model = JobHostSummary
serializer_class = JobHostSummarySerializer
+
class JobEventList(ListAPIView):
model = JobEvent
serializer_class = JobEventSerializer
+
class JobEventDetail(RetrieveAPIView):
model = JobEvent
serializer_class = JobEventSerializer
+
class JobEventChildrenList(SubListAPIView):
model = JobEvent
@@ -3178,6 +3345,7 @@ class JobEventChildrenList(SubListAPIView):
relationship = 'children'
view_name = _('Job Event Children List')
+
class JobEventHostsList(SubListAPIView):
model = Host
@@ -3186,6 +3354,7 @@ class JobEventHostsList(SubListAPIView):
relationship = 'hosts'
view_name = _('Job Event Hosts List')
+
class BaseJobEventsList(SubListAPIView):
model = JobEvent
@@ -3194,18 +3363,22 @@ class BaseJobEventsList(SubListAPIView):
relationship = 'job_events'
view_name = _('Job Events List')
+
class HostJobEventsList(BaseJobEventsList):
parent_model = Host
+
class GroupJobEventsList(BaseJobEventsList):
parent_model = Group
+
class JobJobEventsList(BaseJobEventsList):
parent_model = Job
+
class JobJobPlaysList(BaseJobEventsList):
parent_model = Job
@@ -3588,6 +3761,7 @@ class HostAdHocCommandEventsList(BaseAdHocCommandEventsList):
parent_model = Host
new_in_220 = True
+
#class GroupJobEventsList(BaseJobEventsList):
# parent_model = Group
@@ -3641,6 +3815,7 @@ class SystemJobDetail(RetrieveDestroyAPIView):
model = SystemJob
serializer_class = SystemJobSerializer
+
class SystemJobCancel(RetrieveAPIView):
model = SystemJob
@@ -3655,6 +3830,7 @@ class SystemJobCancel(RetrieveAPIView):
else:
return self.http_method_not_allowed(request, *args, **kwargs)
+
class SystemJobNotificationsList(SubListAPIView):
model = Notification
@@ -3662,18 +3838,21 @@ class SystemJobNotificationsList(SubListAPIView):
parent_model = SystemJob
relationship = 'notifications'
+
class UnifiedJobTemplateList(ListAPIView):
model = UnifiedJobTemplate
serializer_class = UnifiedJobTemplateSerializer
new_in_148 = True
+
class UnifiedJobList(ListAPIView):
model = UnifiedJob
serializer_class = UnifiedJobListSerializer
new_in_148 = True
+
class UnifiedJobStdout(RetrieveAPIView):
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
@@ -3743,29 +3922,35 @@ class UnifiedJobStdout(RetrieveAPIView):
else:
return super(UnifiedJobStdout, self).retrieve(request, *args, **kwargs)
+
class ProjectUpdateStdout(UnifiedJobStdout):
model = ProjectUpdate
+
class InventoryUpdateStdout(UnifiedJobStdout):
model = InventoryUpdate
+
class JobStdout(UnifiedJobStdout):
model = Job
+
class AdHocCommandStdout(UnifiedJobStdout):
model = AdHocCommand
new_in_220 = True
+
class NotificationTemplateList(ListCreateAPIView):
model = NotificationTemplate
serializer_class = NotificationTemplateSerializer
new_in_300 = True
+
class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView):
model = NotificationTemplate
@@ -3781,6 +3966,7 @@ class NotificationTemplateDetail(RetrieveUpdateDestroyAPIView):
status=status.HTTP_405_METHOD_NOT_ALLOWED)
return super(NotificationTemplateDetail, self).delete(request, *args, **kwargs)
+
class NotificationTemplateTest(GenericAPIView):
view_name = _('NotificationTemplate Test')
@@ -3802,6 +3988,7 @@ class NotificationTemplateTest(GenericAPIView):
headers=headers,
status=status.HTTP_202_ACCEPTED)
+
class NotificationTemplateNotificationList(SubListAPIView):
model = Notification
@@ -3810,30 +3997,35 @@ class NotificationTemplateNotificationList(SubListAPIView):
relationship = 'notifications'
parent_key = 'notification_template'
+
class NotificationList(ListAPIView):
model = Notification
serializer_class = NotificationSerializer
new_in_300 = True
+
class NotificationDetail(RetrieveAPIView):
model = Notification
serializer_class = NotificationSerializer
new_in_300 = True
+
class LabelList(ListCreateAPIView):
model = Label
serializer_class = LabelSerializer
new_in_300 = True
+
class LabelDetail(RetrieveUpdateAPIView):
model = Label
serializer_class = LabelSerializer
new_in_300 = True
+
class ActivityStreamList(SimpleListAPIView):
model = ActivityStream
diff --git a/awx/conf/conf.py b/awx/conf/conf.py
index 8562bb0929..cd494f1dee 100644
--- a/awx/conf/conf.py
+++ b/awx/conf/conf.py
@@ -57,6 +57,7 @@ register(
category_slug='cows',
)
+
def _get_read_only_ansible_cow_selection_default():
return getattr(settings, 'ANSIBLE_COW_SELECTION', 'No default cow!')
diff --git a/awx/lib/sitecustomize.py b/awx/lib/sitecustomize.py
index 02ac2eba55..be7c06102d 100644
--- a/awx/lib/sitecustomize.py
+++ b/awx/lib/sitecustomize.py
@@ -5,6 +5,7 @@ import sys
# Based on http://stackoverflow.com/a/6879344/131141 -- Initialize tower display
# callback as early as possible to wrap ansible.display.Display methods.
+
def argv_ready(argv):
if argv and os.path.basename(argv[0]) in {'ansible', 'ansible-playbook'}:
import tower_display_callback # noqa
diff --git a/awx/main/access.py b/awx/main/access.py
index 20d5ae508f..7f23edafa2 100644
--- a/awx/main/access.py
+++ b/awx/main/access.py
@@ -58,13 +58,16 @@ access_registry = {
# ...
}
+
class StateConflict(ValidationError):
status_code = 409
+
def register_access(model_class, access_class):
access_classes = access_registry.setdefault(model_class, [])
access_classes.append(access_class)
+
@property
def user_admin_role(self):
role = Role.objects.get(
@@ -76,9 +79,11 @@ def user_admin_role(self):
role.parents = [org.admin_role.pk for org in self.organizations]
return role
+
def user_accessible_objects(user, role_name):
return ResourceMixin._accessible_objects(User, user, role_name)
+
def get_user_queryset(user, model_class):
'''
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))
return queryset
+
def check_user_access(user, model_class, action, *args, **kwargs):
'''
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 False
+
def get_user_capabilities(user, instance, **kwargs):
'''
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 None
+
def check_superuser(func):
'''
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 wrapper
+
class BaseAccess(object):
'''
Base class for checking user access to a given model. Subclasses should
@@ -488,6 +497,7 @@ class OrganizationAccess(BaseAccess):
"active_jobs": active_jobs})
return True
+
class InventoryAccess(BaseAccess):
'''
I can see inventory when:
@@ -557,6 +567,7 @@ class InventoryAccess(BaseAccess):
def can_run_ad_hoc_commands(self, obj):
return self.user in obj.adhoc_role
+
class HostAccess(BaseAccess):
'''
I can see hosts whenever I can see their inventory.
@@ -611,6 +622,7 @@ class HostAccess(BaseAccess):
def can_delete(self, obj):
return obj and self.user in obj.inventory.admin_role
+
class GroupAccess(BaseAccess):
'''
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 False
+
class InventorySourceAccess(BaseAccess):
'''
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):
return self.user in obj.inventory_source.inventory.admin_role
+
class CredentialAccess(BaseAccess):
'''
I can see credentials when:
@@ -829,6 +843,7 @@ class CredentialAccess(BaseAccess):
# return True
return self.can_change(obj, None)
+
class TeamAccess(BaseAccess):
'''
I can see a team when:
@@ -889,6 +904,7 @@ class TeamAccess(BaseAccess):
return super(TeamAccess, self).can_unattach(obj, sub_obj, relationship,
*args, **kwargs)
+
class ProjectAccess(BaseAccess):
'''
I can see projects when:
@@ -943,6 +959,7 @@ class ProjectAccess(BaseAccess):
def can_start(self, obj, validate_license=True):
return obj and self.user in obj.update_role
+
class ProjectUpdateAccess(BaseAccess):
'''
I can see project updates when I can see the project.
@@ -979,6 +996,7 @@ class ProjectUpdateAccess(BaseAccess):
def can_delete(self, obj):
return obj and self.user in obj.project.admin_role
+
class JobTemplateAccess(BaseAccess):
'''
I can see job templates when:
@@ -1175,6 +1193,7 @@ class JobTemplateAccess(BaseAccess):
"active_jobs": active_jobs})
return True
+
class JobAccess(BaseAccess):
'''
I can see jobs when:
@@ -1313,6 +1332,7 @@ class JobAccess(BaseAccess):
return True
return obj.job_template is not None and self.user in obj.job_template.admin_role
+
class SystemJobTemplateAccess(BaseAccess):
'''
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'''
return False
+
class SystemJobAccess(BaseAccess):
'''
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):
return False # no relaunching of system jobs
+
# TODO:
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):
return self.wfjt_admin(obj) and self.check_same_WFJT(obj, sub_obj)
+
class WorkflowJobNodeAccess(BaseAccess):
'''
I can see a WorkflowJobNode if I have permission to...
@@ -1462,6 +1485,7 @@ class WorkflowJobNodeAccess(BaseAccess):
def can_delete(self, obj):
return False
+
# TODO: revisit for survey logic, notification attachments?
class WorkflowJobTemplateAccess(BaseAccess):
'''
@@ -1604,6 +1628,7 @@ class WorkflowJobAccess(BaseAccess):
return False
return self.can_delete(obj) or self.user == obj.created_by
+
class AdHocCommandAccess(BaseAccess):
'''
I can only see/run ad hoc commands when:
@@ -1660,6 +1685,7 @@ class AdHocCommandAccess(BaseAccess):
return True
return obj.inventory is not None and self.user in obj.inventory.admin_role
+
class AdHocCommandEventAccess(BaseAccess):
'''
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):
return False
+
class JobHostSummaryAccess(BaseAccess):
'''
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):
return False
+
class JobEventAccess(BaseAccess):
'''
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):
return False
+
class UnifiedJobTemplateAccess(BaseAccess):
'''
I can see a unified job template whenever I can see the same project,
@@ -1788,6 +1817,7 @@ class UnifiedJobTemplateAccess(BaseAccess):
return qs.all()
+
class UnifiedJobAccess(BaseAccess):
'''
I can see a unified job whenever I can see the same project update,
@@ -1838,6 +1868,7 @@ class UnifiedJobAccess(BaseAccess):
#)
return qs.all()
+
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
@@ -1878,6 +1909,7 @@ class ScheduleAccess(BaseAccess):
def can_delete(self, obj):
return self.can_change(obj, {})
+
class NotificationTemplateAccess(BaseAccess):
'''
I can see/use a notification_template if I have permission to
@@ -1926,6 +1958,7 @@ class NotificationTemplateAccess(BaseAccess):
return False
return self.user in obj.organization.admin_role
+
class NotificationAccess(BaseAccess):
'''
I can see/use a notification if I have permission to
@@ -1947,6 +1980,7 @@ class NotificationAccess(BaseAccess):
def can_delete(self, obj):
return self.user.can_access(NotificationTemplate, 'delete', obj.notification_template)
+
class LabelAccess(BaseAccess):
'''
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):
return self.can_change(obj, None)
+
class ActivityStreamAccess(BaseAccess):
'''
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):
return False
+
class CustomInventoryScriptAccess(BaseAccess):
model = CustomInventoryScript
@@ -2085,6 +2121,7 @@ class CustomInventoryScriptAccess(BaseAccess):
def can_delete(self, obj):
return self.can_admin(obj)
+
class RoleAccess(BaseAccess):
'''
- I can see roles when
diff --git a/awx/main/conf.py b/awx/main/conf.py
index 70af78240a..b2ed431e87 100644
--- a/awx/main/conf.py
+++ b/awx/main/conf.py
@@ -77,6 +77,7 @@ register(
category_slug='system',
)
+
def _load_default_license_from_file():
try:
license_file = os.environ.get('AWX_LICENSE_FILE', '/etc/tower/license')
diff --git a/awx/main/consumers.py b/awx/main/consumers.py
index bbd155329a..5e0ec6a376 100644
--- a/awx/main/consumers.py
+++ b/awx/main/consumers.py
@@ -30,6 +30,7 @@ def user_from_token(auth_token):
except User.DoesNotExist:
return None
+
@channel_session
def ws_connect(message):
token = None
diff --git a/awx/main/fields.py b/awx/main/fields.py
index 91f59bab8a..1eb8090fc1 100644
--- a/awx/main/fields.py
+++ b/awx/main/fields.py
@@ -40,6 +40,7 @@ class JSONField(upstream_JSONField):
# Based on AutoOneToOneField from django-annoying:
# https://bitbucket.org/offline/django-annoying/src/a0de8b294db3/annoying/fields.py
+
class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
"""Descriptor for access to the object from its related class."""
@@ -56,6 +57,7 @@ class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor):
obj.save()
return obj
+
class AutoOneToOneField(models.OneToOneField):
"""OneToOneField that creates related object if it doesn't exist."""
diff --git a/awx/main/ha.py b/awx/main/ha.py
index 5341ea32bb..35ed6f64f0 100644
--- a/awx/main/ha.py
+++ b/awx/main/ha.py
@@ -4,6 +4,7 @@
# AWX
from awx.main.models import Instance
+
def is_ha_environment():
"""Return True if this is an HA environment, and False
otherwise.
diff --git a/awx/main/management/commands/cleanup_activitystream.py b/awx/main/management/commands/cleanup_activitystream.py
index f4803d2d84..cd3711790a 100644
--- a/awx/main/management/commands/cleanup_activitystream.py
+++ b/awx/main/management/commands/cleanup_activitystream.py
@@ -13,6 +13,7 @@ from django.utils.timezone import now
# AWX
from awx.main.models import ActivityStream
+
class Command(NoArgsCommand):
'''
Management command to purge old activity stream events.
diff --git a/awx/main/management/commands/cleanup_authtokens.py b/awx/main/management/commands/cleanup_authtokens.py
index 65a8d67e6b..113fa52b2f 100644
--- a/awx/main/management/commands/cleanup_authtokens.py
+++ b/awx/main/management/commands/cleanup_authtokens.py
@@ -12,6 +12,7 @@ from django.utils.timezone import now
# AWX
from awx.main.models import * # noqa
+
class Command(BaseCommand):
'''
Management command to cleanup expired auth tokens
diff --git a/awx/main/management/commands/cleanup_facts.py b/awx/main/management/commands/cleanup_facts.py
index e4c2d9f6f4..a709f81c1a 100644
--- a/awx/main/management/commands/cleanup_facts.py
+++ b/awx/main/management/commands/cleanup_facts.py
@@ -18,6 +18,7 @@ from awx.conf.license import feature_enabled
OLDER_THAN = 'older_than'
GRANULARITY = 'granularity'
+
class CleanupFacts(object):
def __init__(self):
self.timestamp = None
@@ -27,7 +28,7 @@ class CleanupFacts(object):
# Find all factVersion < pivot && > (pivot - granularity) grouped by host sorted by time descending (because it's indexed this way)
# foreach group
# Delete all except LAST entry (or Delete all except the FIRST entry, it's an arbitrary decision)
- #
+ #
# pivot -= granularity
# group by host
def cleanup(self, older_than_abs, granularity, module=None):
@@ -89,6 +90,7 @@ class CleanupFacts(object):
deleted_count = self.cleanup(t - older_than, granularity, module=module)
print("Deleted %d facts." % deleted_count)
+
class Command(BaseCommand):
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 + (
@@ -142,4 +144,3 @@ class Command(BaseCommand):
raise CommandError('--granularity invalid value "%s"' % options[GRANULARITY])
cleanup_facts.run(older_than, granularity, module=options['module'])
-
diff --git a/awx/main/management/commands/cleanup_jobs.py b/awx/main/management/commands/cleanup_jobs.py
index 777d21204f..68f428447f 100644
--- a/awx/main/management/commands/cleanup_jobs.py
+++ b/awx/main/management/commands/cleanup_jobs.py
@@ -14,6 +14,7 @@ from django.utils.timezone import now
# AWX
from awx.main.models import Job, AdHocCommand, ProjectUpdate, InventoryUpdate, SystemJob
+
class Command(NoArgsCommand):
'''
Management command to cleanup old jobs and project updates.
diff --git a/awx/main/management/commands/list_instances.py b/awx/main/management/commands/list_instances.py
index cbe767984a..e193a45dd0 100644
--- a/awx/main/management/commands/list_instances.py
+++ b/awx/main/management/commands/list_instances.py
@@ -4,6 +4,7 @@
from awx.main.models import Instance
from django.core.management.base import NoArgsCommand
+
class Command(NoArgsCommand):
"""List instances from the Tower database
"""
diff --git a/awx/main/management/commands/register_instance.py b/awx/main/management/commands/register_instance.py
index 01e2011aa0..7ce6be787b 100644
--- a/awx/main/management/commands/register_instance.py
+++ b/awx/main/management/commands/register_instance.py
@@ -7,6 +7,7 @@ from django.conf import settings
from optparse import make_option
from django.core.management.base import BaseCommand
+
class Command(BaseCommand):
"""
Internal tower command.
diff --git a/awx/main/management/commands/run_callback_receiver.py b/awx/main/management/commands/run_callback_receiver.py
index 5013e38aa4..c0105b2587 100644
--- a/awx/main/management/commands/run_callback_receiver.py
+++ b/awx/main/management/commands/run_callback_receiver.py
@@ -17,8 +17,8 @@ from awx.main.models import * # noqa
logger = logging.getLogger('awx.main.commands.run_callback_receiver')
-class CallbackBrokerWorker(ConsumerMixin):
+class CallbackBrokerWorker(ConsumerMixin):
def __init__(self, connection):
self.connection = connection
@@ -51,6 +51,7 @@ class CallbackBrokerWorker(ConsumerMixin):
logger.error('Callback Task Processor Raised Exception: %r', exc)
message.ack()
+
class Command(NoArgsCommand):
'''
Save Job Callback receiver (see awx.plugins.callbacks.job_event_callback)
diff --git a/awx/main/management/commands/run_fact_cache_receiver.py b/awx/main/management/commands/run_fact_cache_receiver.py
index 90252e2a7d..afc6095309 100644
--- a/awx/main/management/commands/run_fact_cache_receiver.py
+++ b/awx/main/management/commands/run_fact_cache_receiver.py
@@ -18,6 +18,7 @@ from awx.main.socket_queue import Socket
logger = logging.getLogger('awx.main.commands.run_fact_cache_receiver')
+
class FactCacheReceiver(object):
def __init__(self):
self.timestamp = None
@@ -91,6 +92,7 @@ class FactCacheReceiver(object):
else:
self.process_fact_message(message)
+
class Command(NoArgsCommand):
'''
blah blah
diff --git a/awx/main/management/commands/run_socketio_service.py b/awx/main/management/commands/run_socketio_service.py
index c1c49b7945..9b7e5a61d2 100644
--- a/awx/main/management/commands/run_socketio_service.py
+++ b/awx/main/management/commands/run_socketio_service.py
@@ -25,6 +25,7 @@ from socketio.namespace import BaseNamespace
logger = logging.getLogger('awx.main.commands.run_socketio_service')
+
class SocketSession(object):
def __init__(self, session_id, token_key, socket):
self.socket = weakref.ref(socket)
@@ -45,8 +46,8 @@ class SocketSession(object):
auth_token = auth_token[0]
return bool(not auth_token.is_expired())
-class SocketSessionManager(object):
+class SocketSessionManager(object):
def __init__(self):
self.SESSIONS_MAX = 1000
self.socket_sessions = []
@@ -79,8 +80,8 @@ class SocketSessionManager(object):
self._prune()
return session
-class SocketController(object):
+class SocketController(object):
def __init__(self, SocketSessionManager):
self.server = None
self.SocketSessionManager = SocketSessionManager
@@ -127,12 +128,12 @@ class SocketController(object):
socketController = SocketController(SocketSessionManager())
+
#
# Socket session is attached to self.session['socket_session']
# self.session and self.socket.session point to the same dict
#
class TowerBaseNamespace(BaseNamespace):
-
def get_allowed_methods(self):
return ['recv_disconnect']
@@ -179,33 +180,33 @@ class TowerBaseNamespace(BaseNamespace):
if socket_session and not socket_session.is_valid():
self.disconnect(silent=False)
-class TestNamespace(TowerBaseNamespace):
+class TestNamespace(TowerBaseNamespace):
def recv_connect(self):
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")
super(TestNamespace, self).recv_connect()
-class JobNamespace(TowerBaseNamespace):
+class JobNamespace(TowerBaseNamespace):
def recv_connect(self):
logger.info("Received client connect for job namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(JobNamespace, self).recv_connect()
-class JobEventNamespace(TowerBaseNamespace):
+class JobEventNamespace(TowerBaseNamespace):
def recv_connect(self):
logger.info("Received client connect for job event namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(JobEventNamespace, self).recv_connect()
-class AdHocCommandEventNamespace(TowerBaseNamespace):
+class AdHocCommandEventNamespace(TowerBaseNamespace):
def recv_connect(self):
logger.info("Received client connect for ad hoc command event namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(AdHocCommandEventNamespace, self).recv_connect()
-class ScheduleNamespace(TowerBaseNamespace):
+class ScheduleNamespace(TowerBaseNamespace):
def get_allowed_methods(self):
parent_allowed = super(ScheduleNamespace, self).get_allowed_methods()
return parent_allowed + ["schedule_changed"]
@@ -214,16 +215,16 @@ class ScheduleNamespace(TowerBaseNamespace):
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(ScheduleNamespace, self).recv_connect()
+
# Catch-all namespace.
# Deliver 'global' events over this namespace
class ControlNamespace(TowerBaseNamespace):
-
def recv_connect(self):
logger.warn("Received client connect for control namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(ControlNamespace, self).recv_connect()
-class TowerSocket(object):
+class TowerSocket(object):
def __call__(self, environ, start_response):
path = environ['PATH_INFO'].strip('/') or 'index.html'
if path.startswith('socket.io'):
@@ -238,6 +239,7 @@ class TowerSocket(object):
start_response('404 Not Found', [])
return ['Tower version %s' % awx.__version__]
+
def notification_handler(server):
with Socket('websocket', 'r') as websocket:
for message in websocket.listen():
@@ -254,6 +256,7 @@ def notification_handler(server):
else:
socketController.broadcast_packet(packet)
+
class Command(NoArgsCommand):
'''
SocketIO event emitter Tower service
diff --git a/awx/main/management/commands/stats.py b/awx/main/management/commands/stats.py
index 68b5ceef00..f55068d076 100644
--- a/awx/main/management/commands/stats.py
+++ b/awx/main/management/commands/stats.py
@@ -9,6 +9,7 @@ from django.core.management.base import BaseCommand
# AWX
from awx.main.models import * # noqa
+
class Command(BaseCommand):
'''
Emits some simple statistics suitable for external monitoring
diff --git a/awx/main/management/commands/update_password.py b/awx/main/management/commands/update_password.py
index fe45799776..18a9fb053d 100644
--- a/awx/main/management/commands/update_password.py
+++ b/awx/main/management/commands/update_password.py
@@ -9,10 +9,11 @@ from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.contrib.auth.models import User
+
class UpdatePassword(object):
def update_password(self, username, password):
changed = False
- u = User.objects.get(username=username)
+ u = User.objects.get(username=username)
if not u:
raise RuntimeError("User not found")
check = u.check_password(password)
@@ -22,6 +23,7 @@ class UpdatePassword(object):
changed = True
return changed
+
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('--username', dest='username', action='store', type='string', default=None,
diff --git a/awx/main/management/commands/workload_generator.py b/awx/main/management/commands/workload_generator.py
index 658d5c7228..5d1bd07019 100644
--- a/awx/main/management/commands/workload_generator.py
+++ b/awx/main/management/commands/workload_generator.py
@@ -23,7 +23,7 @@ from awx.main.utils import timedelta_total_seconds
TEST_FACT_ANSIBLE = {
"ansible_swapfree_mb" : 4092,
"ansible_default_ipv6" : {
-
+
},
"ansible_distribution_release" : "trusty",
"ansible_system_vendor" : "innotek GmbH",
@@ -199,6 +199,7 @@ EXPERIMENT_DEFAULT = {
]
}
+
class Experiment(object):
def __init__(self, exp, fact_fixtures, raw_db, mongoengine_db):
self.db = raw_db
@@ -291,6 +292,7 @@ class Experiment(object):
print("Finished at: %s" % time_end)
print("Total runtime: %s seconds" % timedelta_total_seconds(time_end - time_start))
+
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
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.generate_workload()
-
diff --git a/awx/main/managers.py b/awx/main/managers.py
index 5fad1e11c8..522157a70f 100644
--- a/awx/main/managers.py
+++ b/awx/main/managers.py
@@ -20,6 +20,7 @@ class HostManager(models.Manager):
except NotImplementedError: # For unit tests only, SQLite doesn't support distinct('name')
return len(set(self.values_list('name', flat=True)))
+
class InstanceManager(models.Manager):
"""A custom manager class for the Instance model.
diff --git a/awx/main/middleware.py b/awx/main/middleware.py
index 75bdf01daa..187017ddba 100644
--- a/awx/main/middleware.py
+++ b/awx/main/middleware.py
@@ -68,6 +68,7 @@ class ActivityStreamMiddleware(threading.local):
if instance.id not in self.instance_ids:
self.instance_ids.append(instance.id)
+
class AuthTokenTimeoutMiddleware(object):
"""Presume that when the user includes the auth header, they go through the
authentication mechanism. Further, that mechanism is presumed to extend
diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py
index 331ea0b6e0..321c340a92 100644
--- a/awx/main/models/__init__.py
+++ b/awx/main/models/__init__.py
@@ -28,6 +28,8 @@ from awx.main.models.channels import * # noqa
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
from django.core.serializers.python import Serializer as _PythonSerializer
_original_handle_m2m_field = _PythonSerializer.handle_m2m_field
+
+
def _new_handle_m2m_field(self, obj, field):
try:
field.rel.through._meta
@@ -49,14 +51,17 @@ User.add_to_class('can_access', check_user_access)
User.add_to_class('accessible_objects', user_accessible_objects)
User.add_to_class('admin_role', user_admin_role)
+
@property
def user_get_organizations(user):
return Organization.objects.filter(member_role__members=user)
+
@property
def user_get_admin_of_organizations(user):
return Organization.objects.filter(admin_role__members=user)
+
@property
def user_get_auditor_of_organizations(user):
return Organization.objects.filter(auditor_role__members=user)
@@ -66,10 +71,12 @@ User.add_to_class('organizations', user_get_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)
+
@property
def user_is_system_auditor(user):
return Role.singleton('system_auditor').members.filter(id=user.id).exists()
+
@user_is_system_auditor.setter
def user_is_system_auditor(user, tf):
if user.id:
diff --git a/awx/main/models/ad_hoc_commands.py b/awx/main/models/ad_hoc_commands.py
index bcb835763e..27d8754aa6 100644
--- a/awx/main/models/ad_hoc_commands.py
+++ b/awx/main/models/ad_hoc_commands.py
@@ -209,6 +209,7 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
def get_notification_friendly_name(self):
return "AdHoc Command"
+
class AdHocCommandEvent(CreatedModifiedModel):
'''
An event/message logged from the ad hoc event callback for each host.
diff --git a/awx/main/models/base.py b/awx/main/models/base.py
index 691b4532fe..bdee496bad 100644
--- a/awx/main/models/base.py
+++ b/awx/main/models/base.py
@@ -320,6 +320,7 @@ class CommonModelNameNotUnique(PrimordialModel):
unique=False,
)
+
class NotificationFieldsModel(BaseModel):
class Meta:
diff --git a/awx/main/models/channels.py b/awx/main/models/channels.py
index cd34678638..bd4f9514ba 100644
--- a/awx/main/models/channels.py
+++ b/awx/main/models/channels.py
@@ -1,5 +1,6 @@
from django.db import models
+
class ChannelGroup(models.Model):
group = models.CharField(max_length=200, unique=True)
channels = models.TextField()
diff --git a/awx/main/models/fact.py b/awx/main/models/fact.py
index 16a67eb45e..480834c2c1 100644
--- a/awx/main/models/fact.py
+++ b/awx/main/models/fact.py
@@ -8,6 +8,7 @@ from jsonbfield.fields import JSONField
__all__ = ('Fact', )
+
class Fact(models.Model):
"""A model representing a fact returned from Ansible.
Facts are stored as JSON dictionaries.
@@ -20,8 +21,8 @@ class Fact(models.Model):
help_text=_('Host for the facts that the fact scan captured.'),
)
timestamp = models.DateTimeField(
- default=None,
- editable=False,
+ default=None,
+ editable=False,
help_text=_('Date and time of the corresponding fact scan gathering time.')
)
module = models.CharField(max_length=128)
diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py
index 3fff1d77cb..cb01d03722 100644
--- a/awx/main/models/ha.py
+++ b/awx/main/models/ha.py
@@ -39,9 +39,11 @@ class Instance(models.Model):
# NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing
return "tower"
+
class TowerScheduleState(SingletonModel):
schedule_last_run = models.DateTimeField(auto_now_add=True)
+
class JobOrigin(models.Model):
"""A model representing the relationship between a unified job and
the instance that was responsible for starting that job.
diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py
index cefc97e8eb..323a8f047f 100644
--- a/awx/main/models/jobs.py
+++ b/awx/main/models/jobs.py
@@ -188,6 +188,7 @@ class JobOptions(BaseModel):
else:
return []
+
class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, ResourceMixin):
'''
A job template is a reusable job definition for applying a project (with
@@ -392,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)))
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
+
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
'''
A job applies a project (with playbook) to an inventory source with a given
@@ -646,6 +648,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin):
def get_notification_friendly_name(self):
return "Job"
+
class JobHostSummary(CreatedModifiedModel):
'''
Per-host statistics for each job.
diff --git a/awx/main/models/label.py b/awx/main/models/label.py
index 9323ab4973..665d0dd98e 100644
--- a/awx/main/models/label.py
+++ b/awx/main/models/label.py
@@ -12,6 +12,7 @@ from awx.main.models.unified_jobs import UnifiedJobTemplate, UnifiedJob
__all__ = ('Label', )
+
class Label(CommonModelNameNotUnique):
'''
Generic Tag. Designed for tagging Job Templates, but expandable to other models.
@@ -55,4 +56,3 @@ class Label(CommonModelNameNotUnique):
return True
else:
return False
-
diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py
index 8ce7025e8c..887d139573 100644
--- a/awx/main/models/mixins.py
+++ b/awx/main/models/mixins.py
@@ -16,6 +16,7 @@ from awx.main.fields import JSONField
__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin']
+
class ResourceMixin(models.Model):
class Meta:
@@ -216,4 +217,3 @@ class SurveyJobMixin(models.Model):
return json.dumps(extra_vars)
else:
return self.extra_vars
-
diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py
index 4f304fccda..8ba92b3782 100644
--- a/awx/main/models/notifications.py
+++ b/awx/main/models/notifications.py
@@ -25,6 +25,7 @@ logger = logging.getLogger('awx.main.models.notifications')
__all__ = ['NotificationTemplate', 'Notification']
+
class NotificationTemplate(CommonModel):
NOTIFICATION_TYPES = [('email', _('Email'), CustomEmailBackend),
@@ -116,6 +117,7 @@ class NotificationTemplate(CommonModel):
notification_obj = EmailMessage(subject, backend_obj.format_body(body), sender, recipients)
return backend_obj.send_messages([notification_obj])
+
class Notification(CreatedModifiedModel):
'''
A notification event emitted when a NotificationTemplate is run
@@ -171,6 +173,7 @@ class Notification(CreatedModifiedModel):
def get_absolute_url(self):
return reverse('api:notification_detail', args=(self.pk,))
+
class JobNotificationMixin(object):
def get_notification_templates(self):
raise RuntimeError("Define me")
@@ -193,4 +196,3 @@ class JobNotificationMixin(object):
def build_notification_failed_message(self):
return self._build_notification_message('failed')
-
diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py
index d39c5180f8..8f96d1656c 100644
--- a/awx/main/models/organization.py
+++ b/awx/main/models/organization.py
@@ -72,7 +72,6 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin):
return self.name
-
class Team(CommonModelNameNotUnique, ResourceMixin):
'''
A team is a group of users that work on common projects.
@@ -200,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
set (i.e. may equal '') even though a session is expired or a limit is reached.
"""
+
+
class AuthToken(BaseModel):
'''
Custom authentication tokens per user with expiration and request-specific
diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py
index 1ed4e418bf..ee65791e6a 100644
--- a/awx/main/models/projects.py
+++ b/awx/main/models/projects.py
@@ -391,6 +391,7 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin):
def get_absolute_url(self):
return reverse('api:project_detail', args=(self.pk,))
+
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin):
'''
Internal job for tracking project updates from SCM.
diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py
index 5d6bef1bfc..7f8e4813df 100644
--- a/awx/main/models/rbac.py
+++ b/awx/main/models/rbac.py
@@ -79,6 +79,7 @@ def check_singleton(func):
return func(*args, **kwargs)
return wrapper
+
@contextlib.contextmanager
def batch_role_ancestor_rebuilding(allow_nesting=False):
'''
@@ -426,6 +427,7 @@ class Role(models.Model):
def is_ancestor_of(self, role):
return role.ancestors.filter(id=self.id).exists()
+
class RoleAncestorEntry(models.Model):
class Meta:
diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py
index 0e48491d04..ef536fea6f 100644
--- a/awx/main/models/unified_jobs.py
+++ b/awx/main/models/unified_jobs.py
@@ -356,6 +356,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
dest_field.add(*list(src_field_value.all().values_list('id', flat=True)))
return unified_job
+
class UnifiedJobTypeStringMixin(object):
@classmethod
def _underscore_to_camel(cls, word):
@@ -379,6 +380,7 @@ class UnifiedJobTypeStringMixin(object):
return None
return model.objects.get(id=job_id)
+
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin):
'''
Concrete base class for unified job run by the task engine.
diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py
index 4e2b6e3b90..bed69330a5 100644
--- a/awx/main/models/workflow.py
+++ b/awx/main/models/workflow.py
@@ -32,6 +32,7 @@ __all__ = ['WorkflowJobTemplate', 'WorkflowJob', 'WorkflowJobOptions', 'Workflow
CHAR_PROMPTS_LIST = ['job_type', 'job_tags', 'skip_tags', 'limit']
+
class WorkflowNodeBase(CreatedModifiedModel):
class Meta:
abstract = True
@@ -158,6 +159,7 @@ class WorkflowNodeBase(CreatedModifiedModel):
return ['workflow_job', 'unified_job_template',
'inventory', 'credential', 'char_prompts']
+
class WorkflowJobTemplateNode(WorkflowNodeBase):
workflow_job_template = models.ForeignKey(
'WorkflowJobTemplate',
@@ -183,6 +185,7 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
create_kwargs[field_name] = getattr(self, field_name)
return WorkflowJobNode.objects.create(**create_kwargs)
+
class WorkflowJobNode(WorkflowNodeBase):
job = models.ForeignKey(
'UnifiedJob',
@@ -205,7 +208,7 @@ class WorkflowJobNode(WorkflowNodeBase):
default={},
editable=False,
)
-
+
def get_absolute_url(self):
return reverse('api:workflow_job_node_detail', args=(self.pk,))
@@ -257,6 +260,7 @@ class WorkflowJobNode(WorkflowNodeBase):
data['extra_vars'] = extra_vars
return data
+
class WorkflowJobOptions(BaseModel):
class Meta:
abstract = True
@@ -268,8 +272,8 @@ class WorkflowJobOptions(BaseModel):
extra_vars_dict = VarsDictProperty('extra_vars', True)
-class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
+class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
class Meta:
app_label = 'main'
@@ -371,6 +375,7 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
warning_data[node.pk] = node_prompts_warnings
return warning_data
+
class WorkflowJobInheritNodesMixin(object):
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)
@@ -412,10 +417,9 @@ class WorkflowJobInheritNodesMixin(object):
new_node = new_nodes[index]
for node_type in ['success_nodes', 'failure_nodes', 'always_nodes']:
self._inherit_relationship(old_node, new_node, node_ids_map, node_type)
-
+
class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin, WorkflowJobInheritNodesMixin):
-
class Meta:
app_label = 'main'
ordering = ('id',)
diff --git a/awx/main/redact.py b/awx/main/redact.py
index 0e9ad0b5d1..a4776601ac 100644
--- a/awx/main/redact.py
+++ b/awx/main/redact.py
@@ -3,6 +3,7 @@ import urlparse
REPLACE_STR = '$encrypted$'
+
class UriCleaner(object):
REPLACE_STR = REPLACE_STR
# https://regex101.com/r/sV2dO2/2
@@ -51,6 +52,7 @@ class UriCleaner(object):
return redactedtext
+
class PlainTextCleaner(object):
REPLACE_STR = REPLACE_STR
diff --git a/awx/main/scheduler/__init__.py b/awx/main/scheduler/__init__.py
index 16ca577d6c..0ac737c375 100644
--- a/awx/main/scheduler/__init__.py
+++ b/awx/main/scheduler/__init__.py
@@ -35,6 +35,7 @@ from celery.task.control import inspect
logger = logging.getLogger('awx.main.scheduler')
+
class TaskManager():
def __init__(self):
self.graph = DependencyGraph()
diff --git a/awx/main/scheduler/dag_simple.py b/awx/main/scheduler/dag_simple.py
index c6aa6247c0..1bfb387569 100644
--- a/awx/main/scheduler/dag_simple.py
+++ b/awx/main/scheduler/dag_simple.py
@@ -8,6 +8,7 @@ from awx.main.models import (
SystemJob,
)
+
class SimpleDAG(object):
''' A simple implementation of a directed acyclic graph '''
diff --git a/awx/main/scheduler/dag_workflow.py b/awx/main/scheduler/dag_workflow.py
index 1995434f48..c765b48678 100644
--- a/awx/main/scheduler/dag_workflow.py
+++ b/awx/main/scheduler/dag_workflow.py
@@ -2,6 +2,7 @@
# AWX
from awx.main.scheduler.dag_simple import SimpleDAG
+
class WorkflowDAG(SimpleDAG):
def __init__(self, workflow_job=None):
super(WorkflowDAG, self).__init__()
diff --git a/awx/main/scheduler/dependency_graph.py b/awx/main/scheduler/dependency_graph.py
index 08c00977c1..7b66a8fe1b 100644
--- a/awx/main/scheduler/dependency_graph.py
+++ b/awx/main/scheduler/dependency_graph.py
@@ -9,6 +9,8 @@ from awx.main.scheduler.partial import (
AdHocCommandDict,
WorkflowJobDict,
)
+
+
class DependencyGraph(object):
PROJECT_UPDATES = 'project_updates'
INVENTORY_UPDATES = 'inventory_updates'
diff --git a/awx/main/scheduler/partial.py b/awx/main/scheduler/partial.py
index e8355cbcfc..d16634f369 100644
--- a/awx/main/scheduler/partial.py
+++ b/awx/main/scheduler/partial.py
@@ -14,6 +14,7 @@ from awx.main.models import (
WorkflowJob,
)
+
class PartialModelDict(object):
FIELDS = ()
model = None
@@ -60,6 +61,7 @@ class PartialModelDict(object):
def task_impact(self):
raise RuntimeError("Inherit and implement me")
+
class JobDict(PartialModelDict):
FIELDS = (
'id', 'status', 'job_template_id', 'inventory_id', 'project_id',
@@ -83,6 +85,7 @@ class JobDict(PartialModelDict):
start_args = start_args or {}
return start_args.get('inventory_sources_already_updated', [])
+
class ProjectUpdateDict(PartialModelDict):
FIELDS = (
'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())]
+
class ProjectUpdateLatestDict(ProjectUpdateDict):
FIELDS = (
'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]))
return results
+
class InventoryUpdateDict(PartialModelDict):
#'inventory_source__update_on_launch',
#'inventory_source__update_cache_timeout',
@@ -139,6 +144,7 @@ class InventoryUpdateDict(PartialModelDict):
def task_impact(self):
return 20
+
class InventoryUpdateLatestDict(InventoryUpdateDict):
#'inventory_source__update_on_launch',
#'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]))
return results
+
class InventorySourceDict(PartialModelDict):
FIELDS = (
'id',
@@ -187,6 +194,7 @@ class InventorySourceDict(PartialModelDict):
}
return [cls(o) for o in cls.model.objects.filter(**kv).values(*cls.get_db_values())]
+
class SystemJobDict(PartialModelDict):
FIELDS = (
'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())]
+
class AdHocCommandDict(PartialModelDict):
FIELDS = (
'id', 'created', 'status', 'inventory_id',
@@ -218,6 +227,7 @@ class AdHocCommandDict(PartialModelDict):
def task_impact(self):
return 20
+
class WorkflowJobDict(PartialModelDict):
FIELDS = (
'id', 'created', 'status', 'workflow_job_template_id',
diff --git a/awx/main/scheduler/tasks.py b/awx/main/scheduler/tasks.py
index 61e5eecc46..5c4c821606 100644
--- a/awx/main/scheduler/tasks.py
+++ b/awx/main/scheduler/tasks.py
@@ -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
# updated model, the call to schedule() may get stale data.
+
@task
def run_job_launch(job_id):
TaskManager().schedule()
+
@task
def run_job_complete(job_id):
TaskManager().schedule()
+
@task
def run_task_manager():
TaskManager().schedule()
+
@task
def run_fail_inconsistent_running_jobs():
with transaction.atomic():
diff --git a/awx/main/signals.py b/awx/main/signals.py
index cb2987beb7..f7df3d3935 100644
--- a/awx/main/signals.py
+++ b/awx/main/signals.py
@@ -32,6 +32,7 @@ logger = logging.getLogger('awx.main.signals')
# 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
+
def emit_job_event_detail(sender, **kwargs):
instance = kwargs['instance']
created = kwargs['created']
@@ -45,6 +46,7 @@ def emit_job_event_detail(sender, **kwargs):
event_serialized["group_name"] = "job_events"
emit_channel_notification('job_events-' + str(instance.job.id), event_serialized)
+
def emit_ad_hoc_command_event_detail(sender, **kwargs):
instance = kwargs['instance']
created = kwargs['created']
@@ -57,6 +59,7 @@ def emit_ad_hoc_command_event_detail(sender, **kwargs):
event_serialized["group_name"] = "ad_hoc_command_events"
emit_channel_notification('ad_hoc_command_events-' + str(instance.ad_hoc_command_id), event_serialized)
+
def emit_update_inventory_computed_fields(sender, **kwargs):
logger.debug("In update inventory computed fields")
if getattr(_inventory_updates, 'is_updating', False):
@@ -91,6 +94,7 @@ def emit_update_inventory_computed_fields(sender, **kwargs):
else:
update_inventory_computed_fields.delay(inventory.id, True)
+
def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
if getattr(_inventory_updates, 'is_updating', False):
return
@@ -111,6 +115,7 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs):
if inventory is not None:
update_inventory_computed_fields.delay(inventory.id, True)
+
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'
if action == 'post_add':
@@ -125,6 +130,7 @@ def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwarg
else:
model.rebuild_role_ancestor_list([], [instance.id])
+
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'
if instance.is_superuser:
@@ -132,6 +138,7 @@ def sync_superuser_status_to_rbac(instance, **kwargs):
else:
Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance)
+
def create_user_role(instance, **kwargs):
try:
Role.objects.get(
@@ -146,6 +153,7 @@ def create_user_role(instance, **kwargs):
)
role.members.add(instance)
+
def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
content_type = ContentType.objects.get_for_model(Organization)
@@ -161,6 +169,7 @@ def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
if action == 'pre_remove':
instance.content_object.admin_role.children.remove(user.admin_role)
+
def rbac_activity_stream(instance, sender, **kwargs):
user_type = ContentType.objects.get_for_model(User)
# Only if we are associating/disassociating
@@ -195,6 +204,7 @@ def rbac_activity_stream(instance, sender, **kwargs):
activity_stream_associate(sender, instance, role=role, **kwargs)
+
def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs):
for l in instance.labels.all():
if l.is_candidate_for_detach():
@@ -226,6 +236,7 @@ pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJobT
# Migrate hosts, groups to parent group(s) whenever a group is deleted
+
@receiver(pre_delete, sender=Group)
def save_related_pks_before_group_delete(sender, **kwargs):
if getattr(_inventory_updates, 'is_removing', False):
@@ -236,6 +247,7 @@ def save_related_pks_before_group_delete(sender, **kwargs):
instance._saved_hosts_pks = set(instance.hosts.values_list('pk', flat=True))
instance._saved_children_pks = set(instance.children.values_list('pk', flat=True))
+
@receiver(post_delete, sender=Group)
def migrate_children_from_deleted_group_to_parent_groups(sender, **kwargs):
if getattr(_inventory_updates, 'is_removing', False):
@@ -269,6 +281,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
+
def _update_host_last_jhs(host):
jhs_qs = JobHostSummary.objects.filter(host__pk=host.pk)
try:
@@ -286,12 +299,14 @@ def _update_host_last_jhs(host):
if update_fields:
host.save(update_fields=update_fields)
+
@receiver(pre_delete, sender=Job)
def save_host_pks_before_job_delete(sender, **kwargs):
instance = kwargs['instance']
hosts_qs = Host.objects.filter( last_job__pk=instance.pk)
instance._saved_hosts_pks = set(hosts_qs.values_list('pk', flat=True))
+
@receiver(post_delete, sender=Job)
def update_host_last_job_after_job_deleted(sender, **kwargs):
instance = kwargs['instance']
@@ -301,6 +316,7 @@ def update_host_last_job_after_job_deleted(sender, **kwargs):
# Set via ActivityStreamRegistrar to record activity stream events
+
class ActivityStreamEnabled(threading.local):
def __init__(self):
self.enabled = True
@@ -341,6 +357,7 @@ model_serializer_mapping = {
Notification: NotificationSerializer,
}
+
def activity_stream_create(sender, instance, created, **kwargs):
if created and activity_stream_enabled:
# Skip recording any inventory source directly associated with a group.
@@ -363,6 +380,7 @@ def activity_stream_create(sender, instance, created, **kwargs):
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
getattr(activity_entry, object1).add(instance)
+
def activity_stream_update(sender, instance, **kwargs):
if instance.id is None:
return
@@ -386,6 +404,7 @@ def activity_stream_update(sender, instance, **kwargs):
if instance._meta.model_name != 'setting': # Is not conf.Setting instance
getattr(activity_entry, object1).add(instance)
+
def activity_stream_delete(sender, instance, **kwargs):
if not activity_stream_enabled:
return
@@ -400,6 +419,7 @@ def activity_stream_delete(sender, instance, **kwargs):
object1=object1)
activity_entry.save()
+
def activity_stream_associate(sender, instance, **kwargs):
if not activity_stream_enabled:
return
diff --git a/awx/main/tasks.py b/awx/main/tasks.py
index 6817bda76b..47a6583e9a 100644
--- a/awx/main/tasks.py
+++ b/awx/main/tasks.py
@@ -69,6 +69,7 @@ Try upgrading OpenSSH or providing your private key in an different format. \
logger = logging.getLogger('awx.main.tasks')
+
@celeryd_init.connect
def celery_startup(conf=None, **kwargs):
# Re-init all schedules
@@ -81,6 +82,7 @@ def celery_startup(conf=None, **kwargs):
except Exception as e:
logger.error("Failed to rebuild schedule {}: {}".format(sch, e))
+
@task(queue='default')
def send_notifications(notification_list, job_id=None):
if not isinstance(notification_list, list):
@@ -102,6 +104,7 @@ def send_notifications(notification_list, job_id=None):
if job_id is not None:
job_actual.notifications.add(notification)
+
@task(bind=True, queue='default')
def run_administrative_checks(self):
if not settings.TOWER_ADMIN_ALERTS:
@@ -122,10 +125,12 @@ def run_administrative_checks(self):
tower_admin_emails,
fail_silently=True)
+
@task(bind=True, queue='default')
def cleanup_authtokens(self):
AuthToken.objects.filter(expires__lt=now()).delete()
+
@task(bind=True)
def cluster_node_heartbeat(self):
inst = Instance.objects.filter(hostname=settings.CLUSTER_HOST_ID)
@@ -136,6 +141,7 @@ def cluster_node_heartbeat(self):
return
raise RuntimeError("Cluster Host Not Found: {}".format(settings.CLUSTER_HOST_ID))
+
@task(bind=True, queue='default')
def tower_periodic_scheduler(self):
run_now = now()
@@ -165,6 +171,7 @@ def tower_periodic_scheduler(self):
emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules"))
state.save()
+
def _send_notification_templates(instance, status_str):
if status_str not in ['succeeded', '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
run_job_complete.delay(instance.id)
+
@task(bind=True, queue='default')
def handle_work_error(self, task_id, subtasks=None):
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)
pass
+
@task(queue='default')
def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
'''
@@ -1222,6 +1231,7 @@ class RunProjectUpdate(BaseTask):
else:
logger.error("Could not find scm revision in check")
+
class RunInventoryUpdate(BaseTask):
name = 'awx.main.tasks.run_inventory_update'
@@ -1556,6 +1566,7 @@ class RunInventoryUpdate(BaseTask):
def get_idle_timeout(self):
return getattr(settings, 'INVENTORY_UPDATE_IDLE_TIMEOUT', None)
+
class RunAdHocCommand(BaseTask):
'''
Celery task to run an ad hoc command using ansible.
diff --git a/awx/main/tests/URI.py b/awx/main/tests/URI.py
index d04da03436..54fd1d8b06 100644
--- a/awx/main/tests/URI.py
+++ b/awx/main/tests/URI.py
@@ -1,6 +1,8 @@
# Helps with test cases.
# 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
+
+
class URI(object):
DEFAULTS = {
'scheme' : 'http',
diff --git a/awx/main/tests/base.py b/awx/main/tests/base.py
index 907caa7e75..ca5f8237d2 100644
--- a/awx/main/tests/base.py
+++ b/awx/main/tests/base.py
@@ -41,6 +41,7 @@ TEST_PLAYBOOK = '''- hosts: mygroup
command: test 1 = 1
'''
+
class QueueTestMixin(object):
def start_queue(self):
self.start_rabbit()
@@ -83,6 +84,7 @@ class QueueStartStopTestMixin(QueueTestMixin):
super(QueueStartStopTestMixin, self).tearDown()
self.terminate_queue()
+
class MockCommonlySlowTestMixin(object):
def __init__(self, *args, **kwargs):
from awx.api import generics
@@ -91,6 +93,8 @@ class MockCommonlySlowTestMixin(object):
ansible_version = get_ansible_version()
+
+
class BaseTestMixin(MockCommonlySlowTestMixin):
'''
Mixin with shared code for use by all test cases.
@@ -676,17 +680,20 @@ class BaseTestMixin(MockCommonlySlowTestMixin):
u'expected no traceback, got:\n%s' %
job.result_traceback)
+
class BaseTest(BaseTestMixin, django.test.TestCase):
'''
Base class for unit tests.
'''
+
class BaseTransactionTest(BaseTestMixin, django.test.TransactionTestCase):
'''
Base class for tests requiring transactions (or where the test database
needs to be accessed by subprocesses).
'''
+
@override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
ANSIBLE_TRANSPORT='local')
@@ -698,6 +705,7 @@ class BaseLiveServerTest(BaseTestMixin, django.test.LiveServerTestCase):
super(BaseLiveServerTest, self).setUp()
settings.INTERNAL_API_URL = self.live_server_url
+
@override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
ANSIBLE_TRANSPORT='local',
diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py
index 3412ca1ed8..1fdb3d1ca2 100644
--- a/awx/main/tests/conftest.py
+++ b/awx/main/tests/conftest.py
@@ -11,22 +11,27 @@ from awx.main.tests.factories import (
create_workflow_job_template,
)
+
@pytest.fixture
def job_template_factory():
return create_job_template
+
@pytest.fixture
def organization_factory():
return create_organization
+
@pytest.fixture
def notification_template_factory():
return create_notification_template
+
@pytest.fixture
def survey_spec_factory():
return create_survey_spec
+
@pytest.fixture
def job_template_with_survey_passwords_factory(job_template_factory):
def rf(persisted):
@@ -38,22 +43,27 @@ def job_template_with_survey_passwords_factory(job_template_factory):
return objects.job_template
return rf
+
@pytest.fixture
def job_with_secret_key_unit(job_with_secret_key_factory):
return job_with_secret_key_factory(persisted=False)
+
@pytest.fixture
def workflow_job_template_factory():
return create_workflow_job_template
+
@pytest.fixture
def get_ssh_version(mocker):
return mocker.patch('awx.main.tasks.get_ssh_version', return_value='OpenSSH_6.9p1, LibreSSL 2.1.8')
+
@pytest.fixture
def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory):
return job_template_with_survey_passwords_factory(persisted=False)
+
@pytest.fixture
def enterprise_license():
from awx.main.task_engine import TaskEnhancer
diff --git a/awx/main/tests/factories/fixtures.py b/awx/main/tests/factories/fixtures.py
index 8f6e5df414..41d95d8089 100644
--- a/awx/main/tests/factories/fixtures.py
+++ b/awx/main/tests/factories/fixtures.py
@@ -25,6 +25,7 @@ from awx.main.models import (
# persisted=False
#
+
def mk_instance(persisted=True):
if not persisted:
raise RuntimeError('creating an Instance requires persisted=True')
@@ -158,6 +159,7 @@ def mk_job_template(name, job_type='run',
jt.save()
return jt
+
def mk_workflow_job(status='new', workflow_job_template=None, extra_vars={},
persisted=True):
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()
return job
+
def mk_workflow_job_template(name, extra_vars='', spec=None, organization=None, persisted=True):
if 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()
return wfjt
+
def mk_workflow_job_template_node(workflow_job_template=None,
unified_job_template=None,
success_nodes=None,
@@ -197,6 +201,7 @@ def mk_workflow_job_template_node(workflow_job_template=None,
workflow_node.save()
return workflow_node
+
def mk_workflow_job_node(unified_job_template=None,
success_nodes=None,
failure_nodes=None,
diff --git a/awx/main/tests/factories/objects.py b/awx/main/tests/factories/objects.py
index 9f739cc9cf..7de49d998c 100644
--- a/awx/main/tests/factories/objects.py
+++ b/awx/main/tests/factories/objects.py
@@ -2,6 +2,7 @@ from collections import namedtuple
from .exc import NotUnique
+
def generate_objects(artifacts, kwargs):
'''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
diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py
index 5a23a01577..9eb26538e8 100644
--- a/awx/main/tests/factories/tower.py
+++ b/awx/main/tests/factories/tower.py
@@ -87,6 +87,7 @@ def apply_roles(roles, objects, persisted):
else:
raise RuntimeError('unable to add non-user {} for members list of {}'.format(member_str, obj_str))
+
def generate_users(organization, teams, superuser, persisted, **kwargs):
'''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.
@@ -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)
return users
+
def generate_teams(organization, persisted, **kwargs):
'''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.
@@ -126,6 +128,7 @@ def generate_teams(organization, persisted, **kwargs):
teams[t] = mk_team(t, organization=organization, persisted=persisted)
return teams
+
def create_survey_spec(variables=None, default_type='integer', required=True):
'''
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
#
+
def create_job_template(name, roles=None, persisted=True, **kwargs):
Objects = generate_objects(["job_template", "jobs",
"organization",
@@ -260,6 +264,7 @@ def create_job_template(name, roles=None, persisted=True, **kwargs):
organization=org,
survey=spec,)
+
def create_organization(name, roles=None, persisted=True, **kwargs):
Objects = generate_objects(["organization",
"teams", "users",
@@ -319,6 +324,7 @@ def create_organization(name, roles=None, persisted=True, **kwargs):
notification_templates=_Mapped(notification_templates),
inventories=_Mapped(inventories))
+
def create_notification_template(name, roles=None, persisted=True, **kwargs):
Objects = generate_objects(["notification_template",
"organization",
@@ -346,6 +352,7 @@ def create_notification_template(name, roles=None, persisted=True, **kwargs):
superusers=_Mapped(superusers),
teams=teams)
+
def generate_workflow_job_template_nodes(workflow_job_template,
persisted,
**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]:
getattr(new_node, node_type).add(new_nodes[related_index])
+
# TODO: Implement survey and jobs
def create_workflow_job_template(name, organization=None, persisted=True, **kwargs):
Objects = generate_objects(["workflow_job_template",
diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py
index 85bb70f65d..8315504996 100644
--- a/awx/main/tests/functional/api/test_activity_streams.py
+++ b/awx/main/tests/functional/api/test_activity_streams.py
@@ -7,13 +7,16 @@ from awx.main.access import ActivityStreamAccess
from django.core.urlresolvers import reverse
+
def mock_feature_enabled(feature):
return True
+
@pytest.fixture
def activity_stream_entry(organization, org_admin):
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)
@pytest.mark.django_db
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
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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 response.data['summary_fields']['organization'][0]['name'] == 'test-org'
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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.data['summary_fields']['actor']['username'] == 'admin-poster'
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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.object_relationship_type == 'awx.main.models.organization.Organization.admin_role'
+
@pytest.mark.django_db
@pytest.mark.activity_stream_access
@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_delete(activity_stream_entry)
+
@pytest.mark.django_db
@pytest.mark.activity_stream_access
@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(notification_template__pk=notification_template.pk, operation='create').count() == 1
+
@pytest.mark.django_db
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
def test_stream_user_direct_role_updates(get, post, organization_factory):
diff --git a/awx/main/tests/functional/api/test_adhoc.py b/awx/main/tests/functional/api/test_adhoc.py
index e7029b0c79..d2ab578a22 100644
--- a/awx/main/tests/functional/api/test_adhoc.py
+++ b/awx/main/tests/functional/api/test_adhoc.py
@@ -4,7 +4,6 @@ import pytest
from django.core.urlresolvers import reverse
-
"""
def run_test_ad_hoc_command(self, **kwargs):
# 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)
"""
+
@pytest.fixture
def post_adhoc(post, inventory, machine_credential):
def f(url, data, user, expect=201):
@@ -46,7 +46,6 @@ def post_adhoc(post, inventory, machine_credential):
return f
-
@pytest.mark.django_db
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)
@@ -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):
post(reverse('api:ad_hoc_command_list'), {}, admin, expect=400)
+
@pytest.mark.django_db
def test_empty_put_405(admin, put):
put(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
+
@pytest.mark.django_db
def test_empty_patch_405(admin, patch):
patch(reverse('api:ad_hoc_command_list'), {}, admin, expect=405)
+
@pytest.mark.django_db
def test_empty_delete_405(admin, delete):
delete(reverse('api:ad_hoc_command_list'), admin, expect=405)
+
@pytest.mark.django_db
def test_user_post_ad_hoc_command_list(alice, post_adhoc, inventory, machine_credential):
inventory.adhoc_role.members.add(alice)
machine_credential.use_role.members.add(alice)
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=201)
+
@pytest.mark.django_db
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.
machine_credential.use_role.members.add(alice)
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
+
@pytest.mark.django_db
def test_user_post_ad_hoc_command_list_without_creds(alice, post_adhoc, inventory, machine_credential):
inventory.adhoc_role.members.add(alice)
post_adhoc(reverse('api:ad_hoc_command_list'), {}, alice, expect=403)
+
@pytest.mark.django_db
def test_user_post_ad_hoc_command_list_without_inventory(alice, post_adhoc, inventory, machine_credential):
machine_credential.use_role.members.add(alice)
@@ -145,4 +151,3 @@ def test_bad_data3(admin, post_adhoc):
@pytest.mark.django_db
def test_bad_data4(admin, post_adhoc):
post_adhoc(reverse('api:ad_hoc_command_list'), {'forks': -1}, admin, expect=400)
-
diff --git a/awx/main/tests/functional/api/test_create_attach_views.py b/awx/main/tests/functional/api/test_create_attach_views.py
index 5399356a21..48f3aadc7b 100644
--- a/awx/main/tests/functional/api/test_create_attach_views.py
+++ b/awx/main/tests/functional/api/test_create_attach_views.py
@@ -16,6 +16,7 @@ def test_user_role_view_access(rando, inventory, mocker, post):
inventory.admin_role, rando, 'members', data,
skip_sub_obj_read_check=False)
+
@pytest.mark.django_db
def test_team_role_view_access(rando, team, inventory, mocker, post):
"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,
skip_sub_obj_read_check=False)
+
@pytest.mark.django_db
def test_role_team_view_access(rando, team, inventory, mocker, post):
"""Assure that /role/N/teams/ enforces the same permission restrictions
diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py
index 0e22f3d845..bd6cd25841 100644
--- a/awx/main/tests/functional/api/test_credential.py
+++ b/awx/main/tests/functional/api/test_credential.py
@@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse
# user credential creation
#
+
@pytest.mark.django_db
def test_create_user_credential_via_credentials_list(post, get, alice):
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.data['count'] == 1
+
@pytest.mark.django_db
def test_credential_validation_error_with_bad_user(post, admin):
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.data['user'][0] == 'Incorrect type. Expected pk value, received unicode.'
+
@pytest.mark.django_db
def test_create_user_credential_via_user_credentials_list(post, get, alice):
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.data['count'] == 1
+
@pytest.mark.django_db
def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
response = post(reverse('api:credential_list'), {
@@ -53,6 +57,7 @@ def test_create_user_credential_via_credentials_list_xfail(post, alice, bob):
}, alice)
assert response.status_code == 403
+
@pytest.mark.django_db
def test_create_user_credential_via_user_credentials_list_xfail(post, alice, bob):
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
#
+
@pytest.mark.django_db
def test_create_team_credential(post, get, team, organization, org_admin, team_member):
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
assert response.data['results'][0]['summary_fields']['organization']['id'] == team.organization.id
+
@pytest.mark.django_db
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,)), {
@@ -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.data['count'] == 1
+
@pytest.mark.django_db
def test_create_team_credential_by_urelated_user_xfail(post, team, organization, alice, team_member):
response = post(reverse('api:credential_list'), {
@@ -106,6 +114,7 @@ def test_create_team_credential_by_urelated_user_xfail(post, team, organization,
}, alice)
assert response.status_code == 403
+
@pytest.mark.django_db
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?
@@ -122,6 +131,7 @@ def test_create_team_credential_by_team_member_xfail(post, team, organization, a
# Permission granting
#
+
@pytest.mark.django_db
def test_grant_org_credential_to_org_user_through_role_users(post, credential, organization, org_admin, org_member):
credential.organization = organization
@@ -131,6 +141,7 @@ def test_grant_org_credential_to_org_user_through_role_users(post, credential, o
}, org_admin)
assert response.status_code == 204
+
@pytest.mark.django_db
def test_grant_org_credential_to_org_user_through_user_roles(post, credential, organization, org_admin, org_member):
credential.organization = organization
@@ -140,6 +151,7 @@ def test_grant_org_credential_to_org_user_through_user_roles(post, credential, o
}, org_admin)
assert response.status_code == 204
+
@pytest.mark.django_db
def test_grant_org_credential_to_non_org_user_through_role_users(post, credential, organization, org_admin, alice):
credential.organization = organization
@@ -149,6 +161,7 @@ def test_grant_org_credential_to_non_org_user_through_role_users(post, credentia
}, org_admin)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_grant_org_credential_to_non_org_user_through_user_roles(post, credential, organization, org_admin, alice):
credential.organization = organization
@@ -158,6 +171,7 @@ def test_grant_org_credential_to_non_org_user_through_user_roles(post, credentia
}, org_admin)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_grant_private_credential_to_user_through_role_users(post, credential, alice, bob):
# normal users can't do this
@@ -167,6 +181,7 @@ def test_grant_private_credential_to_user_through_role_users(post, credential, a
}, alice)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_grant_private_credential_to_org_user_through_role_users(post, credential, org_admin, org_member):
# org admins can't either
@@ -176,6 +191,7 @@ def test_grant_private_credential_to_org_user_through_role_users(post, credentia
}, org_admin)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_sa_grant_private_credential_to_user_through_role_users(post, credential, admin, bob):
# but system admins can
@@ -184,6 +200,7 @@ def test_sa_grant_private_credential_to_user_through_role_users(post, credential
}, admin)
assert response.status_code == 204
+
@pytest.mark.django_db
def test_grant_private_credential_to_user_through_user_roles(post, credential, alice, bob):
# normal users can't do this
@@ -193,6 +210,7 @@ def test_grant_private_credential_to_user_through_user_roles(post, credential, a
}, alice)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_grant_private_credential_to_org_user_through_user_roles(post, credential, org_admin, org_member):
# org admins can't either
@@ -202,6 +220,7 @@ def test_grant_private_credential_to_org_user_through_user_roles(post, credentia
}, org_admin)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_sa_grant_private_credential_to_user_through_user_roles(post, credential, admin, bob):
# but system admins can
@@ -210,6 +229,7 @@ def test_sa_grant_private_credential_to_user_through_user_roles(post, credential
}, admin)
assert response.status_code == 204
+
@pytest.mark.django_db
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
@@ -221,6 +241,7 @@ def test_grant_org_credential_to_team_through_role_teams(post, credential, organ
assert response.status_code == 204
assert org_auditor in credential.read_role
+
@pytest.mark.django_db
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
@@ -232,6 +253,7 @@ def test_grant_org_credential_to_team_through_team_roles(post, credential, organ
assert response.status_code == 204
assert org_auditor in credential.read_role
+
@pytest.mark.django_db
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
@@ -240,6 +262,7 @@ def test_sa_grant_private_credential_to_team_through_role_teams(post, credential
}, admin)
assert response.status_code == 400
+
@pytest.mark.django_db
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
@@ -249,12 +272,11 @@ def test_sa_grant_private_credential_to_team_through_team_roles(post, credential
assert response.status_code == 400
-
-
#
# organization credentials
#
+
@pytest.mark.django_db
def test_create_org_credential_as_not_admin(post, organization, org_member):
response = post(reverse('api:credential_list'), {
@@ -264,6 +286,7 @@ def test_create_org_credential_as_not_admin(post, organization, org_member):
}, org_member)
assert response.status_code == 403
+
@pytest.mark.django_db
def test_create_org_credential_as_admin(post, organization, org_admin):
response = post(reverse('api:credential_list'), {
@@ -273,6 +296,7 @@ def test_create_org_credential_as_admin(post, organization, org_admin):
}, org_admin)
assert response.status_code == 201
+
@pytest.mark.django_db
def test_credential_detail(post, get, organization, org_admin):
response = post(reverse('api:credential_list'), {
@@ -288,6 +312,7 @@ def test_credential_detail(post, get, organization, org_admin):
related_fields = response.data['related']
assert 'organization' in related_fields
+
@pytest.mark.django_db
def test_list_created_org_credentials(post, get, organization, org_admin, org_member):
response = post(reverse('api:credential_list'), {
@@ -336,6 +361,7 @@ def test_cant_change_organization(patch, credential, organization, org_admin):
}, org_admin)
assert response.status_code == 403
+
@pytest.mark.django_db
def test_cant_add_organization(patch, credential, organization, org_admin):
assert credential.organization is None
@@ -350,6 +376,7 @@ def test_cant_add_organization(patch, credential, organization, org_admin):
# Openstack Credentials
#
+
@pytest.mark.django_db
def test_openstack_create_ok(post, organization, admin):
data = {
@@ -364,6 +391,7 @@ def test_openstack_create_ok(post, organization, admin):
response = post(reverse('api:credential_list'), data, admin)
assert response.status_code == 201
+
@pytest.mark.django_db
def test_openstack_create_fail_required_fields(post, organization, admin):
data = {
@@ -383,6 +411,7 @@ def test_openstack_create_fail_required_fields(post, organization, admin):
# misc xfail conditions
#
+
@pytest.mark.django_db
def test_create_credential_missing_user_team_org_xfail(post, admin):
# 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',
}, admin)
assert response.status_code == 400
-
diff --git a/awx/main/tests/functional/api/test_fact_versions.py b/awx/main/tests/functional/api/test_fact_versions.py
index fc521eb615..8fe06ff7af 100644
--- a/awx/main/tests/functional/api/test_fact_versions.py
+++ b/awx/main/tests/functional/api/test_fact_versions.py
@@ -13,12 +13,15 @@ from awx.main.utils import timestamp_apiformat
from django.core.urlresolvers import reverse
from django.utils import timezone
+
def mock_feature_enabled(feature):
return True
+
def mock_feature_disabled(feature):
return False
+
def setup_common(hosts, fact_scans, get, user, epoch=timezone.now(), get_params={}, host_count=1):
hosts = hosts(host_count=host_count)
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)
+
def check_url(url1_full, fact_known, module):
url1_split = urlparse.urlsplit(url1_full)
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])
assert urllib.urlencode(url1_params_sorted) == urllib.urlencode(url2_params_sorted)
+
def check_response_facts(facts_known, response):
for i, fact_known in enumerate(facts_known):
assert fact_known.module == response.data['results'][i]['module']
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)
+
def check_system_tracking_feature_forbidden(response):
assert 402 == response.status_code
assert 'Your license does not permit use of system tracking.' == response.data['detail']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -62,6 +69,7 @@ def test_system_tracking_license_get(hosts, get, user):
check_system_tracking_feature_forbidden(response)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -72,6 +80,7 @@ def test_system_tracking_license_options(hosts, options, user):
check_system_tracking_feature_forbidden(response)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -85,6 +94,7 @@ def test_no_facts_db(hosts, get, user):
}
assert response_expected == response.data
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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 'module' in results[0]
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
@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 ("packages", "Packages") in response.data['actions']['GET']['module']['choices']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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):
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)
@pytest.mark.django_db
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):
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)
@pytest.mark.django_db
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)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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)
+
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
hosts = hosts(host_count=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)
return response
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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 "You do not have permission to perform this action." == response.data['detail']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@pytest.mark.django_db
@@ -233,6 +253,7 @@ def test_super_user_ok(hosts, fact_scans, get, user, team):
assert 200 == response.status_code
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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)
assert 403 == response.status_code
-
diff --git a/awx/main/tests/functional/api/test_fact_view.py b/awx/main/tests/functional/api/test_fact_view.py
index fd646d9456..c97bf4ab2d 100644
--- a/awx/main/tests/functional/api/test_fact_view.py
+++ b/awx/main/tests/functional/api/test_fact_view.py
@@ -6,12 +6,15 @@ from awx.main.utils import timestamp_apiformat
from django.core.urlresolvers import reverse
from django.utils import timezone
+
def mock_feature_enabled(feature):
return True
+
def mock_feature_disabled(feature):
return False
+
# 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):
for f in facts:
@@ -19,6 +22,7 @@ def find_fact(facts, host_id, module_name, timestamp):
return f
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={}):
hosts = hosts(host_count=1)
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)
return (fact_known, response)
+
def check_system_tracking_feature_forbidden(response):
assert 402 == response.status_code
assert 'Your license does not permit use of system tracking.' == response.data['detail']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -43,6 +49,7 @@ def test_system_tracking_license_get(hosts, get, user):
check_system_tracking_feature_forbidden(response)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_disabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -53,6 +60,7 @@ def test_system_tracking_license_options(hosts, options, user):
check_system_tracking_feature_forbidden(response)
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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 expected_response == response.data
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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 reverse('api:host_detail', args=(hosts[0].pk,)) == response.data['related']['host']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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 fact_known.module == response.data['module']
+
def _test_search_by_module(hosts, fact_scans, get, user, fact_json, module_name):
params = {
'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 module_name == response.data['module']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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')
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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')
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
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']
+
def _test_user_access_control(hosts, fact_scans, get, user_obj, team_obj):
hosts = hosts(host_count=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)
return response
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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 "You do not have permission to perform this action." == response.data['detail']
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@pytest.mark.django_db
@@ -157,6 +174,7 @@ def test_super_user_ok(hosts, fact_scans, get, user, team):
assert 200 == response.status_code
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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
+
@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
@pytest.mark.ac
@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)
assert 403 == response.status_code
-
diff --git a/awx/main/tests/functional/api/test_host_detail.py b/awx/main/tests/functional/api/test_host_detail.py
index 79213490b0..acfe73f4b5 100644
--- a/awx/main/tests/functional/api/test_host_detail.py
+++ b/awx/main/tests/functional/api/test_host_detail.py
@@ -4,6 +4,7 @@ import pytest
from django.core.urlresolvers import reverse
+
@pytest.mark.django_db
def test_basic_fields(hosts, fact_scans, get, user):
hosts = hosts(host_count=1)
diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py
index 38e06dad07..925d7352fd 100644
--- a/awx/main/tests/functional/api/test_inventory.py
+++ b/awx/main/tests/functional/api/test_inventory.py
@@ -2,6 +2,7 @@ import pytest
from django.core.urlresolvers import reverse
+
@pytest.mark.django_db
def test_inventory_source_notification_on_cloud_only(get, post, group_factory, user, notification_template):
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)
post(reverse('api:inventory_groups_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
+
@pytest.mark.parametrize("role_field,expected_status_code", [
(None, 403),
('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)
post(reverse('api:inventory_hosts_list', args=(inventory.id,)), data, alice, expect=expected_status_code)
+
@pytest.mark.parametrize("role_field,expected_status_code", [
(None, 403),
('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)
delete(reverse('api:host_detail', args=(host.id,)), alice, expect=expected_status_code)
+
@pytest.mark.parametrize("role_field,expected_status_code", [
(None, 403),
('admin_role', 202),
diff --git a/awx/main/tests/functional/api/test_job_runtime_params.py b/awx/main/tests/functional/api/test_job_runtime_params.py
index af7d133c4c..ed48f7ec02 100644
--- a/awx/main/tests/functional/api/test_job_runtime_params.py
+++ b/awx/main/tests/functional/api/test_job_runtime_params.py
@@ -8,6 +8,7 @@ from awx.main.models.jobs import Job, JobTemplate
from django.core.urlresolvers import reverse
+
@pytest.fixture
def runtime_data(organization):
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,
)
+
@pytest.fixture
def job_with_links(machine_credential, inventory):
return Job.objects.create(name='existing-job', credential=machine_credential, inventory=inventory)
+
@pytest.fixture
def job_template_prompts(project, inventory, machine_credential):
def rf(on_off):
@@ -45,6 +48,7 @@ def job_template_prompts(project, inventory, machine_credential):
)
return rf
+
@pytest.fixture
def job_template_prompts_null(project):
return JobTemplate.objects.create(
@@ -62,6 +66,7 @@ def job_template_prompts_null(project):
ask_credential_on_launch=True,
)
+
@pytest.fixture
def bad_scan_JT(job_template_prompts):
job_template = job_template_prompts(True)
@@ -69,6 +74,7 @@ def bad_scan_JT(job_template_prompts):
job_template.save()
return job_template
+
# End of setup, tests start here
@pytest.mark.django_db
@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 'skip_tags' in response.data['ignored_fields']
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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)
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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='')
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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
mock_job.signal_start.assert_called_once_with(**runtime_data)
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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['credential'] == [u'Invalid pk "48474" - object does not exist.']
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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.']
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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."]
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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.'
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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.'
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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.limit == 'my_server'
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
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)
assert not inventory_user.can_access(Job, 'start', job_with_links)
+
@pytest.mark.django_db
def test_job_launch_JT_with_validation(machine_credential, deploy_jobtemplate):
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_obj.credential.id == machine_credential.id
+
@pytest.mark.django_db
@pytest.mark.job_runtime_vars
def test_job_launch_unprompted_vars_with_survey(mocker, survey_spec_factory, job_template_prompts, post, admin_user):
diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py
index ca6bdf3d31..4983aaac69 100644
--- a/awx/main/tests/functional/api/test_job_template.py
+++ b/awx/main/tests/functional/api/test_job_template.py
@@ -9,6 +9,7 @@ from awx.main.migrations import _save_password_keys as save_password_keys
from django.core.urlresolvers import reverse
from django.apps import apps
+
@pytest.mark.django_db
@pytest.mark.parametrize(
"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',
}, alice, expect=expect)
+
@pytest.mark.django_db
@pytest.mark.parametrize(
"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',
}, alice, expect=expect)
+
@pytest.mark.django_db
def test_edit_playbook(patch, job_template_factory, alice):
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',
}, alice, expect=403)
+
@pytest.mark.django_db
def test_edit_nonsenstive(patch, job_template_factory, alice):
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)
print(res.data)
assert res.data['name'] == 'updated'
+
+
@pytest.fixture
def jt_copy_edit(job_template_factory, project):
objects = job_template_factory(
@@ -111,6 +117,7 @@ def jt_copy_edit(job_template_factory, project):
project=project)
return objects.job_template
+
@pytest.mark.django_db
def test_job_template_role_user(post, organization_factory, job_template_factory):
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
-
@pytest.mark.django_db
def test_jt_admin_copy_edit_functional(jt_copy_edit, rando, get, post):
-
# Grant random user JT admin access only
jt_copy_edit.admin_role.members.add(rando)
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)
assert post_response.status_code == 403
+
@pytest.mark.django_db
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
@@ -175,6 +181,7 @@ def test_scan_jt_no_inventory(job_template_factory):
assert not serializer.is_valid()
assert 'inventory' in serializer.errors
+
@pytest.mark.django_db
def test_scan_jt_surveys(inventory):
serializer = JobTemplateSerializer(data={"name": "Test", "job_type": "scan",
@@ -183,6 +190,7 @@ def test_scan_jt_surveys(inventory):
assert not serializer.is_valid()
assert "survey_enabled" in serializer.errors
+
@pytest.mark.django_db
def test_jt_without_project(inventory):
data = dict(name="Test", job_type="run",
@@ -198,6 +206,7 @@ def test_jt_without_project(inventory):
serializer = JobTemplateSerializer(data=data)
assert serializer.is_valid()
+
@pytest.mark.django_db
def test_disallow_template_delete_on_running_job(job_template_factory, delete, admin_user):
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)
assert delete_response.status_code == 409
+
@pytest.mark.django_db
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
@@ -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()
assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'}
+
@pytest.mark.django_db
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
diff --git a/awx/main/tests/functional/api/test_organization_counts.py b/awx/main/tests/functional/api/test_organization_counts.py
index f20396e46b..500667e107 100644
--- a/awx/main/tests/functional/api/test_organization_counts.py
+++ b/awx/main/tests/functional/api/test_organization_counts.py
@@ -2,6 +2,7 @@ import pytest
from django.core.urlresolvers import reverse
+
@pytest.fixture
def organization_resource_creator(organization, user):
def rf(users, admins, job_templates, projects, inventories, teams):
@@ -58,10 +59,12 @@ COUNTS_ZEROS = {
'teams': 0
}
+
@pytest.fixture
def resourced_organization(organization_resource_creator):
return organization_resource_creator(**COUNTS_PRIMES)
+
@pytest.mark.django_db
def test_org_counts_detail_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser
@@ -73,6 +76,7 @@ def test_org_counts_detail_admin(resourced_organization, user, get):
counts = response.data['summary_fields']['related_field_counts']
assert counts == COUNTS_PRIMES
+
@pytest.mark.django_db
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
@@ -91,6 +95,7 @@ def test_org_counts_detail_member(resourced_organization, user, get):
'teams': 0
}
+
@pytest.mark.django_db
def test_org_counts_list_admin(resourced_organization, user, get):
# Check that all types of resources are counted by a superuser
@@ -101,6 +106,7 @@ def test_org_counts_list_admin(resourced_organization, user, get):
counts = response.data['results'][0]['summary_fields']['related_field_counts']
assert counts == COUNTS_PRIMES
+
@pytest.mark.django_db
def test_org_counts_list_member(resourced_organization, user, get):
# Check that a non-admin user can only see the full project and
@@ -120,6 +126,7 @@ def test_org_counts_list_member(resourced_organization, user, get):
'teams': 0
}
+
@pytest.mark.django_db
def test_new_org_zero_counts(user, post):
# Check that a POST to the organization list endpoint returns
@@ -133,6 +140,7 @@ def test_new_org_zero_counts(user, post):
counts_dict = new_org_list['summary_fields']['related_field_counts']
assert counts_dict == COUNTS_ZEROS
+
@pytest.mark.django_db
def test_two_organizations(resourced_organization, organizations, user, get):
# Check correct results for two organizations are returned
@@ -151,6 +159,7 @@ def test_two_organizations(resourced_organization, organizations, user, get):
assert counts[org_id_full] == COUNTS_PRIMES
assert counts[org_id_zero] == COUNTS_ZEROS
+
@pytest.mark.django_db
def test_scan_JT_counted(resourced_organization, user, get):
admin_user = user('admin', True)
@@ -171,6 +180,7 @@ def test_scan_JT_counted(resourced_organization, user, get):
assert detail_response.status_code == 200
assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict
+
@pytest.mark.django_db
def test_JT_associated_with_project(organizations, project, user, get):
# Check that adding a project to an organization gets the project's JT
diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py
index f3705c354b..66c828020a 100644
--- a/awx/main/tests/functional/api/test_rbac_displays.py
+++ b/awx/main/tests/functional/api/test_rbac_displays.py
@@ -172,6 +172,7 @@ def mock_access_method(mocker):
mock_method.__name__ = 'bars' # Required for a logging statement
return mock_method
+
@pytest.mark.django_db
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={})
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
+
@pytest.mark.django_db
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
@@ -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={})
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach'] == 'foobar'
+
@pytest.mark.django_db
def test_team_roles_unattach_functional(team, team_member, inventory, get):
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
assert response.data['results'][0]['summary_fields']['user_capabilities']['unattach']
+
@pytest.mark.django_db
def test_user_roles_unattach_functional(organization, alice, bob, get):
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)
assert qs[0].capabilities_cache == {'edit': False, 'start': True}
+
@pytest.mark.django_db
def test_prefetch_group_capabilities(group, 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)
assert qs[0].capabilities_cache == {'edit': False, 'adhoc': True}
+
@pytest.mark.django_db
def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_credential, rando):
job_template.project = project
@@ -309,11 +315,13 @@ def test_prefetch_jt_copy_capability(job_template, project, inventory, machine_c
]}], JobTemplate, rando)
assert qs[0].capabilities_cache == {'copy': True}
+
@pytest.mark.django_db
def test_manual_projects_no_update(project, get, admin_user):
response = get(reverse('api:project_detail', args=[project.pk]), admin_user, expect=200)
assert not response.data['summary_fields']['user_capabilities']['start']
+
@pytest.mark.django_db
def test_group_update_capabilities_possible(group, inventory_source, admin_user):
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'])
assert capabilities['start']
+
@pytest.mark.django_db
def test_group_update_capabilities_impossible(group, inventory_source, admin_user):
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'])
assert not capabilities['start']
+
@pytest.mark.django_db
def test_license_check_not_called(mocker, job_template, project, org_admin, get):
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):
get(reverse('api:job_template_detail', args=[job_template.pk]), org_admin, expect=200)
assert not mock_license_check.called
-
diff --git a/awx/main/tests/functional/api/test_resource_access_lists.py b/awx/main/tests/functional/api/test_resource_access_lists.py
index 9d8d95c98a..433c14a9c4 100644
--- a/awx/main/tests/functional/api/test_resource_access_lists.py
+++ b/awx/main/tests/functional/api/test_resource_access_lists.py
@@ -3,6 +3,7 @@ import pytest
from django.core.urlresolvers import reverse
from awx.main.models import Role
+
@pytest.mark.django_db
def test_indirect_access_list(get, organization, project, team_factory, user, admin):
project_admin = user('project_admin')
diff --git a/awx/main/tests/functional/api/test_role.py b/awx/main/tests/functional/api/test_role.py
index 94215521a5..98610bf8e4 100644
--- a/awx/main/tests/functional/api/test_role.py
+++ b/awx/main/tests/functional/api/test_role.py
@@ -2,6 +2,7 @@ import pytest
from django.core.urlresolvers import reverse
+
@pytest.mark.django_db
def test_admin_visible_to_orphaned_users(get, alice):
names = set()
diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py
index 1e64222522..7f57eb4fa0 100644
--- a/awx/main/tests/functional/api/test_settings.py
+++ b/awx/main/tests/functional/api/test_settings.py
@@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse
# AWX
from awx.conf.models import Setting
+
'''
Ensures that tests don't pick up dev container license file
'''
@@ -19,9 +20,9 @@ def mock_no_license_file(mocker):
os.environ['AWX_LICENSE_FILE'] = '/does_not_exist'
return None
+
@pytest.mark.django_db
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',))
response = get(url, user=admin, expect=200)
assert not response.data['LICENSE']
diff --git a/awx/main/tests/functional/api/test_survey_spec.py b/awx/main/tests/functional/api/test_survey_spec.py
index 814a83f1ae..f954538973 100644
--- a/awx/main/tests/functional/api/test_survey_spec.py
+++ b/awx/main/tests/functional/api/test_survey_spec.py
@@ -16,11 +16,13 @@ def mock_no_surveys(self, add_host=False, feature=None, check_expiration=True):
else:
pass
+
@pytest.fixture
def job_template_with_survey(job_template_factory):
objects = job_template_factory('jt', project='prj', survey='submitted_email')
return objects.job_template
+
# Survey license-based denial tests
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
@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)
assert response.data['detail'] == 'Your license does not allow adding surveys.'
+
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
@pytest.mark.django_db
@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)
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)
@pytest.mark.django_db
@pytest.mark.survey
@@ -48,6 +52,7 @@ def test_job_start_blocked_without_survey_license(job_template_with_survey, admi
with pytest.raises(LicenseForbids):
access.can_start(job_template_with_survey)
+
@mock.patch('awx.main.access.BaseAccess.check_license', mock_no_surveys)
@pytest.mark.django_db
@pytest.mark.survey
@@ -65,6 +70,7 @@ def test_deny_creating_with_survey(project, post, admin_user):
user=admin_user, expect=402)
assert response.data['detail'] == 'Feature surveys is not enabled in the active license.'
+
# Test normal operations with survey license work
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
@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,)),
admin_user, expect=200)
+
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
@pytest.mark.django_db
@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)
assert updated_jt.survey_spec == survey_input_data
+
# Tests related to survey content validation
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
@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)
assert response.data['error'] == "Survey question 0 is not a json object."
+
@mock.patch('awx.api.views.feature_enabled', lambda feature: True)
@pytest.mark.django_db
@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)
assert response.data['error'] == "'variable' 'submitter_email' duplicated in survey question 1."
+
# Test actions that should be allowed with non-survey license
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
@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)
assert access.can_change(job_template_with_survey, dict(survey_enabled=False))
+
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
@pytest.mark.django_db
@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={}))
+
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
@pytest.mark.django_db
@pytest.mark.survey
@@ -137,6 +149,7 @@ def test_job_start_allowed_with_survey_spec(job_template_factory, admin_user):
access = JobTemplateAccess(admin_user)
assert access.can_start(job_template_with_survey, {})
+
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
@pytest.mark.django_db
@pytest.mark.survey
@@ -146,6 +159,7 @@ def test_job_template_delete_access_with_survey(job_template_with_survey, admin_
access = JobTemplateAccess(admin_user)
assert access.can_delete(job_template_with_survey)
+
@mock.patch('awx.api.views.feature_enabled', lambda feature: False)
@mock.patch('awx.main.access.BaseAccess.check_license', new=mock_no_surveys)
@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)
assert new_jt.survey_spec == {}
+
@mock.patch('awx.main.access.BaseAccess.check_license', lambda self, **kwargs: True)
@mock.patch('awx.main.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
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)
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.models.unified_jobs.UnifiedJobTemplate.create_unified_job',
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()
post(reverse('api:job_template_launch', args=[obj.pk]), {}, admin_user, expect=201)
+
@pytest.mark.django_db
@pytest.mark.survey
def test_redact_survey_passwords_in_activity_stream(job_template_with_survey_passwords):
diff --git a/awx/main/tests/functional/api/test_unified_jobs_view.py b/awx/main/tests/functional/api/test_unified_jobs_view.py
index ed7034a28e..0f0a2ca956 100644
--- a/awx/main/tests/functional/api/test_unified_jobs_view.py
+++ b/awx/main/tests/functional/api/test_unified_jobs_view.py
@@ -35,6 +35,7 @@ def test_cases(project):
ret.append(e)
return ret
+
@pytest.fixture
def negative_test_cases(job_factory):
ret = []
@@ -53,6 +54,7 @@ formats = [
('html', 'text/html'),
]
+
@pytest.mark.parametrize("format,content_type", formats)
@pytest.mark.django_db
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 content.count(test_data['uri'].host) == test_data['occurrences']
+
@pytest.mark.parametrize("format,content_type", formats)
@pytest.mark.django_db
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
def test_options_fields_choices(instance, options, user):
-
url = reverse('api:unified_job_list')
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 'choice' == response.data['actions']['GET']['status']['type']
assert UnifiedJob.STATUS_CHOICES == response.data['actions']['GET']['status']['choices']
-
-
diff --git a/awx/main/tests/functional/api/test_user.py b/awx/main/tests/functional/api/test_user.py
index d739d417c0..fb4c7a33e0 100644
--- a/awx/main/tests/functional/api/test_user.py
+++ b/awx/main/tests/functional/api/test_user.py
@@ -7,6 +7,7 @@ from django.core.urlresolvers import reverse
# user creation
#
+
@pytest.mark.django_db
def test_user_create(post, admin):
response = post(reverse('api:user_list'), {
@@ -19,6 +20,7 @@ def test_user_create(post, admin):
}, admin)
assert response.status_code == 201
+
@pytest.mark.django_db
def test_fail_double_create_user(post, admin):
response = post(reverse('api:user_list'), {
@@ -41,6 +43,7 @@ def test_fail_double_create_user(post, admin):
}, admin)
assert response.status_code == 400
+
@pytest.mark.django_db
def test_create_delete_create_user(post, delete, admin):
response = post(reverse('api:user_list'), {
diff --git a/awx/main/tests/functional/commands/conftest.py b/awx/main/tests/functional/commands/conftest.py
index 2de8846b0a..2917c10fcc 100644
--- a/awx/main/tests/functional/commands/conftest.py
+++ b/awx/main/tests/functional/commands/conftest.py
@@ -3,6 +3,7 @@ import time
from datetime import datetime
+
@pytest.fixture
def fact_msg_base(inventory, hosts):
host_objs = hosts(1)
@@ -13,6 +14,7 @@ def fact_msg_base(inventory, hosts):
'inventory_id': inventory.id
}
+
@pytest.fixture
def fact_msg_small(fact_msg_base):
fact_msg_base['facts'] = {
@@ -77,7 +79,7 @@ def fact_msg_small(fact_msg_base):
}
}
return fact_msg_base
-
+
'''
Facts sent from ansible to our fact cache reciever.
@@ -92,18 +94,20 @@ key of 'ansible'
}
'''
+
@pytest.fixture
def fact_msg_ansible(fact_msg_base, fact_ansible_json):
fact_msg_base['facts'] = fact_ansible_json
return fact_msg_base
+
@pytest.fixture
def fact_msg_packages(fact_msg_base, fact_packages_json):
fact_msg_base['facts']['packages'] = fact_packages_json
return fact_msg_base
+
@pytest.fixture
def fact_msg_services(fact_msg_base, fact_services_json):
fact_msg_base['facts']['services'] = fact_services_json
return fact_msg_base
-
diff --git a/awx/main/tests/functional/commands/test_cleanup_facts.py b/awx/main/tests/functional/commands/test_cleanup_facts.py
index 206fe04d2e..99849084bd 100644
--- a/awx/main/tests/functional/commands/test_cleanup_facts.py
+++ b/awx/main/tests/functional/commands/test_cleanup_facts.py
@@ -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.inventory import Host
+
def mock_feature_enabled(feature):
return True
+
def mock_feature_disabled(feature):
return False
+
@pytest.mark.django_db
def test_cleanup_granularity(fact_scans, hosts):
epoch = timezone.now()
@@ -52,6 +55,7 @@ def test_cleanup_older_than(fact_scans, hosts):
deleted_count = cleanup_facts.cleanup(fact_middle.timestamp, granularity)
assert 210 == deleted_count
+
@pytest.mark.django_db
def test_cleanup_older_than_granularity_module(fact_scans, hosts):
epoch = timezone.now()
@@ -96,6 +100,7 @@ def test_cleanup_logic(fact_scans, hosts):
timestamp_pivot -= granularity
assert fact.timestamp == timestamp_pivot
+
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_disabled)
@pytest.mark.django_db
@pytest.mark.license_feature
@@ -105,6 +110,7 @@ def test_system_tracking_feature_disabled(mocker):
cmd.handle(None)
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)
@pytest.mark.django_db
def test_parameters_ok(mocker):
@@ -118,6 +124,7 @@ def test_parameters_ok(mocker):
cmd.handle(None, **kv)
run.assert_called_once_with(relativedelta(days=1), relativedelta(days=1), module=None)
+
@pytest.mark.django_db
def test_string_time_to_timestamp_ok():
kvs = [
@@ -147,6 +154,7 @@ def test_string_time_to_timestamp_ok():
res = cmd.string_time_to_timestamp(kv['time'])
assert kv['timestamp'] == res
+
@pytest.mark.django_db
def test_string_time_to_timestamp_invalid():
kvs = [
@@ -176,6 +184,7 @@ def test_string_time_to_timestamp_invalid():
res = cmd.string_time_to_timestamp(kv['time'])
assert res is None
+
@mock.patch('awx.main.management.commands.cleanup_facts.feature_enabled', new=mock_feature_enabled)
@pytest.mark.django_db
def test_parameters_fail(mocker):
@@ -198,4 +207,3 @@ def test_parameters_fail(mocker):
with pytest.raises(CommandError) as err:
cmd.handle(None, older_than=kv['older_than'], granularity=kv['granularity'])
assert kv['msg'] in err.value
-
diff --git a/awx/main/tests/functional/commands/test_commands.py b/awx/main/tests/functional/commands/test_commands.py
index d3f4e6b949..95cd291cee 100644
--- a/awx/main/tests/functional/commands/test_commands.py
+++ b/awx/main/tests/functional/commands/test_commands.py
@@ -10,6 +10,7 @@ from django.core.management import call_command
from awx.main.management.commands.update_password import UpdatePassword
+
def run_command(name, *args, **options):
command_runner = options.pop('command_runner', call_command)
stdin_fileobj = options.pop('stdin_fileobj', None)
diff --git a/awx/main/tests/functional/commands/test_run_fact_cache_receiver.py b/awx/main/tests/functional/commands/test_run_fact_cache_receiver.py
index c6b00ef048..2e9ceb3b9e 100644
--- a/awx/main/tests/functional/commands/test_run_fact_cache_receiver.py
+++ b/awx/main/tests/functional/commands/test_run_fact_cache_receiver.py
@@ -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.inventory import Host
+
# TODO: Check that timestamp and other attributes are as expected
def check_process_fact_message_module(fact_returned, data, module_name):
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 module_name == fact_returned.module
+
@pytest.mark.django_db
def test_process_fact_message_ansible(fact_msg_ansible):
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')
+
@pytest.mark.django_db
def test_process_fact_message_packages(fact_msg_packages):
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')
+
@pytest.mark.django_db
def test_process_fact_message_services(fact_msg_services):
receiver = FactCacheReceiver()
@@ -83,6 +87,7 @@ def test_process_facts_message_ansible_overwrite(fact_scans, fact_msg_ansible):
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
+
# Ensure that the message flows from the socket through to process_fact_message()
@pytest.mark.django_db
def test_run_receiver(mocker, fact_msg_ansible):
diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py
index 9d7b913069..f272f3f88e 100644
--- a/awx/main/tests/functional/conftest.py
+++ b/awx/main/tests/functional/conftest.py
@@ -43,6 +43,7 @@ from awx.main.models.notifications import (
Notification
)
+
'''
Disable all django model signals.
'''
@@ -62,6 +63,7 @@ Allows django signal code to execute without the need for redis
def celery_memory_broker():
settings.BROKER_URL='memory://localhost/'
+
@pytest.fixture
def user():
def u(name, is_superuser=False):
@@ -73,6 +75,7 @@ def user():
return user
return u
+
@pytest.fixture
def check_jobtemplate(project, inventory, credential):
return \
@@ -84,6 +87,7 @@ def check_jobtemplate(project, inventory, credential):
name='check-job-template'
)
+
@pytest.fixture
def deploy_jobtemplate(project, inventory, credential):
return \
@@ -95,10 +99,12 @@ def deploy_jobtemplate(project, inventory, credential):
name='deploy-job-template'
)
+
@pytest.fixture
def team(organization):
return organization.teams.create(name='test-team')
+
@pytest.fixture
def team_member(user, team):
ret = user('team-member', False)
@@ -116,6 +122,7 @@ def project(instance, organization):
)
return prj
+
@pytest.fixture
def project_factory(organization):
def factory(name):
@@ -129,12 +136,14 @@ def project_factory(organization):
return prj
return factory
+
@pytest.fixture
def job_factory(job_template, 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 factory
+
@pytest.fixture
def team_factory(organization):
def factory(name):
@@ -147,35 +156,43 @@ def team_factory(organization):
return t
return factory
+
@pytest.fixture
def user_project(user):
owner = user('owner')
return Project.objects.create(name="test-user-project", created_by=owner, description="test-user-project-desc")
+
@pytest.fixture
def instance(settings):
return Instance.objects.create(uuid=settings.SYSTEM_UUID, hostname="instance.example.org", capacity=100)
+
@pytest.fixture
def organization(instance):
return Organization.objects.create(name="test-org", description="test-org-desc")
+
@pytest.fixture
def credential():
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret')
+
@pytest.fixture
def machine_credential():
return Credential.objects.create(name='machine-cred', kind='ssh', username='test_user', password='pas4word')
+
@pytest.fixture
def org_credential(organization):
return Credential.objects.create(kind='aws', name='test-cred', username='something', password='secret', organization=organization)
+
@pytest.fixture
def inventory(organization):
return organization.inventories.create(name="test-inv")
+
@pytest.fixture
def inventory_factory(organization):
def factory(name, org=organization):
@@ -186,10 +203,12 @@ def inventory_factory(organization):
return inv
return factory
+
@pytest.fixture
def label(organization):
return organization.labels.create(name="test-label", description="test-label-desc")
+
@pytest.fixture
def notification_template(organization):
return NotificationTemplate.objects.create(name='test-notification_template',
@@ -198,6 +217,7 @@ def notification_template(organization):
notification_configuration=dict(url="http://localhost",
headers={"Test": "Header"}))
+
@pytest.fixture
def notification(notification_template):
return Notification.objects.create(notification_template=notification_template,
@@ -207,27 +227,33 @@ def notification(notification_template):
recipients='admin@redhat.com',
subject='email subject')
+
@pytest.fixture
def job_template_with_survey_passwords(job_template_with_survey_passwords_factory):
return job_template_with_survey_passwords_factory(persisted=True)
+
@pytest.fixture
def admin(user):
return user('admin', True)
+
@pytest.fixture
def alice(user):
return user('alice', False)
+
@pytest.fixture
def bob(user):
return user('bob', False)
+
@pytest.fixture
def rando(user):
"Rando, the random user that doesn't have access to anything"
return user('rando', False)
+
@pytest.fixture
def org_admin(user, organization):
ret = user('org-admin', False)
@@ -235,6 +261,7 @@ def org_admin(user, organization):
organization.member_role.members.add(ret)
return ret
+
@pytest.fixture
def org_auditor(user, organization):
ret = user('org-auditor', False)
@@ -242,12 +269,14 @@ def org_auditor(user, organization):
organization.member_role.members.add(ret)
return ret
+
@pytest.fixture
def org_member(user, organization):
ret = user('org-member', False)
organization.member_role.members.add(ret)
return ret
+
@pytest.fixture
def organizations(instance):
def rf(organization_count=1):
@@ -258,6 +287,7 @@ def organizations(instance):
return orgs
return rf
+
@pytest.fixture
def group_factory(inventory):
def g(name):
@@ -267,6 +297,7 @@ def group_factory(inventory):
return Group.objects.create(inventory=inventory, name=name)
return g
+
@pytest.fixture
def hosts(group_factory):
group1 = group_factory('group-1')
@@ -282,23 +313,28 @@ def hosts(group_factory):
return hosts
return rf
+
@pytest.fixture
def group(inventory):
return inventory.groups.create(name='single-group')
+
@pytest.fixture
def inventory_source(group, inventory):
return InventorySource.objects.create(name=group.name, group=group,
inventory=inventory, source='gce')
+
@pytest.fixture
def inventory_update(inventory_source):
return InventoryUpdate.objects.create(inventory_source=inventory_source)
+
@pytest.fixture
def host(group, inventory):
return group.hosts.create(name='single-host', inventory=inventory)
+
@pytest.fixture
def permissions():
return {
@@ -340,36 +376,42 @@ def _request(verb):
return response
return rf
+
@pytest.fixture
def post():
return _request('post')
+
@pytest.fixture
def get():
return _request('get')
+
@pytest.fixture
def put():
return _request('put')
+
@pytest.fixture
def patch():
return _request('patch')
+
@pytest.fixture
def delete():
return _request('delete')
+
@pytest.fixture
def head():
return _request('head')
+
@pytest.fixture
def options():
return _request('options')
-
@pytest.fixture
def fact_scans(group_factory, fact_ansible_json, fact_packages_json, fact_services_json):
group1 = group_factory('group-1')
@@ -392,27 +434,33 @@ def fact_scans(group_factory, fact_ansible_json, fact_packages_json, fact_servic
return facts
return rf
+
def _fact_json(module_name):
current_dir = os.path.dirname(os.path.realpath(__file__))
with open('%s/%s.json' % (current_dir, module_name)) as f:
return json.load(f)
+
@pytest.fixture
def fact_ansible_json():
return _fact_json('ansible')
+
@pytest.fixture
def fact_packages_json():
return _fact_json('packages')
+
@pytest.fixture
def fact_services_json():
return _fact_json('services')
+
@pytest.fixture
def permission_inv_read(organization, inventory, team):
return Permission.objects.create(inventory=inventory, team=team, permission_type=PERM_INVENTORY_READ)
+
@pytest.fixture
def job_template(organization):
jt = JobTemplate(name='test-job_template')
@@ -420,6 +468,7 @@ def job_template(organization):
return jt
+
@pytest.fixture
def job_template_labels(organization, job_template):
job_template.labels.create(name="label-1", organization=organization)
diff --git a/awx/main/tests/functional/core/test_licenses.py b/awx/main/tests/functional/core/test_licenses.py
index c9b2af6dc0..f2c3d9348e 100644
--- a/awx/main/tests/functional/core/test_licenses.py
+++ b/awx/main/tests/functional/core/test_licenses.py
@@ -8,6 +8,7 @@ from datetime import datetime
from awx.main.models import Host
from awx.main.task_engine import TaskEnhancer
+
@pytest.mark.django_db
def test_license_writer(inventory, admin):
task_enhancer = TaskEnhancer(
@@ -50,6 +51,7 @@ def test_license_writer(inventory, admin):
assert vdata['compliant'] is False
assert vdata['subscription_name']
+
@pytest.mark.django_db
def test_expired_licenses():
task_enhancer = TaskEnhancer(
diff --git a/awx/main/tests/functional/models/fact/test_get_host_fact.py b/awx/main/tests/functional/models/fact/test_get_host_fact.py
index 4e74615b45..89234e3337 100644
--- a/awx/main/tests/functional/models/fact/test_get_host_fact.py
+++ b/awx/main/tests/functional/models/fact/test_get_host_fact.py
@@ -5,6 +5,7 @@ from django.utils import timezone
from awx.main.models import Fact
+
@pytest.mark.django_db
def test_newest_scan_exact(hosts, fact_scans):
epoch = timezone.now()
@@ -112,4 +113,3 @@ def test_by_module(hosts, fact_scans):
assert fact_found_services == fact_known_services
assert fact_found_packages == fact_known_packages
-
diff --git a/awx/main/tests/functional/models/fact/test_get_timeline.py b/awx/main/tests/functional/models/fact/test_get_timeline.py
index da3360340a..192bd40e86 100644
--- a/awx/main/tests/functional/models/fact/test_get_timeline.py
+++ b/awx/main/tests/functional/models/fact/test_get_timeline.py
@@ -5,6 +5,7 @@ from django.utils import timezone
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):
hosts = hosts(host_count=2)
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)
return (facts_known, fact_objs)
+
@pytest.mark.django_db
def test_all(hosts, fact_scans):
epoch = timezone.now()
@@ -30,6 +32,7 @@ def test_all(hosts, fact_scans):
assert 9 == len(facts_known)
assert 9 == len(fact_objs)
+
@pytest.mark.django_db
def test_all_ansible(hosts, fact_scans):
epoch = timezone.now()
@@ -43,6 +46,7 @@ def test_all_ansible(hosts, fact_scans):
for i in xrange(len(facts_known) - 1, 0):
assert facts_known[i].id == fact_objs[i].id
+
@pytest.mark.django_db
def test_empty_db(hosts, fact_scans):
hosts = hosts(host_count=2)
@@ -54,6 +58,7 @@ def test_empty_db(hosts, fact_scans):
assert 0 == len(fact_objs)
+
@pytest.mark.django_db
def test_no_results(hosts, fact_scans):
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)
assert 0 == len(fact_objs)
+
@pytest.mark.django_db
def test_exact_same_equal(hosts, fact_scans):
epoch = timezone.now()
@@ -74,6 +80,7 @@ def test_exact_same_equal(hosts, fact_scans):
assert facts_known[0].id == fact_objs[0].id
+
@pytest.mark.django_db
def test_exact_from_exclusive_to_inclusive(hosts, fact_scans):
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
+
@pytest.mark.django_db
def test_to_lte(hosts, fact_scans):
epoch = timezone.now()
@@ -101,6 +109,7 @@ def test_to_lte(hosts, fact_scans):
for i in xrange(0, len(fact_objs)):
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
+
@pytest.mark.django_db
def test_from_gt(hosts, fact_scans):
epoch = timezone.now()
@@ -115,6 +124,7 @@ def test_from_gt(hosts, fact_scans):
for i in xrange(0, len(fact_objs)):
assert facts_known_subset[len(facts_known_subset) - i - 1].id == fact_objs[i].id
+
@pytest.mark.django_db
def test_no_ts(hosts, fact_scans):
epoch = timezone.now()
@@ -125,5 +135,3 @@ def test_no_ts(hosts, fact_scans):
for i in xrange(len(facts_known) - 1, 0):
assert facts_known[i].id == fact_objs[i].id
-
-
diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py
index 309e29e4f0..1a9cbb4b0b 100644
--- a/awx/main/tests/functional/models/test_workflow.py
+++ b/awx/main/tests/functional/models/test_workflow.py
@@ -14,7 +14,6 @@ from django.test import TransactionTestCase
@pytest.mark.django_db
class TestWorkflowDAGFunctional(TransactionTestCase):
-
def workflow_job(self):
wfj = WorkflowJob.objects.create()
nodes = [WorkflowJobNode.objects.create(workflow_job=wfj) for i in range(0, 5)]
@@ -35,6 +34,7 @@ class TestWorkflowDAGFunctional(TransactionTestCase):
with self.assertNumQueries(4):
dag._init_graph(wfj)
+
@pytest.mark.django_db
class TestWorkflowJob:
@pytest.fixture
@@ -95,9 +95,9 @@ class TestWorkflowJob:
assert queued_node.get_job_kwargs()['extra_vars'] == {'a': 42, 'b': 43}
assert queued_node.ancestor_artifacts == {'a': 42, 'b': 43}
+
@pytest.mark.django_db
class TestWorkflowJobTemplate:
-
@pytest.fixture
def wfjt(self, workflow_job_template_factory):
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) ==
{'Error': 'Cannot associate failure_nodes when always_nodes have been associated.'})
+
@pytest.mark.django_db
class TestWorkflowJobFailure:
"""
diff --git a/awx/main/tests/functional/test_db_credential.py b/awx/main/tests/functional/test_db_credential.py
index 7ed76fdd62..90ec21d4cd 100644
--- a/awx/main/tests/functional/test_db_credential.py
+++ b/awx/main/tests/functional/test_db_credential.py
@@ -3,6 +3,7 @@ import pytest
from django.db import IntegrityError
from awx.main.models import Credential
+
@pytest.mark.django_db
def test_cred_unique_org_name_kind(organization_factory):
objects = organization_factory("test")
diff --git a/awx/main/tests/functional/test_fixture_factories.py b/awx/main/tests/functional/test_fixture_factories.py
index 1c25f9d38d..83d96fdbd3 100644
--- a/awx/main/tests/functional/test_fixture_factories.py
+++ b/awx/main/tests/functional/test_fixture_factories.py
@@ -2,6 +2,7 @@ import pytest
from awx.main.tests.factories import NotUnique
+
def test_roles_exc_not_persisted(organization_factory):
with pytest.raises(RuntimeError) as exc:
organization_factory('test-org', roles=['test-org.admin_role:user1'], persisted=False)
@@ -92,6 +93,7 @@ def test_job_template_factory(job_template_factory):
assert jt_objects.job_template.survey_spec is not None
assert 'test-survey' in jt_objects.jobs[1].extra_vars
+
def test_survey_spec_generator_simple(survey_spec_factory):
survey_spec = survey_spec_factory('survey_variable')
assert 'name' in survey_spec
@@ -100,6 +102,7 @@ def test_survey_spec_generator_simple(survey_spec_factory):
assert type(survey_spec['spec'][0]) is dict
assert survey_spec['spec'][0]['type'] == 'integer'
+
def test_survey_spec_generator_mixed(survey_spec_factory):
survey_spec = survey_spec_factory(
[{'variable': 'question1', 'type': 'integer', 'max': 87},
diff --git a/awx/main/tests/functional/test_jobs.py b/awx/main/tests/functional/test_jobs.py
index 8a44b622a8..ae6ee0e850 100644
--- a/awx/main/tests/functional/test_jobs.py
+++ b/awx/main/tests/functional/test_jobs.py
@@ -2,6 +2,7 @@ from awx.main.models import Job, Instance
from django.test.utils import override_settings
import pytest
+
@pytest.mark.django_db
def test_orphan_unified_job_creation(instance, inventory):
job = Job.objects.create(job_template=None, inventory=inventory, name='hi world')
@@ -10,6 +11,7 @@ def test_orphan_unified_job_creation(instance, inventory):
assert job2.inventory == inventory
assert job2.name == 'hi world'
+
@pytest.mark.django_db
def test_job_capacity_and_with_inactive_node():
Instance.objects.create(hostname='test-1', capacity=50)
diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py
index e5494edbea..cfa8ce76ee 100644
--- a/awx/main/tests/functional/test_notifications.py
+++ b/awx/main/tests/functional/test_notifications.py
@@ -7,6 +7,7 @@ from awx.main.models.jobs import JobTemplate
from django.core.urlresolvers import reverse
+
@pytest.mark.django_db
def test_get_notification_template_list(get, user, notification_template):
url = reverse('api:notification_template_list')
@@ -14,6 +15,7 @@ def test_get_notification_template_list(get, user, notification_template):
assert response.status_code == 200
assert len(response.data['results']) == 1
+
@pytest.mark.django_db
def test_basic_parameterization(get, post, user, organization):
u = user('admin-poster', True)
@@ -38,6 +40,7 @@ def test_basic_parameterization(get, post, user, organization):
assert 'url' in response.data['notification_configuration']
assert 'headers' in response.data['notification_configuration']
+
@pytest.mark.django_db
def test_encrypted_subfields(get, post, user, organization):
def assert_send(self, messages):
@@ -63,6 +66,7 @@ def test_encrypted_subfields(get, post, user, organization):
with mock.patch.object(notification_template_actual.notification_class, "send_messages", assert_send):
notification_template_actual.send("Test", {'body': "Test"})
+
@pytest.mark.django_db
def test_inherited_notification_templates(get, post, user, organization, project):
u = user('admin-poster', True)
@@ -98,6 +102,7 @@ def test_inherited_notification_templates(get, post, user, organization, project
assert len(project.notification_templates['any']) == 2
assert len(g.inventory_source.notification_templates['any']) == 1
+
@pytest.mark.django_db
def test_notification_template_merging(get, post, user, organization, project, notification_template):
user('admin-poster', True)
@@ -105,14 +110,17 @@ def test_notification_template_merging(get, post, user, organization, project, n
project.notification_templates_any.add(notification_template)
assert len(project.notification_templates['any']) == 1
+
@pytest.mark.django_db
def test_notification_template_simple_patch(patch, notification_template, admin):
patch(reverse('api:notification_template_detail', args=(notification_template.id,)), { 'name': 'foo'}, admin, expect=200)
+
@pytest.mark.django_db
def test_notification_template_invalid_notification_type(patch, notification_template, admin):
patch(reverse('api:notification_template_detail', args=(notification_template.id,)), { 'notification_type': 'invalid'}, admin, expect=400)
+
@pytest.mark.django_db
def test_disallow_delete_when_notifications_pending(delete, user, notification_template):
u = user('superuser', True)
diff --git a/awx/main/tests/functional/test_partial.py b/awx/main/tests/functional/test_partial.py
index d0b3ec6fa2..fd6d294cf2 100644
--- a/awx/main/tests/functional/test_partial.py
+++ b/awx/main/tests/functional/test_partial.py
@@ -20,10 +20,12 @@ from awx.main.scheduler.partial import (
InventoryUpdateLatestDict,
)
+
@pytest.fixture
def org():
return Organization.objects.create(name="org1")
+
class TestProjectUpdateLatestDictDict():
@pytest.fixture
def successful_project_update(self):
@@ -85,6 +87,7 @@ class TestInventoryUpdateDict():
assert 1 == len(tasks)
assert waiting_inventory_update.id == tasks[0]['id']
+
class TestInventoryUpdateLatestDict():
@pytest.fixture
def inventory(self, org):
diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py
index 0a8fec4e72..cd3f6e57ff 100644
--- a/awx/main/tests/functional/test_projects.py
+++ b/awx/main/tests/functional/test_projects.py
@@ -83,12 +83,14 @@ def test_team_project_list(get, team_project_list):
# alice should see all projects they can see when viewing an admin
assert get(reverse('api:user_projects_list', args=(admin.pk,)), alice).data['count'] == 2
+
@pytest.mark.django_db
def test_team_project_list_fail1(get, team_project_list):
objects = team_project_list
res = get(reverse('api:team_projects_list', args=(objects.teams.team2.pk,)), objects.users.alice)
assert res.status_code == 403
+
@pytest.mark.parametrize("u,expected_status_code", [
('rando', 403),
('org_member', 403),
@@ -115,18 +117,22 @@ def test_create_project(post, organization, org_admin, org_member, admin, rando,
if expected_status_code == 201:
assert Project.objects.filter(name='Project', organization=organization).exists()
+
@pytest.mark.django_db()
def test_create_project_null_organization(post, organization, admin):
post(reverse('api:project_list'), { 'name': 't', 'organization': None}, admin, expect=201)
+
@pytest.mark.django_db()
def test_create_project_null_organization_xfail(post, organization, org_admin):
post(reverse('api:project_list'), { 'name': 't', 'organization': None}, org_admin, expect=403)
+
@pytest.mark.django_db()
def test_patch_project_null_organization(patch, organization, project, admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': organization.id}, admin, expect=200)
+
@pytest.mark.django_db()
def test_patch_project_null_organization_xfail(patch, project, org_admin):
patch(reverse('api:project_detail', args=(project.id,)), { 'name': 't', 'organization': None}, org_admin, expect=400)
diff --git a/awx/main/tests/functional/test_rbac_api.py b/awx/main/tests/functional/test_rbac_api.py
index 5d5591cc67..8e8a24fc3c 100644
--- a/awx/main/tests/functional/test_rbac_api.py
+++ b/awx/main/tests/functional/test_rbac_api.py
@@ -5,11 +5,14 @@ from django.db import transaction
from django.core.urlresolvers import reverse
from awx.main.models.rbac import Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR
+
def mock_feature_enabled(feature):
return True
+
#@mock.patch('awx.api.views.feature_enabled', new=mock_feature_enabled)
+
@pytest.fixture
def role():
return Role.objects.create(role_field='admin_role')
@@ -19,6 +22,7 @@ def role():
# /roles
#
+
@pytest.mark.django_db
def test_get_roles_list_admin(organization, get, admin):
'Admin can see list of all roles'
@@ -28,6 +32,7 @@ def test_get_roles_list_admin(organization, get, admin):
roles = response.data
assert roles['count'] > 0
+
@pytest.mark.django_db
def test_get_roles_list_user(organization, inventory, team, get, user):
'Users can see all roles they have access to, but not all roles'
@@ -57,6 +62,7 @@ def test_get_roles_list_user(organization, inventory, team, get, user):
assert inventory.admin_role.id not in role_hash
assert team.member_role.id not in role_hash
+
@pytest.mark.django_db
def test_roles_visibility(get, organization, project, admin, alice, bob):
Role.singleton('system_auditor').members.add(alice)
@@ -66,6 +72,7 @@ def test_roles_visibility(get, organization, project, admin, alice, bob):
organization.auditor_role.members.add(bob)
assert get(reverse('api:role_list') + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1
+
@pytest.mark.django_db
def test_roles_filter_visibility(get, organization, project, admin, alice, bob):
Role.singleton('system_auditor').members.add(alice)
@@ -80,6 +87,7 @@ def test_roles_filter_visibility(get, organization, project, admin, alice, bob):
project.use_role.members.add(bob) # sibling role should still grant visibility
assert get(reverse('api:user_roles_list', args=(admin.id,)) + '?id=%d' % project.update_role.id, user=bob).data['count'] == 1
+
@pytest.mark.django_db
def test_cant_create_role(post, admin):
"Ensure we can't create new roles through the api"
@@ -105,6 +113,7 @@ def test_cant_delete_role(delete, admin):
# /user//roles
#
+
@pytest.mark.django_db
def test_get_user_roles_list(get, admin):
url = reverse('api:user_roles_list', args=(admin.id,))
@@ -113,6 +122,7 @@ def test_get_user_roles_list(get, admin):
roles = response.data
assert roles['count'] > 0 # 'system_administrator' role if nothing else
+
@pytest.mark.django_db
def test_user_view_other_user_roles(organization, inventory, team, get, alice, bob):
'Users can see roles for other users, but only the roles that that user has access to see as well'
@@ -159,8 +169,6 @@ def test_user_view_other_user_roles(organization, inventory, team, get, alice, b
assert team.member_role.id in role_hash # Alice can now see this
-
-
@pytest.mark.django_db
def test_add_role_to_user(role, post, admin):
assert admin.roles.filter(id=role.id).count() == 0
@@ -178,6 +186,7 @@ def test_add_role_to_user(role, post, admin):
assert response.status_code == 400
assert admin.roles.filter(id=role.id).count() == 1
+
@pytest.mark.django_db
def test_remove_role_from_user(role, post, admin):
assert admin.roles.filter(id=role.id).count() == 0
@@ -191,12 +200,11 @@ def test_remove_role_from_user(role, post, admin):
assert admin.roles.filter(id=role.id).count() == 0
-
-
#
# /team//roles
#
+
@pytest.mark.django_db
def test_get_teams_roles_list(get, team, organization, admin):
team.member_role.children.add(organization.admin_role)
@@ -226,6 +234,7 @@ def test_add_role_to_teams(team, post, admin):
assert response.status_code == 400
assert team.member_role.children.filter(id=team.member_role.id).count() == 1
+
@pytest.mark.django_db
def test_remove_role_from_teams(team, post, admin):
assert team.member_role.children.filter(id=team.member_role.id).count() == 0
@@ -239,11 +248,11 @@ def test_remove_role_from_teams(team, post, admin):
assert team.member_role.children.filter(id=team.member_role.id).count() == 0
-
#
# /roles//
#
+
@pytest.mark.django_db
def test_get_role(get, admin, role):
url = reverse('api:role_detail', args=(role.id,))
@@ -259,6 +268,7 @@ def test_put_role_405(put, admin, role):
#r = Role.objects.get(id=role.id)
#assert r.name == 'Some new name'
+
@pytest.mark.django_db
def test_put_role_access_denied(put, alice, role):
url = reverse('api:role_detail', args=(role.id,))
@@ -270,6 +280,7 @@ def test_put_role_access_denied(put, alice, role):
# /roles//users/
#
+
@pytest.mark.django_db
def test_get_role_users(get, admin, role):
role.members.add(admin)
@@ -279,6 +290,7 @@ def test_get_role_users(get, admin, role):
assert response.data['count'] == 1
assert response.data['results'][0]['id'] == admin.id
+
@pytest.mark.django_db
def test_add_user_to_role(post, admin, role):
url = reverse('api:role_users_list', args=(role.id,))
@@ -286,6 +298,7 @@ def test_add_user_to_role(post, admin, role):
post(url, {'id': admin.id}, admin)
assert role.members.filter(id=admin.id).count() == 1
+
@pytest.mark.django_db
def test_remove_user_to_role(post, admin, role):
role.members.add(admin)
@@ -294,6 +307,7 @@ def test_remove_user_to_role(post, admin, role):
post(url, {'disassociate': True, 'id': admin.id}, admin)
assert role.members.filter(id=admin.id).count() == 0
+
@pytest.mark.django_db
def test_org_admin_add_user_to_job_template(post, organization, check_jobtemplate, user):
'Tests that a user with permissions to assign/revoke membership to a particular role can do so'
@@ -355,10 +369,12 @@ def test_user_fail_to_remove_user_to_job_template(post, organization, check_jobt
assert joe in check_jobtemplate.execute_role
+
#
# /roles//teams/
#
+
@pytest.mark.django_db
def test_get_role_teams(get, team, admin, role):
role.parents.add(team.member_role)
@@ -377,6 +393,7 @@ def test_add_team_to_role(post, team, admin, role):
assert res.status_code == 204
assert role.parents.filter(id=team.member_role.id).count() == 1
+
@pytest.mark.django_db
def test_remove_team_from_role(post, team, admin, role):
role.members.add(admin)
@@ -391,6 +408,7 @@ def test_remove_team_from_role(post, team, admin, role):
# /roles//parents/
#
+
@pytest.mark.django_db
def test_role_parents(get, team, admin, role):
role.parents.add(team.member_role)
@@ -405,6 +423,7 @@ def test_role_parents(get, team, admin, role):
# /roles//children/
#
+
@pytest.mark.django_db
def test_role_children(get, team, admin, role):
role.parents.add(team.member_role)
@@ -420,6 +439,7 @@ def test_role_children(get, team, admin, role):
# Generics
#
+
@pytest.mark.django_db
def test_ensure_rbac_fields_are_present(organization, get, admin):
url = reverse('api:organization_detail', args=(organization.id,))
diff --git a/awx/main/tests/functional/test_rbac_core.py b/awx/main/tests/functional/test_rbac_core.py
index 75c89643ad..bae3b61dc0 100644
--- a/awx/main/tests/functional/test_rbac_core.py
+++ b/awx/main/tests/functional/test_rbac_core.py
@@ -53,7 +53,6 @@ def test_auto_inheritance_by_parents(organization, alice):
assert alice not in organization.admin_role
-
@pytest.mark.django_db
def test_accessible_objects(organization, alice, bob):
A = Role.objects.create()
@@ -68,6 +67,7 @@ def test_accessible_objects(organization, alice, bob):
assert Organization.accessible_objects(alice, 'admin_role').count() == 1
assert Organization.accessible_objects(bob, 'admin_role').count() == 0
+
@pytest.mark.django_db
def test_team_symantics(organization, team, alice):
assert alice not in organization.auditor_role
@@ -93,6 +93,7 @@ def test_auto_field_adjustments(organization, inventory, team, alice):
assert alice not in inventory.admin_role
#assert False
+
@pytest.mark.django_db
def test_implicit_deletes(alice):
'Ensures implicit resources and roles delete themselves'
@@ -127,6 +128,7 @@ def test_content_object(user):
org = Organization.objects.create(name='test-org')
assert org.admin_role.content_object.id == org.id
+
@pytest.mark.django_db
def test_hierarchy_rebuilding_multi_path():
'Tests a subdtle cases around role hierarchy rebuilding when you have multiple paths to the same role of different length'
diff --git a/awx/main/tests/functional/test_rbac_credential.py b/awx/main/tests/functional/test_rbac_credential.py
index ae68f036d8..6c87a53d27 100644
--- a/awx/main/tests/functional/test_rbac_credential.py
+++ b/awx/main/tests/functional/test_rbac_credential.py
@@ -8,6 +8,7 @@ from awx.main.migrations import _rbac as rbac
from django.apps import apps
from django.contrib.auth.models import User
+
@pytest.mark.django_db
def test_credential_migration_user(credential, user, permissions):
u = user('user', False)
@@ -18,6 +19,7 @@ def test_credential_migration_user(credential, user, permissions):
assert u in credential.admin_role
+
@pytest.mark.django_db
def test_two_teams_same_cred_name(organization_factory):
objects = organization_factory("test",
@@ -33,12 +35,14 @@ def test_two_teams_same_cred_name(organization_factory):
assert objects.teams.team1.member_role in cred1.use_role.parents.all()
assert objects.teams.team2.member_role in cred2.use_role.parents.all()
+
@pytest.mark.django_db
def test_credential_use_role(credential, user, permissions):
u = user('user', False)
credential.use_role.members.add(u)
assert u in credential.use_role
+
@pytest.mark.django_db
def test_credential_migration_team_member(credential, team, user, permissions):
u = user('user', False)
@@ -58,6 +62,7 @@ def test_credential_migration_team_member(credential, team, user, permissions):
assert u in credential.use_role
assert u not in credential.admin_role
+
@pytest.mark.django_db
def test_credential_migration_team_admin(credential, team, user, permissions):
u = user('user', False)
@@ -71,6 +76,7 @@ def test_credential_migration_team_admin(credential, team, user, permissions):
rbac.migrate_credential(apps, None)
assert u in credential.admin_role
+
@pytest.mark.django_db
def test_credential_migration_org_auditor(credential, team, org_auditor):
# Team's organization is the org_auditor's org
@@ -89,6 +95,7 @@ def test_credential_migration_org_auditor(credential, team, org_auditor):
assert org_auditor not in credential.use_role
assert org_auditor in credential.read_role
+
def test_credential_access_superuser():
u = User(username='admin', is_superuser=True)
access = CredentialAccess(u)
@@ -98,6 +105,7 @@ def test_credential_access_superuser():
assert access.can_change(credential, None)
assert access.can_delete(credential)
+
@pytest.mark.django_db
def test_credential_access_auditor(credential, organization_factory):
objects = organization_factory("org_cred_auditor",
@@ -109,6 +117,7 @@ def test_credential_access_auditor(credential, organization_factory):
access = CredentialAccess(objects.users.user1)
assert access.can_read(credential)
+
@pytest.mark.django_db
def test_credential_access_admin(user, team, credential):
u = user('org-admin', False)
@@ -135,6 +144,7 @@ def test_credential_access_admin(user, team, credential):
# should have can_change access as org-admin
assert access.can_change(credential, {'description': 'New description.'})
+
@pytest.mark.django_db
def test_org_credential_access_member(alice, org_credential, credential):
org_credential.admin_role.members.add(alice)
@@ -152,6 +162,7 @@ def test_org_credential_access_member(alice, org_credential, credential):
'description': 'New description.',
'organization': None})
+
@pytest.mark.django_db
def test_cred_job_template_xfail(user, deploy_jobtemplate):
' Personal credential migration '
@@ -167,6 +178,7 @@ def test_cred_job_template_xfail(user, deploy_jobtemplate):
rbac.migrate_credential(apps, None)
assert not access.can_change(cred, {'organization': org.pk})
+
@pytest.mark.django_db
def test_cred_job_template(user, team, deploy_jobtemplate):
' Team credential migration => org credential '
@@ -188,6 +200,7 @@ def test_cred_job_template(user, team, deploy_jobtemplate):
org.admin_role.members.remove(a)
assert not access.can_change(cred, {'organization': org.pk})
+
@pytest.mark.django_db
def test_cred_multi_job_template_single_org_xfail(user, deploy_jobtemplate):
a = user('admin', False)
@@ -204,6 +217,7 @@ def test_cred_multi_job_template_single_org_xfail(user, deploy_jobtemplate):
assert not access.can_change(cred, {'organization': org.pk})
+
@pytest.mark.django_db
def test_cred_multi_job_template_single_org(user, team, deploy_jobtemplate):
a = user('admin', False)
@@ -223,6 +237,7 @@ def test_cred_multi_job_template_single_org(user, team, deploy_jobtemplate):
org.admin_role.members.remove(a)
assert not access.can_change(cred, {'organization': org.pk})
+
@pytest.mark.django_db
def test_single_cred_multi_job_template_multi_org(user, organizations, credential, team):
orgs = organizations(2)
@@ -252,6 +267,7 @@ def test_single_cred_multi_job_template_multi_org(user, organizations, credentia
assert jts[0].credential != jts[1].credential
+
@pytest.mark.django_db
def test_cred_inventory_source(user, inventory, credential):
u = user('member', False)
@@ -268,6 +284,7 @@ def test_cred_inventory_source(user, inventory, credential):
rbac.migrate_credential(apps, None)
assert u not in credential.use_role
+
@pytest.mark.django_db
def test_cred_project(user, credential, project):
u = user('member', False)
@@ -280,12 +297,14 @@ def test_cred_project(user, credential, project):
rbac.migrate_credential(apps, None)
assert u not in credential.use_role
+
@pytest.mark.django_db
def test_cred_no_org(user, credential):
su = user('su', True)
access = CredentialAccess(su)
assert access.can_change(credential, {'user': su.pk})
+
@pytest.mark.django_db
def test_cred_team(user, team, credential):
u = user('a', False)
diff --git a/awx/main/tests/functional/test_rbac_inventory.py b/awx/main/tests/functional/test_rbac_inventory.py
index 4cfee93653..f2d3a087ab 100644
--- a/awx/main/tests/functional/test_rbac_inventory.py
+++ b/awx/main/tests/functional/test_rbac_inventory.py
@@ -30,6 +30,7 @@ def test_custom_inv_script_access(organization, user):
organization.admin_role.members.add(ou)
assert ou in custom_inv.admin_role
+
@pytest.mark.django_db
def test_modify_inv_script_foreign_org_admin(org_admin, organization, organization_factory, project):
custom_inv = CustomInventoryScript.objects.create(name='test', script='test', description='test',
@@ -39,6 +40,7 @@ def test_modify_inv_script_foreign_org_admin(org_admin, organization, organizati
access = CustomInventoryScriptAccess(org_admin)
assert not access.can_change(custom_inv, {'organization': other_org.pk, 'name': 'new-project'})
+
@pytest.mark.django_db
def test_org_member_inventory_script_permissions(org_member, organization):
custom_inv = CustomInventoryScript.objects.create(name='test', script='test', organization=organization)
@@ -47,6 +49,7 @@ def test_org_member_inventory_script_permissions(org_member, organization):
assert not access.can_delete(custom_inv)
assert not access.can_change(custom_inv, {'name': 'ed-test'})
+
@pytest.mark.django_db
def test_inventory_admin_user(inventory, permissions, user):
u = user('admin', False)
@@ -61,6 +64,7 @@ def test_inventory_admin_user(inventory, permissions, user):
assert inventory.use_role.members.filter(id=u.id).exists() is False
assert inventory.update_role.members.filter(id=u.id).exists() is False
+
@pytest.mark.django_db
def test_inventory_auditor_user(inventory, permissions, user):
u = user('auditor', False)
@@ -77,6 +81,7 @@ def test_inventory_auditor_user(inventory, permissions, user):
assert inventory.use_role.members.filter(id=u.id).exists() is False
assert inventory.update_role.members.filter(id=u.id).exists() is False
+
@pytest.mark.django_db
def test_inventory_updater_user(inventory, permissions, user):
u = user('updater', False)
@@ -92,6 +97,7 @@ def test_inventory_updater_user(inventory, permissions, user):
assert inventory.use_role.members.filter(id=u.id).exists() is False
assert inventory.update_role.members.filter(id=u.id).exists()
+
@pytest.mark.django_db
def test_inventory_executor_user(inventory, permissions, user):
u = user('executor', False)
@@ -109,7 +115,6 @@ def test_inventory_executor_user(inventory, permissions, user):
assert inventory.update_role.members.filter(id=u.id).exists() is False
-
@pytest.mark.django_db
def test_inventory_admin_team(inventory, permissions, user, team):
u = user('admin', False)
@@ -232,6 +237,7 @@ def test_access_auditor(organization, inventory, user):
assert not access.can_delete(inventory)
assert not access.can_run_ad_hoc_commands(inventory)
+
@pytest.mark.django_db
def test_inventory_update_org_admin(inventory_update, org_admin):
access = InventoryUpdateAccess(org_admin)
diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/test_rbac_job.py
index febade67eb..de1ccf6817 100644
--- a/awx/main/tests/functional/test_rbac_job.py
+++ b/awx/main/tests/functional/test_rbac_job.py
@@ -23,22 +23,26 @@ def normal_job(deploy_jobtemplate):
inventory=deploy_jobtemplate.inventory
)
+
@pytest.fixture
def jt_user(deploy_jobtemplate, rando):
deploy_jobtemplate.execute_role.members.add(rando)
return rando
+
@pytest.fixture
def inv_updater(inventory, rando):
inventory.update_role.members.add(rando)
return rando
+
@pytest.fixture
def host_adhoc(host, machine_credential, rando):
host.inventory.adhoc_role.members.add(rando)
machine_credential.use_role.members.add(rando)
return rando
+
@pytest.fixture
def proj_updater(project, rando):
project.update_role.members.add(rando)
@@ -52,6 +56,7 @@ def test_superuser_sees_orphans(normal_job, admin_user):
access = JobAccess(admin_user)
assert access.can_read(normal_job)
+
@pytest.mark.django_db
def test_org_member_does_not_see_orphans(normal_job, org_member, project):
normal_job.job_template = None
@@ -60,18 +65,21 @@ def test_org_member_does_not_see_orphans(normal_job, org_member, project):
access = JobAccess(org_member)
assert not access.can_read(normal_job)
+
@pytest.mark.django_db
def test_org_admin_sees_orphans(normal_job, org_admin):
normal_job.job_template = None
access = JobAccess(org_admin)
assert access.can_read(normal_job)
+
@pytest.mark.django_db
def test_org_auditor_sees_orphans(normal_job, org_auditor):
normal_job.job_template = None
access = JobAccess(org_auditor)
assert access.can_read(normal_job)
+
# Delete permissions testing
@pytest.mark.django_db
def test_JT_admin_delete_denied(normal_job, rando):
@@ -79,12 +87,14 @@ def test_JT_admin_delete_denied(normal_job, rando):
access = JobAccess(rando)
assert not access.can_delete(normal_job)
+
@pytest.mark.django_db
def test_inventory_admin_delete_denied(normal_job, rando):
normal_job.job_template.inventory.admin_role.members.add(rando)
access = JobAccess(rando)
assert not access.can_delete(normal_job)
+
@pytest.mark.django_db
def test_null_related_delete_denied(normal_job, rando):
normal_job.project = None
@@ -92,24 +102,28 @@ def test_null_related_delete_denied(normal_job, rando):
access = JobAccess(rando)
assert not access.can_delete(normal_job)
+
@pytest.mark.django_db
def test_delete_job_with_orphan_proj(normal_job, rando):
normal_job.project.organization = None
access = JobAccess(rando)
assert not access.can_delete(normal_job)
+
@pytest.mark.django_db
def test_inventory_org_admin_delete_allowed(normal_job, org_admin):
normal_job.project = None # do this so we test job->inventory->org->admin connection
access = JobAccess(org_admin)
assert access.can_delete(normal_job)
+
@pytest.mark.django_db
def test_project_org_admin_delete_allowed(normal_job, org_admin):
normal_job.inventory = None # do this so we test job->project->org->admin connection
access = JobAccess(org_admin)
assert access.can_delete(normal_job)
+
@pytest.mark.django_db
class TestJobAndUpdateCancels:
diff --git a/awx/main/tests/functional/test_rbac_job_start.py b/awx/main/tests/functional/test_rbac_job_start.py
index c934973cf4..4b6c092680 100644
--- a/awx/main/tests/functional/test_rbac_job_start.py
+++ b/awx/main/tests/functional/test_rbac_job_start.py
@@ -4,10 +4,10 @@ from awx.main.models.inventory import Inventory
from awx.main.models.credential import Credential
from awx.main.models.jobs import JobTemplate, Job
+
@pytest.mark.django_db
@pytest.mark.job_permissions
def test_admin_executing_permissions(deploy_jobtemplate, inventory, machine_credential, user):
-
admin_user = user('admin-user', True)
assert admin_user.can_access(Inventory, 'use', inventory)
@@ -15,33 +15,34 @@ def test_admin_executing_permissions(deploy_jobtemplate, inventory, machine_cred
assert admin_user.can_access(JobTemplate, 'start', deploy_jobtemplate)
assert admin_user.can_access(Credential, 'use', machine_credential)
+
@pytest.mark.django_db
@pytest.mark.job_permissions
def test_job_template_start_access(deploy_jobtemplate, user):
-
common_user = user('test-user', False)
deploy_jobtemplate.execute_role.members.add(common_user)
assert common_user.can_access(JobTemplate, 'start', deploy_jobtemplate)
+
@pytest.mark.django_db
@pytest.mark.job_permissions
def test_credential_use_access(machine_credential, user):
-
common_user = user('test-user', False)
machine_credential.use_role.members.add(common_user)
assert common_user.can_access(Credential, 'use', machine_credential)
+
@pytest.mark.django_db
@pytest.mark.job_permissions
def test_inventory_use_access(inventory, user):
-
common_user = user('test-user', False)
inventory.use_role.members.add(common_user)
assert common_user.can_access(Inventory, 'use', inventory)
+
@pytest.mark.django_db
class TestJobRelaunchAccess:
@pytest.fixture
diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/test_rbac_job_templates.py
index 71412959c9..ac545dafee 100644
--- a/awx/main/tests/functional/test_rbac_job_templates.py
+++ b/awx/main/tests/functional/test_rbac_job_templates.py
@@ -22,6 +22,7 @@ def jt_objects(job_template_factory):
credential='cred1', cloud_credential='aws1', network_credential='juniper1')
return objects
+
@pytest.mark.django_db
def test_job_template_migration_check(credential, deploy_jobtemplate, check_jobtemplate, user):
admin = user('admin', is_superuser=True)
@@ -53,6 +54,7 @@ def test_job_template_migration_check(credential, deploy_jobtemplate, check_jobt
assert admin in deploy_jobtemplate.execute_role
assert joe not in deploy_jobtemplate.execute_role
+
@pytest.mark.django_db
def test_job_template_migration_deploy(credential, deploy_jobtemplate, check_jobtemplate, user):
admin = user('admin', is_superuser=True)
@@ -168,6 +170,7 @@ def test_job_template_access_superuser(check_license, user, deploy_jobtemplate):
assert access.can_read(deploy_jobtemplate)
assert access.can_add({})
+
@pytest.mark.django_db
def test_job_template_access_read_level(jt_objects, rando):
@@ -184,6 +187,7 @@ def test_job_template_access_read_level(jt_objects, rando):
assert not access.can_add(dict(cloud_credential=jt_objects.cloud_credential.pk, project=proj_pk))
assert not access.can_add(dict(network_credential=jt_objects.network_credential.pk, project=proj_pk))
+
@pytest.mark.django_db
def test_job_template_access_use_level(jt_objects, rando):
@@ -200,6 +204,7 @@ def test_job_template_access_use_level(jt_objects, rando):
assert access.can_add(dict(cloud_credential=jt_objects.cloud_credential.pk, project=proj_pk))
assert access.can_add(dict(network_credential=jt_objects.network_credential.pk, project=proj_pk))
+
@pytest.mark.django_db
def test_job_template_access_org_admin(jt_objects, rando):
access = JobTemplateAccess(rando)
@@ -220,6 +225,7 @@ def test_job_template_access_org_admin(jt_objects, rando):
assert access.can_read(jt_objects.job_template)
assert access.can_delete(jt_objects.job_template)
+
@pytest.mark.django_db
@pytest.mark.job_permissions
def test_job_template_creator_access(project, rando, post):
@@ -243,6 +249,7 @@ def test_job_template_creator_access(project, rando, post):
# Creating a JT should place the creator in the admin role
assert rando in jt_obj.admin_role
+
@pytest.mark.django_db
def test_associate_label(label, user, job_template):
access = JobTemplateAccess(user('joe', False))
@@ -250,6 +257,7 @@ def test_associate_label(label, user, job_template):
label.organization.read_role.members.add(user('joe', False))
assert access.can_attach(job_template, label, 'labels', None)
+
@pytest.mark.django_db
def test_move_schedule_to_JT_no_access(job_template, rando):
schedule = Schedule.objects.create(
@@ -260,6 +268,7 @@ def test_move_schedule_to_JT_no_access(job_template, rando):
access = ScheduleAccess(rando)
assert not access.can_change(schedule, data=dict(unified_job_template=jt2.pk))
+
@pytest.mark.django_db
def test_move_schedule_from_JT_no_access(job_template, rando):
schedule = Schedule.objects.create(
diff --git a/awx/main/tests/functional/test_rbac_label.py b/awx/main/tests/functional/test_rbac_label.py
index d10e48ed0a..a34a4bf27f 100644
--- a/awx/main/tests/functional/test_rbac_label.py
+++ b/awx/main/tests/functional/test_rbac_label.py
@@ -4,6 +4,7 @@ from awx.main.access import (
LabelAccess,
)
+
@pytest.mark.django_db
def test_label_get_queryset_user(label, user):
u = user('user', False)
@@ -11,16 +12,19 @@ def test_label_get_queryset_user(label, user):
label.organization.member_role.members.add(u)
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_label_get_queryset_su(label, user):
access = LabelAccess(user('user', True))
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_label_access(label, user):
access = LabelAccess(user('user', False))
assert not access.can_read(label)
+
@pytest.mark.django_db
def test_label_access_superuser(label, user):
access = LabelAccess(user('admin', True))
@@ -29,6 +33,7 @@ def test_label_access_superuser(label, user):
assert access.can_change(label, None)
assert access.can_delete(label)
+
@pytest.mark.django_db
def test_label_access_admin(organization_factory):
'''can_change because I am an admin of that org'''
@@ -48,6 +53,7 @@ def test_label_access_admin(organization_factory):
assert access.can_change(label, {'organization': members.organization.id})
assert access.can_delete(label)
+
@pytest.mark.django_db
def test_label_access_user(label, user):
access = LabelAccess(user('user', False))
diff --git a/awx/main/tests/functional/test_rbac_notifications.py b/awx/main/tests/functional/test_rbac_notifications.py
index a9a5e7c5f9..05f19740fe 100644
--- a/awx/main/tests/functional/test_rbac_notifications.py
+++ b/awx/main/tests/functional/test_rbac_notifications.py
@@ -5,33 +5,39 @@ from awx.main.access import (
NotificationAccess
)
+
@pytest.mark.django_db
def test_notification_template_get_queryset_orgmember(notification_template, user):
access = NotificationTemplateAccess(user('user', False))
notification_template.organization.member_role.members.add(user('user', False))
assert access.get_queryset().count() == 0
+
@pytest.mark.django_db
def test_notification_template_get_queryset_nonorgmember(notification_template, user):
access = NotificationTemplateAccess(user('user', False))
assert access.get_queryset().count() == 0
+
@pytest.mark.django_db
def test_notification_template_get_queryset_su(notification_template, user):
access = NotificationTemplateAccess(user('user', True))
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_notification_template_get_queryset_orgadmin(notification_template, user):
access = NotificationTemplateAccess(user('admin', False))
notification_template.organization.admin_role.members.add(user('admin', False))
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_notification_template_get_queryset_org_auditor(notification_template, org_auditor):
access = NotificationTemplateAccess(org_auditor)
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_notification_template_access_superuser(notification_template_factory):
nf_objects = notification_template_factory('test-orphaned', organization='test', superusers=['admin'])
@@ -50,6 +56,7 @@ def test_notification_template_access_superuser(notification_template_factory):
assert access.can_change(nf, None)
assert access.can_delete(nf)
+
@pytest.mark.django_db
def test_notification_template_access_admin(organization_factory, notification_template_factory):
other_objects = organization_factory('other')
@@ -75,6 +82,7 @@ def test_notification_template_access_admin(organization_factory, notification_t
assert not access.can_change(nf, None)
assert not access.can_delete(nf)
+
@pytest.mark.django_db
def test_notification_template_access_org_user(notification_template, user):
u = user('normal', False)
@@ -84,34 +92,40 @@ def test_notification_template_access_org_user(notification_template, user):
assert not access.can_change(notification_template, None)
assert not access.can_delete(notification_template)
+
@pytest.mark.django_db
def test_notificaiton_template_orphan_access_org_admin(notification_template, organization, org_admin):
notification_template.organization = None
access = NotificationTemplateAccess(org_admin)
assert not access.can_change(notification_template, {'organization': organization.id})
+
@pytest.mark.django_db
def test_notification_access_get_queryset_org_admin(notification, org_admin):
access = NotificationAccess(org_admin)
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_notification_access_get_queryset_org_auditor(notification, org_auditor):
access = NotificationAccess(org_auditor)
assert access.get_queryset().count() == 1
+
@pytest.mark.django_db
def test_notification_access_system_admin(notification, admin):
access = NotificationAccess(admin)
assert access.can_read(notification)
assert access.can_delete(notification)
+
@pytest.mark.django_db
def test_notification_access_org_admin(notification, org_admin):
access = NotificationAccess(org_admin)
assert access.can_read(notification)
assert access.can_delete(notification)
+
@pytest.mark.django_db
def test_notification_access_org_auditor(notification, org_auditor):
access = NotificationAccess(org_auditor)
diff --git a/awx/main/tests/functional/test_rbac_organization.py b/awx/main/tests/functional/test_rbac_organization.py
index 77558c0e7c..1ecf6c7f85 100644
--- a/awx/main/tests/functional/test_rbac_organization.py
+++ b/awx/main/tests/functional/test_rbac_organization.py
@@ -22,6 +22,7 @@ def test_organization_migration_admin(organization, permissions, user):
assert u in organization.admin_role
+
@pytest.mark.django_db
def test_organization_migration_user(organization, permissions, user):
u = user('user', False)
diff --git a/awx/main/tests/functional/test_rbac_project.py b/awx/main/tests/functional/test_rbac_project.py
index 2b342df198..9f7cfbe705 100644
--- a/awx/main/tests/functional/test_rbac_project.py
+++ b/awx/main/tests/functional/test_rbac_project.py
@@ -92,6 +92,7 @@ def test_project_migration():
assert o2.projects.all()[0].jobtemplates.count() == 1
assert o3.projects.all()[0].jobtemplates.count() == 0
+
@pytest.mark.django_db
def test_single_org_project_migration(organization):
project = Project.objects.create(name='my project',
@@ -103,6 +104,7 @@ def test_single_org_project_migration(organization):
project = Project.objects.get(id=project.id)
assert project.organization.id == organization.id
+
@pytest.mark.django_db
def test_no_org_project_migration(organization):
project = Project.objects.create(name='my project',
@@ -112,6 +114,7 @@ def test_no_org_project_migration(organization):
rbac.migrate_projects(apps, None)
assert project.organization is None
+
@pytest.mark.django_db
def test_multi_org_project_migration():
org1 = Organization.objects.create(name="org1", description="org1 desc")
@@ -145,6 +148,7 @@ def test_project_user_project(user_project, project, user):
assert u in user_project.read_role
assert u not in project.read_role
+
@pytest.mark.django_db
def test_project_accessible_by_sa(user, project):
u = user('systemadmin', is_superuser=True)
@@ -159,6 +163,7 @@ def test_project_accessible_by_sa(user, project):
print(project.admin_role.ancestors.all())
assert u in project.admin_role
+
@pytest.mark.django_db
def test_project_org_members(user, organization, project):
admin = user('orgadmin')
@@ -176,6 +181,7 @@ def test_project_org_members(user, organization, project):
assert admin in project.admin_role
assert member in project.read_role
+
@pytest.mark.django_db
def test_project_team(user, team, project):
nonmember = user('nonmember')
@@ -194,6 +200,7 @@ def test_project_team(user, team, project):
assert member in project.read_role
assert nonmember not in project.read_role
+
@pytest.mark.django_db
def test_project_explicit_permission(user, team, project, organization):
u = user('prjuser')
@@ -211,6 +218,7 @@ def test_project_explicit_permission(user, team, project, organization):
assert u in project.read_role
+
@pytest.mark.django_db
def test_create_project_foreign_org_admin(org_admin, organization, organization_factory):
"""Org admins can only create projects in their own org."""
@@ -218,6 +226,7 @@ def test_create_project_foreign_org_admin(org_admin, organization, organization_
access = ProjectAccess(org_admin)
assert not access.can_add({'organization': other_org.pk, 'name': 'new-project'})
+
@pytest.mark.django_db
def test_modify_project_foreign_org_admin(org_admin, organization, organization_factory, project):
"""Org admins can only modify projects in their own org."""
diff --git a/awx/main/tests/functional/test_rbac_role.py b/awx/main/tests/functional/test_rbac_role.py
index 613051e395..16ad46f8db 100644
--- a/awx/main/tests/functional/test_rbac_role.py
+++ b/awx/main/tests/functional/test_rbac_role.py
@@ -18,6 +18,7 @@ def test_team_access_attach(rando, team, inventory):
data = {'id': inventory.admin_role.pk}
assert not access.can_attach(team, inventory.admin_role, 'member_role.children', data, False)
+
@pytest.mark.django_db
def test_user_access_attach(rando, inventory):
inventory.read_role.members.add(rando)
@@ -25,6 +26,7 @@ def test_user_access_attach(rando, inventory):
data = {'id': inventory.admin_role.pk}
assert not access.can_attach(rando, inventory.admin_role, 'roles', data, False)
+
@pytest.mark.django_db
def test_role_access_attach(rando, inventory):
inventory.read_role.members.add(rando)
diff --git a/awx/main/tests/functional/test_rbac_team.py b/awx/main/tests/functional/test_rbac_team.py
index 6907589462..5e7cf4ad85 100644
--- a/awx/main/tests/functional/test_rbac_team.py
+++ b/awx/main/tests/functional/test_rbac_team.py
@@ -22,6 +22,7 @@ def test_team_attach_unattach(team, user):
assert not access.can_attach(team, team.member_role, 'member_role.children', None)
assert not access.can_unattach(team, team.member_role, 'member_role.chidlren')
+
@pytest.mark.django_db
def test_team_access_superuser(team, user):
team.member_role.members.add(user('member', False))
@@ -36,6 +37,7 @@ def test_team_access_superuser(team, user):
assert len(t.member_role.members.all()) == 1
assert len(t.organization.admin_role.members.all()) == 0
+
@pytest.mark.django_db
def test_team_access_org_admin(organization, team, user):
a = user('admin', False)
@@ -52,6 +54,7 @@ def test_team_access_org_admin(organization, team, user):
assert len(t.member_role.members.all()) == 0
assert len(t.organization.admin_role.members.all()) == 1
+
@pytest.mark.django_db
def test_team_access_member(organization, team, user):
u = user('member', False)
@@ -68,6 +71,7 @@ def test_team_access_member(organization, team, user):
assert len(t.member_role.members.all()) == 1
assert len(t.organization.admin_role.members.all()) == 0
+
@pytest.mark.django_db
def test_team_accessible_by(team, user, project):
u = user('team_member', False)
@@ -79,6 +83,7 @@ def test_team_accessible_by(team, user, project):
team.member_role.members.add(u)
assert u in project.read_role
+
@pytest.mark.django_db
def test_team_accessible_objects(team, user, project):
u = user('team_member', False)
@@ -90,6 +95,7 @@ def test_team_accessible_objects(team, user, project):
team.member_role.members.add(u)
assert len(Project.accessible_objects(u, 'read_role')) == 1
+
@pytest.mark.django_db
def test_team_admin_member_access(team, user, project):
u = user('team_admin', False)
diff --git a/awx/main/tests/functional/test_rbac_user.py b/awx/main/tests/functional/test_rbac_user.py
index de2b9aa8b1..19efd0859b 100644
--- a/awx/main/tests/functional/test_rbac_user.py
+++ b/awx/main/tests/functional/test_rbac_user.py
@@ -7,6 +7,7 @@ from awx.main.migrations import _rbac as rbac
from awx.main.access import UserAccess
from awx.main.models import Role
+
@pytest.mark.django_db
def test_user_admin(user_project, project, user):
username = unicode("\xc3\xb4", "utf-8")
@@ -28,6 +29,7 @@ def test_user_admin(user_project, project, user):
assert sa.members.filter(id=joe.id).exists() is False
assert sa.members.filter(id=admin.id).exists() is True
+
@pytest.mark.django_db
def test_user_queryset(user):
u = user('pete', False)
@@ -36,6 +38,7 @@ def test_user_queryset(user):
qs = access.get_queryset()
assert qs.count() == 1
+
@pytest.mark.django_db
def test_user_accessible_objects(user, organization):
admin = user('admin', False)
@@ -49,6 +52,7 @@ def test_user_accessible_objects(user, organization):
organization.member_role.members.remove(u)
assert User.accessible_objects(admin, 'admin_role').count() == 1
+
@pytest.mark.django_db
def test_org_user_admin(user, organization):
admin = user('orgadmin')
@@ -63,6 +67,7 @@ def test_org_user_admin(user, organization):
organization.admin_role.members.remove(admin)
assert admin not in member.admin_role
+
@pytest.mark.django_db
def test_org_user_removed(user, organization):
admin = user('orgadmin')
@@ -76,6 +81,7 @@ def test_org_user_removed(user, organization):
organization.member_role.members.remove(member)
assert admin not in member.admin_role
+
@pytest.mark.django_db
def test_org_admin_create_sys_auditor(org_admin):
access = UserAccess(org_admin)
@@ -83,6 +89,7 @@ def test_org_admin_create_sys_auditor(org_admin):
username='new_user', password="pa$$sowrd", email="asdf@redhat.com",
is_system_auditor='true'))
+
@pytest.mark.django_db
def test_org_admin_edit_sys_auditor(org_admin, alice, organization):
organization.member_role.members.add(alice)
diff --git a/awx/main/tests/functional/test_rbac_workflow.py b/awx/main/tests/functional/test_rbac_workflow.py
index 663af2639a..ca22dfd1a0 100644
--- a/awx/main/tests/functional/test_rbac_workflow.py
+++ b/awx/main/tests/functional/test_rbac_workflow.py
@@ -7,21 +7,25 @@ from awx.main.access import (
# WorkflowJobNodeAccess
)
+
@pytest.fixture
def wfjt(workflow_job_template_factory, organization):
objects = workflow_job_template_factory('test_workflow', organization=organization, persisted=True)
return objects.workflow_job_template
+
@pytest.fixture
def wfjt_with_nodes(workflow_job_template_factory, organization, job_template):
objects = workflow_job_template_factory(
'test_workflow', organization=organization, workflow_job_template_nodes=[{'unified_job_template': job_template}], persisted=True)
return objects.workflow_job_template
+
@pytest.fixture
def wfjt_node(wfjt_with_nodes):
return wfjt_with_nodes.workflow_job_template_nodes.all()[0]
+
@pytest.fixture
def workflow_job(wfjt):
return wfjt.jobs.create(name='test_workflow')
@@ -50,6 +54,7 @@ class TestWorkflowJobTemplateAccess:
access = WorkflowJobTemplateAccess(org_admin)
assert not access.can_add({'reference_obj': wfjt_with_nodes})
+
@pytest.mark.django_db
class TestWorkflowJobTemplateNodeAccess:
@@ -57,6 +62,7 @@ class TestWorkflowJobTemplateNodeAccess:
access = WorkflowJobTemplateNodeAccess(org_admin)
assert not access.can_change(wfjt_node, {'job_type': 'scan'})
+
@pytest.mark.django_db
class TestWorkflowJobAccess:
diff --git a/awx/main/tests/job_base.py b/awx/main/tests/job_base.py
index 76460e5b68..d24d19611a 100644
--- a/awx/main/tests/job_base.py
+++ b/awx/main/tests/job_base.py
@@ -12,6 +12,7 @@ TEST_PLAYBOOK = '''- hosts: all
command: test 1 = 1
'''
+
class BaseJobTestMixin(BaseTestMixin):
diff --git a/awx/main/tests/manual/workflows/linear.py b/awx/main/tests/manual/workflows/linear.py
index 4a8801de88..7375d8e9ab 100755
--- a/awx/main/tests/manual/workflows/linear.py
+++ b/awx/main/tests/manual/workflows/linear.py
@@ -5,6 +5,7 @@ from awx.main.models import (
)
from awx.main.models.jobs import JobTemplate
+
def do_init_workflow(job_template_success, job_template_fail, job_template_never):
wfjt, created = WorkflowJobTemplate.objects.get_or_create(name="linear workflow")
wfjt.delete()
@@ -30,6 +31,7 @@ def do_init_workflow(job_template_success, job_template_fail, job_template_never
nodes_success[1].failure_nodes.add(nodes_never[2])
+
def do_init():
jt_success = JobTemplate.objects.get(id=5)
jt_fail= JobTemplate.objects.get(id=6)
diff --git a/awx/main/tests/manual/workflows/parallel.py b/awx/main/tests/manual/workflows/parallel.py
index 766ddc6d0c..ff4973f8b4 100755
--- a/awx/main/tests/manual/workflows/parallel.py
+++ b/awx/main/tests/manual/workflows/parallel.py
@@ -5,6 +5,7 @@ from awx.main.models import (
)
from awx.main.models.jobs import JobTemplate
+
def do_init_workflow(job_template_success, job_template_fail, job_template_never, jts_parallel):
wfjt, created = WorkflowJobTemplate.objects.get_or_create(name="parallel workflow")
wfjt.delete()
@@ -30,6 +31,7 @@ def do_init_workflow(job_template_success, job_template_fail, job_template_never
for i, n in enumerate(nodes_parallel):
n.failure_nodes.add(nodes_never[i])
+
def do_init():
jt_success = JobTemplate.objects.get(id=5)
jt_fail= JobTemplate.objects.get(id=6)
diff --git a/awx/main/tests/old/ad_hoc.py b/awx/main/tests/old/ad_hoc.py
index 2e05ae8017..8da0e33d24 100644
--- a/awx/main/tests/old/ad_hoc.py
+++ b/awx/main/tests/old/ad_hoc.py
@@ -29,6 +29,7 @@ from awx.main.tests.data.ssh import (
__all__ = ['RunAdHocCommandTest', 'AdHocCommandApiTest']
+
class BaseAdHocCommandTest(BaseJobExecutionTest):
'''
Common initialization for testing ad hoc commands.
@@ -377,6 +378,7 @@ class RunAdHocCommandTest(BaseAdHocCommandTest):
def run_pexpect_mock(self, *args, **kwargs):
return 'successful', 0
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class AdHocCommandApiTest(BaseAdHocCommandTest):
'''
diff --git a/awx/main/tests/old/commands/commands_monolithic.py b/awx/main/tests/old/commands/commands_monolithic.py
index 28e0d79db1..5a176e76bb 100644
--- a/awx/main/tests/old/commands/commands_monolithic.py
+++ b/awx/main/tests/old/commands/commands_monolithic.py
@@ -185,6 +185,7 @@ class BaseCommandMixin(object):
sys.stderr = original_stderr
return result, captured_stdout, captured_stderr
+
class CreateDefaultOrgTest(BaseCommandMixin, BaseTest):
'''
Test cases for create_default_org management command.
@@ -209,6 +210,7 @@ class CreateDefaultOrgTest(BaseCommandMixin, BaseTest):
self.assertFalse('Default organization added' in stdout)
self.assertEqual(Organization.objects.count(), 1)
+
class DumpDataTest(BaseCommandMixin, BaseTest):
'''
Test cases for dumpdata management command.
@@ -223,6 +225,7 @@ class DumpDataTest(BaseCommandMixin, BaseTest):
self.assertEqual(result, None)
json.loads(stdout)
+
@override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
ANSIBLE_TRANSPORT='local')
diff --git a/awx/main/tests/old/inventory.py b/awx/main/tests/old/inventory.py
index e7e751532c..a2baf49fe6 100644
--- a/awx/main/tests/old/inventory.py
+++ b/awx/main/tests/old/inventory.py
@@ -39,6 +39,7 @@ inventory['group-\u037c\u03b4\u0138\u0137\u03cd\u03a1\u0121\u0137\u0138\u01a1'].
print json.dumps(inventory)
"""
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class InventoryTest(BaseTest):
diff --git a/awx/main/tests/old/jobs/job_launch.py b/awx/main/tests/old/jobs/job_launch.py
index dabf3568bc..f97b1638e0 100644
--- a/awx/main/tests/old/jobs/job_launch.py
+++ b/awx/main/tests/old/jobs/job_launch.py
@@ -17,6 +17,7 @@ import yaml
__all__ = ['JobTemplateLaunchTest', 'JobTemplateLaunchPasswordsTest']
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
def setUp(self):
@@ -186,6 +187,7 @@ class JobTemplateLaunchTest(BaseJobTestMixin, django.test.TransactionTestCase):
with self.current_user(self.user_sue):
self.post(self.launch_url, {}, expect=400)
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobTemplateLaunchPasswordsTest(BaseJobTestMixin, django.test.TransactionTestCase):
def setUp(self):
diff --git a/awx/main/tests/old/jobs/job_relaunch.py b/awx/main/tests/old/jobs/job_relaunch.py
index 3a9e050288..5cb8f30cac 100644
--- a/awx/main/tests/old/jobs/job_relaunch.py
+++ b/awx/main/tests/old/jobs/job_relaunch.py
@@ -17,6 +17,7 @@ from awx.main.tests.job_base import BaseJobTestMixin
__all__ = ['JobRelaunchTest',]
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobRelaunchTest(BaseJobTestMixin, BaseLiveServerTest):
diff --git a/awx/main/tests/old/jobs/jobs_monolithic.py b/awx/main/tests/old/jobs/jobs_monolithic.py
index b7321bb611..eb4bbf85d7 100644
--- a/awx/main/tests/old/jobs/jobs_monolithic.py
+++ b/awx/main/tests/old/jobs/jobs_monolithic.py
@@ -186,6 +186,7 @@ TEST_SURVEY_REQUIREMENTS = '''
}
'''
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobTemplateTest(BaseJobTestMixin, django.test.TransactionTestCase):
@@ -505,6 +506,7 @@ class JobTemplateTest(BaseJobTestMixin, django.test.TransactionTestCase):
with self.current_user(self.user_doug):
self.get(detail_url, expect=403)
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobTest(BaseJobTestMixin, django.test.TransactionTestCase):
@@ -658,6 +660,7 @@ class JobTest(BaseJobTestMixin, django.test.TransactionTestCase):
# and that jobs come back nicely serialized with related resources and so on ...
# that we can drill all the way down and can get at host failure lists, etc ...
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
@override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
@@ -1099,6 +1102,7 @@ class JobTransactionTest(BaseJobTestMixin, django.test.LiveServerTestCase):
self.assertEqual(job.status, 'successful', job.result_stdout)
self.assertFalse(errors)
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobTemplateSurveyTest(BaseJobTestMixin, django.test.TransactionTestCase):
def setUp(self):
diff --git a/awx/main/tests/old/jobs/start_cancel.py b/awx/main/tests/old/jobs/start_cancel.py
index 3a6957af69..1067a618f3 100644
--- a/awx/main/tests/old/jobs/start_cancel.py
+++ b/awx/main/tests/old/jobs/start_cancel.py
@@ -17,6 +17,7 @@ from awx.main.tests.job_base import BaseJobTestMixin
__all__ = ['JobStartCancelTest',]
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class JobStartCancelTest(BaseJobTestMixin, BaseLiveServerTest):
diff --git a/awx/main/tests/old/jobs/survey_password.py b/awx/main/tests/old/jobs/survey_password.py
index f7588301d8..64b3bfee06 100644
--- a/awx/main/tests/old/jobs/survey_password.py
+++ b/awx/main/tests/old/jobs/survey_password.py
@@ -141,6 +141,7 @@ TESTS = {
}
}
+
class SurveyPasswordBaseTest(BaseTest, QueueStartStopTestMixin):
def setUp(self):
super(SurveyPasswordBaseTest, self).setUp()
@@ -176,6 +177,7 @@ class SurveyPasswordBaseTest(BaseTest, QueueStartStopTestMixin):
url = reverse('api:job_detail', args=(job.pk,))
return self.get(url, expect=200, auth=self.get_super_credentials(), accept='application/json')
+
class SurveyPasswordRedactedTest(SurveyPasswordBaseTest):
'''
Transpose TEST[]['tests'] to the below format. A more flat format."
diff --git a/awx/main/tests/old/projects.py b/awx/main/tests/old/projects.py
index 01c459b794..3b894e18cc 100644
--- a/awx/main/tests/old/projects.py
+++ b/awx/main/tests/old/projects.py
@@ -37,6 +37,7 @@ TEST_PLAYBOOK = '''- hosts: mygroup
command: test 1 = 1
'''
+
class ProjectsTest(BaseTransactionTest):
# tests for users, projects, and teams
diff --git a/awx/main/tests/old/schedules.py b/awx/main/tests/old/schedules.py
index 916df4e89d..8e114d7950 100644
--- a/awx/main/tests/old/schedules.py
+++ b/awx/main/tests/old/schedules.py
@@ -46,6 +46,8 @@ BAD_SCHEDULES = ["", "DTSTART:20140331T055000 RRULE:FREQ=MINUTELY;INTERVAL=10;CO
"DTSTART:20140331T055000Z RRULE:FREQ=YEARLY;BYWEEKNO=10;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=HOURLY;INTERVAL=1 DTSTART:20140331T055000Z RRULE:FREQ=HOURLY;INTERVAL=1",
"DTSTART:20140331T055000Z RRULE:FREQ=HOURLY;INTERVAL=1 RRULE:FREQ=HOURLY;INTERVAL=1"]
+
+
class ScheduleTest(BaseTest):
def setUp(self):
diff --git a/awx/main/tests/old/scripts.py b/awx/main/tests/old/scripts.py
index 09df77ae45..7af019fd7c 100644
--- a/awx/main/tests/old/scripts.py
+++ b/awx/main/tests/old/scripts.py
@@ -14,6 +14,7 @@ from awx.main.tests.base import BaseLiveServerTest
__all__ = ['InventoryScriptTest']
+
class BaseScriptTest(BaseLiveServerTest):
'''
Base class for tests that run external scripts to access the API.
@@ -55,6 +56,7 @@ class BaseScriptTest(BaseLiveServerTest):
stdout, stderr = proc.communicate()
return proc.returncode, stdout, stderr
+
class InventoryScriptTest(BaseScriptTest):
'''
Test helper to run management command as standalone script.
diff --git a/awx/main/tests/old/settings.py b/awx/main/tests/old/settings.py
index 3c507ac992..a1614dac38 100644
--- a/awx/main/tests/old/settings.py
+++ b/awx/main/tests/old/settings.py
@@ -47,6 +47,7 @@ TEST_TOWER_SETTINGS_MANIFEST = {
}
}
+
@override_settings(TOWER_SETTINGS_MANIFEST=TEST_TOWER_SETTINGS_MANIFEST)
@pytest.mark.skip(reason="Settings deferred to 3.1")
class SettingsPlaceholder(BaseTest):
diff --git a/awx/main/tests/old/tasks.py b/awx/main/tests/old/tasks.py
index a80ea07b87..eba9bf552b 100644
--- a/awx/main/tests/old/tasks.py
+++ b/awx/main/tests/old/tasks.py
@@ -237,6 +237,7 @@ TEST_VAULT_PLAYBOOK = '''$ANSIBLE_VAULT;1.1;AES256
TEST_VAULT_PASSWORD = '1234'
+
@unittest.skipIf(os.environ.get('SKIP_SLOW_TESTS', False), 'Skipping slow test')
class RunJobTest(BaseJobExecutionTest):
'''
diff --git a/awx/main/tests/old/users.py b/awx/main/tests/old/users.py
index 73ceafd840..26ec3c3676 100644
--- a/awx/main/tests/old/users.py
+++ b/awx/main/tests/old/users.py
@@ -39,6 +39,7 @@ class AuthTokenTimeoutTest(BaseTest):
self.assertIn('Auth-Token-Timeout', response)
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION))
+
class AuthTokenLimitTest(BaseTest):
def setUp(self):
super(AuthTokenLimitTest, self).setUp()
@@ -163,6 +164,7 @@ class AuthTokenProxyTest(BaseTest):
response = self._get_me(expect=200, auth=auth_token, remote_addr=remote_addr, client_kwargs=client_kwargs)
self.check_me_is_admin(response)
+
class UsersTest(BaseTest):
def collection(self):
diff --git a/awx/main/tests/unit/api/serializers/conftest.py b/awx/main/tests/unit/api/serializers/conftest.py
index 3b056a149f..af35a4a002 100644
--- a/awx/main/tests/unit/api/serializers/conftest.py
+++ b/awx/main/tests/unit/api/serializers/conftest.py
@@ -1,6 +1,7 @@
import pytest
+
@pytest.fixture
def get_related_assert():
def fn(model_obj, related, resource_name, related_resource_name):
@@ -8,6 +9,7 @@ def get_related_assert():
assert related[related_resource_name] == '/api/v1/%s/%d/%s/' % (resource_name, model_obj.pk, related_resource_name)
return fn
+
@pytest.fixture
def get_related_mock_and_run():
def fn(serializer_class, model_obj):
@@ -16,6 +18,7 @@ def get_related_mock_and_run():
return related
return fn
+
@pytest.fixture
def test_get_related(get_related_assert, get_related_mock_and_run):
def fn(serializer_class, model_obj, resource_name, related_resource_name):
@@ -24,12 +27,14 @@ def test_get_related(get_related_assert, get_related_mock_and_run):
return related
return fn
+
@pytest.fixture
def get_summary_fields_assert():
def fn(summary, summary_field_name):
assert summary_field_name in summary
return fn
+
@pytest.fixture
def get_summary_fields_mock_and_run():
def fn(serializer_class, model_obj):
@@ -37,10 +42,11 @@ def get_summary_fields_mock_and_run():
return serializer.get_summary_fields(model_obj)
return fn
+
@pytest.fixture
def test_get_summary_fields(get_summary_fields_mock_and_run, get_summary_fields_assert):
def fn(serializer_class, model_obj, summary_field_name):
summary = get_summary_fields_mock_and_run(serializer_class, model_obj)
get_summary_fields_assert(summary, summary_field_name)
- return summary
+ return summary
return fn
diff --git a/awx/main/tests/unit/api/serializers/test_inventory_serializers.py b/awx/main/tests/unit/api/serializers/test_inventory_serializers.py
index 35f36a1100..e79ea7c10b 100644
--- a/awx/main/tests/unit/api/serializers/test_inventory_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_inventory_serializers.py
@@ -19,8 +19,8 @@ from rest_framework.test import (
force_authenticate,
)
-class TestCustomInventoryScriptSerializer(object):
+class TestCustomInventoryScriptSerializer(object):
@pytest.mark.parametrize("superuser,sysaudit,admin_role,value",
((True, False, False, '#!/python'),
(False, True, False, '#!/python'),
diff --git a/awx/main/tests/unit/api/serializers/test_job_serializers.py b/awx/main/tests/unit/api/serializers/test_job_serializers.py
index a9eaecd2e9..4997e3674b 100644
--- a/awx/main/tests/unit/api/serializers/test_job_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_job_serializers.py
@@ -13,6 +13,7 @@ from awx.main.models import (
Job,
)
+
def mock_JT_resource_data():
return ({}, [])
@@ -22,18 +23,22 @@ def job_template(mocker):
mock_jt.resource_validation_data = mock_JT_resource_data
return mock_jt
+
@pytest.fixture
def job(mocker, job_template):
return mocker.MagicMock(pk=5, job_template=job_template)
+
@pytest.fixture
def labels(mocker):
return [Label(id=x, name='label-%d' % x) for x in xrange(0, 25)]
+
@pytest.fixture
def jobs(mocker):
return [Job(id=x, name='job-%d' % x) for x in xrange(0, 25)]
+
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
class TestJobSerializerGetRelated():
@@ -58,10 +63,10 @@ class TestJobSerializerGetRelated():
assert 'job_template' in related
assert related['job_template'] == '/api/v1/%s/%d/' % ('job_templates', job.job_template.pk)
+
@mock.patch('awx.api.serializers.BaseSerializer.to_representation', lambda self,obj: {
'extra_vars': obj.extra_vars})
class TestJobSerializerSubstitution():
-
def test_survey_password_hide(self, mocker):
job = mocker.MagicMock(**{
'display_extra_vars.return_value': '{\"secret_key\": \"$encrypted$\"}',
@@ -73,6 +78,7 @@ class TestJobSerializerSubstitution():
job.display_extra_vars.assert_called_once_with()
assert 'my_password' not in extra_vars
+
@mock.patch('awx.api.serializers.BaseSerializer.get_summary_fields', lambda x,y: {})
class TestJobOptionsSerializerGetSummaryFields():
def test__summary_field_labels_10_max(self, mocker, job_template, labels):
@@ -88,4 +94,3 @@ class TestJobOptionsSerializerGetSummaryFields():
def test_labels_exists(self, test_get_summary_fields, job_template):
test_get_summary_fields(JobOptionsSerializer, job_template, 'labels')
-
diff --git a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
index ba47b09e7f..77c7e69dc4 100644
--- a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py
@@ -17,23 +17,28 @@ from rest_framework.test import APIRequestFactory
#DRF
from rest_framework import serializers
+
def mock_JT_resource_data():
return ({}, [])
+
@pytest.fixture
def job_template(mocker):
mock_jt = mocker.MagicMock(pk=5)
mock_jt.resource_validation_data = mock_JT_resource_data
return mock_jt
+
@pytest.fixture
def job(mocker, job_template):
return mocker.MagicMock(pk=5, job_template=job_template)
+
@pytest.fixture
def jobs(mocker):
return [Job(id=x, name='job-%d' % x) for x in xrange(0, 25)]
+
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
@mock.patch('awx.api.serializers.JobOptionsSerializer.get_related', lambda x,y: {})
class TestJobTemplateSerializerGetRelated():
@@ -57,6 +62,7 @@ class TestJobTemplateSerializerGetRelated():
related = get_related_mock_and_run(JobTemplateSerializer, job_template)
assert 'callback' not in related
+
class TestJobTemplateSerializerGetSummaryFields():
def test__recent_jobs(self, mocker, job_template, jobs):
@@ -109,8 +115,8 @@ class TestJobTemplateSerializerGetSummaryFields():
assert response['user_capabilities']['copy'] == 'foo'
assert response['user_capabilities']['edit'] == 'foobar'
-class TestJobTemplateSerializerValidation(object):
+class TestJobTemplateSerializerValidation(object):
good_extra_vars = ["{\"test\": \"keys\"}", "---\ntest: key"]
bad_extra_vars = ["{\"test\": \"keys\"", "---\ntest: [2"]
@@ -121,4 +127,3 @@ class TestJobTemplateSerializerValidation(object):
for ev in self.bad_extra_vars:
with pytest.raises(serializers.ValidationError):
serializer.validate_extra_vars(ev)
-
diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
index 3d86952f8d..4f11ce64be 100644
--- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
@@ -16,6 +16,7 @@ from awx.main.models import (
WorkflowJobNode,
)
+
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x,y: {})
class TestWorkflowJobTemplateSerializerGetRelated():
@pytest.fixture
@@ -35,6 +36,7 @@ class TestWorkflowJobTemplateSerializerGetRelated():
'workflow_job_templates',
related_resource_name)
+
@mock.patch('awx.api.serializers.BaseSerializer.get_related', lambda x,y: {})
class TestWorkflowNodeBaseSerializerGetRelated():
@pytest.fixture
@@ -42,7 +44,7 @@ class TestWorkflowNodeBaseSerializerGetRelated():
jt = job_template_factory(name="blah", persisted=False).job_template
jt.pk = 1
return jt
-
+
@pytest.fixture
def workflow_job_template_node_related(self, job_template):
return WorkflowJobTemplateNode(pk=1, unified_job_template=job_template)
@@ -60,6 +62,7 @@ class TestWorkflowNodeBaseSerializerGetRelated():
related = WorkflowJobTemplateNodeSerializer().get_related(workflow_job_template_node)
assert 'unified_job_template' not in related
+
@mock.patch('awx.api.serializers.WorkflowNodeBaseSerializer.get_related', lambda x,y: {})
class TestWorkflowJobTemplateNodeSerializerGetRelated():
@pytest.fixture
@@ -111,11 +114,12 @@ class FakeView:
def get_object(self):
return self.obj
+
class FakeRequest:
pass
-class TestWorkflowJobTemplateNodeSerializerCharPrompts():
+class TestWorkflowJobTemplateNodeSerializerCharPrompts():
@pytest.fixture
def WFJT_serializer(self):
serializer = WorkflowJobTemplateNodeSerializer()
diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py
index 55ef257567..65356958bb 100644
--- a/awx/main/tests/unit/api/test_filters.py
+++ b/awx/main/tests/unit/api/test_filters.py
@@ -4,6 +4,7 @@ from rest_framework.exceptions import PermissionDenied
from awx.api.filters import FieldLookupBackend
from awx.main.models import Credential, JobTemplate
+
@pytest.mark.parametrize(u"empty_value", [u'', ''])
def test_empty_in(empty_value):
field_lookup = FieldLookupBackend()
@@ -11,12 +12,14 @@ def test_empty_in(empty_value):
field_lookup.value_to_python(JobTemplate, 'project__in', empty_value)
assert 'empty value for __in' in str(excinfo.value)
+
@pytest.mark.parametrize(u"valid_value", [u'foo', u'foo,'])
def test_valid_in(valid_value):
field_lookup = FieldLookupBackend()
value, new_lookup = field_lookup.value_to_python(JobTemplate, 'project__in', valid_value)
assert 'foo' in value
+
@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in'])
@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
def test_filter_on_password_field(password_field, lookup_suffix):
@@ -26,6 +29,7 @@ def test_filter_on_password_field(password_field, lookup_suffix):
field, new_lookup = field_lookup.get_field_from_lookup(Credential, lookup)
assert 'not allowed' in str(excinfo.value)
+
@pytest.mark.parametrize('lookup_suffix', ['', 'contains', 'startswith', 'in'])
@pytest.mark.parametrize('password_field', Credential.PASSWORD_FIELDS)
def test_filter_on_related_password_field(password_field, lookup_suffix):
diff --git a/awx/main/tests/unit/api/test_generics.py b/awx/main/tests/unit/api/test_generics.py
index 289b4547a8..b10b1c6c54 100644
--- a/awx/main/tests/unit/api/test_generics.py
+++ b/awx/main/tests/unit/api/test_generics.py
@@ -10,21 +10,25 @@ from rest_framework.response import Response
# AWX
from awx.api.generics import ParentMixin, SubListCreateAttachDetachAPIView, DeleteLastUnattachLabelMixin
+
@pytest.fixture
def get_object_or_404(mocker):
# pytest patch without return_value generates a random value, we are counting on this
return mocker.patch('awx.api.generics.get_object_or_404')
+
@pytest.fixture
def get_object_or_400(mocker):
return mocker.patch('awx.api.generics.get_object_or_400')
+
@pytest.fixture
def mock_response_new(mocker):
m = mocker.patch('awx.api.generics.Response.__new__')
m.return_value = m
return m
+
@pytest.fixture
def parent_relationship_factory(mocker):
def rf(serializer_class, relationship_name, relationship_value=mocker.Mock()):
@@ -38,6 +42,7 @@ def parent_relationship_factory(mocker):
return (serializer, mock_parent_relationship)
return rf
+
# TODO: Test create and associate failure (i.e. id doesn't exist, record already exists, permission denied)
# TODO: Mock and check return (Response)
class TestSubListCreateAttachDetachAPIView:
@@ -122,6 +127,7 @@ class TestSubListCreateAttachDetachAPIView:
view.unattach_validate.assert_called_with(mock_request)
view.unattach_by_id.assert_not_called()
+
class TestDeleteLastUnattachLabelMixin:
@mock.patch('__builtin__.super')
def test_unattach_ok(self, super, mocker):
@@ -159,6 +165,7 @@ class TestDeleteLastUnattachLabelMixin:
super.unattach_validate.assert_called_with(mock_request)
assert mock_response == res
+
class TestParentMixin:
def test_get_parent_object(self, mocker, get_object_or_404):
parent_mixin = ParentMixin()
@@ -168,7 +175,6 @@ class TestParentMixin:
mock_parent_mixin = mocker.MagicMock(wraps=parent_mixin)
return_value = mock_parent_mixin.get_parent_object()
-
+
get_object_or_404.assert_called_with(parent_mixin.parent_model, **parent_mixin.kwargs)
assert get_object_or_404.return_value == return_value
-
diff --git a/awx/main/tests/unit/api/test_roles.py b/awx/main/tests/unit/api/test_roles.py
index 2ee8ec2102..5cb49a92b0 100644
--- a/awx/main/tests/unit/api/test_roles.py
+++ b/awx/main/tests/unit/api/test_roles.py
@@ -19,6 +19,7 @@ from awx.main.models import (
Role,
)
+
@pytest.mark.parametrize("pk, err", [
(111, "not change the membership"),
(1, "may not perform"),
@@ -48,6 +49,7 @@ def test_user_roles_list_user_admin_role(pk, err):
assert response.status_code == 403
assert err in response.content
+
@pytest.mark.parametrize("admin_role, err", [
(True, "may not perform"),
(False, "not change the membership"),
@@ -80,6 +82,7 @@ def test_role_users_list_other_user_admin_role(admin_role, err):
assert response.status_code == 403
assert err in response.content
+
def test_team_roles_list_post_org_roles():
with mock.patch('awx.api.views.get_object_or_400') as role_get, \
mock.patch('awx.api.views.ContentType.objects.get_for_model') as ct_get:
diff --git a/awx/main/tests/unit/api/test_views.py b/awx/main/tests/unit/api/test_views.py
index 798c30bf6f..37b9177db7 100644
--- a/awx/main/tests/unit/api/test_views.py
+++ b/awx/main/tests/unit/api/test_views.py
@@ -6,12 +6,14 @@ from awx.api.views import (
JobTemplateLabelList,
)
+
@pytest.fixture
def mock_response_new(mocker):
m = mocker.patch('awx.api.views.Response.__new__')
m.return_value = m
return m
+
class TestApiV1RootView:
def test_get_endpoints(self, mocker, mock_response_new):
endpoints = [
@@ -54,6 +56,7 @@ class TestApiV1RootView:
for endpoint in endpoints:
assert endpoint in data_arg
+
class TestJobTemplateLabelList:
def test_inherited_mixin_unattach(self):
with mock.patch('awx.api.generics.DeleteLastUnattachLabelMixin.unattach') as mixin_unattach:
diff --git a/awx/main/tests/unit/models/test_job_template_unit.py b/awx/main/tests/unit/models/test_job_template_unit.py
index 088b4d3d7c..28f2dc2993 100644
--- a/awx/main/tests/unit/models/test_job_template_unit.py
+++ b/awx/main/tests/unit/models/test_job_template_unit.py
@@ -14,6 +14,7 @@ def test_missing_project_error(job_template_factory):
validation_errors, resources_needed_to_start = obj.resource_validation_data()
assert 'project' in validation_errors
+
def test_inventory_credential_need_to_start(job_template_factory):
objects = job_template_factory(
'job-template-few-resources',
@@ -23,6 +24,7 @@ def test_inventory_credential_need_to_start(job_template_factory):
assert 'inventory' in obj.resources_needed_to_start
assert 'credential' in obj.resources_needed_to_start
+
def test_inventory_credential_contradictions(job_template_factory):
objects = job_template_factory(
'job-template-paradox',
@@ -35,6 +37,7 @@ def test_inventory_credential_contradictions(job_template_factory):
assert 'inventory' in validation_errors
assert 'credential' in validation_errors
+
def test_survey_answers_as_string(job_template_factory):
objects = job_template_factory(
'job-template-with-survey',
@@ -45,12 +48,14 @@ def test_survey_answers_as_string(job_template_factory):
accepted, ignored = jt._accept_or_ignore_job_kwargs(extra_vars=user_extra_vars)
assert 'var1' in accepted['extra_vars']
+
@pytest.mark.survey
def test_job_template_survey_password_redaction(job_template_with_survey_passwords_unit):
"""Tests the JobTemplate model's funciton to redact passwords from
extra_vars - used when creating a new job"""
assert job_template_with_survey_passwords_unit.survey_password_variables() == ['secret_key', 'SSN']
+
def test_job_template_survey_variable_validation(job_template_factory):
objects = job_template_factory(
'survey_variable_validation',
diff --git a/awx/main/tests/unit/models/test_label.py b/awx/main/tests/unit/models/test_label.py
index 9aaa6a1171..5c35803403 100644
--- a/awx/main/tests/unit/models/test_label.py
+++ b/awx/main/tests/unit/models/test_label.py
@@ -9,9 +9,9 @@ mock_query_set = mock.MagicMock()
mock_objects = mock.MagicMock(filter=mock.MagicMock(return_value=mock_query_set))
+
@mock.patch('awx.main.models.label.Label.objects', mock_objects)
class TestLabelFilterMocked:
-
def test_get_orphaned_labels(self, mocker):
ret = Label.get_orphaned_labels()
@@ -63,4 +63,3 @@ class TestLabelFilterMocked:
mock_jt_qs.count.assert_called_with()
assert ret is expected
-
diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py
index 4f9653755e..d58f7bd1c7 100644
--- a/awx/main/tests/unit/models/test_survey_models.py
+++ b/awx/main/tests/unit/models/test_survey_models.py
@@ -19,6 +19,7 @@ def job(mocker):
ret.project = mocker.MagicMock(scm_revision='asdf1234')
return ret
+
@pytest.mark.survey
def test_job_survey_password_redaction():
"""Tests the Job model's funciton to redact passwords from
@@ -37,6 +38,7 @@ def test_job_survey_password_redaction():
'secret_key': '$encrypted$',
'SSN': '$encrypted$'}
+
@pytest.mark.survey
def test_survey_passwords_not_in_extra_vars():
"""Tests that survey passwords not included in extra_vars are
@@ -52,6 +54,7 @@ def test_survey_passwords_not_in_extra_vars():
'submitter_email': 'foobar@redhat.com',
}
+
def test_job_safe_args_redacted_passwords(job):
"""Verify that safe_args hides passwords in the job extra_vars"""
kwargs = {'ansible_version': '2.1'}
@@ -61,6 +64,7 @@ def test_job_safe_args_redacted_passwords(job):
extra_vars = json.loads(safe_args[ev_index])
assert extra_vars['secret_key'] == '$encrypted$'
+
def test_job_args_unredacted_passwords(job):
kwargs = {'ansible_version': '2.1'}
run_job = RunJob()
@@ -69,6 +73,7 @@ def test_job_args_unredacted_passwords(job):
extra_vars = json.loads(args[ev_index])
assert extra_vars['secret_key'] == 'my_password'
+
class TestWorkflowSurveys:
def test_update_kwargs_survey_defaults(self, survey_spec_factory):
"Assure that the survey default over-rides a JT variable"
diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py
index 709668ddcf..e48b90385d 100644
--- a/awx/main/tests/unit/models/test_workflow_unit.py
+++ b/awx/main/tests/unit/models/test_workflow_unit.py
@@ -8,6 +8,7 @@ from awx.main.models.workflow import (
)
import mock
+
class TestWorkflowJobInheritNodesMixin():
class TestCreateWorkflowJobNodes():
@pytest.fixture
@@ -23,7 +24,7 @@ class TestWorkflowJobInheritNodesMixin():
mixin = WorkflowJobInheritNodesMixin()
mixin._create_workflow_job_nodes(job_template_nodes)
-
+
for job_template_node in job_template_nodes:
workflow_job_node_create.assert_any_call(workflow_job=mixin)
@@ -38,7 +39,7 @@ class TestWorkflowJobInheritNodesMixin():
def test__map_workflow_job_nodes(self, job_template_nodes, job_nodes):
mixin = WorkflowJobInheritNodesMixin()
-
+
node_ids_map = mixin._map_workflow_job_nodes(job_template_nodes, job_nodes)
assert len(node_ids_map) == len(job_template_nodes)
@@ -59,7 +60,7 @@ class TestWorkflowJobInheritNodesMixin():
def job_nodes(self, mocker):
nodes = [mocker.MagicMock(id=i) for i in range(100, 110)]
return nodes
-
+
@pytest.fixture
def job_nodes_dict(self, job_nodes):
_map = {}
@@ -70,7 +71,7 @@ class TestWorkflowJobInheritNodesMixin():
def test__inherit_relationship(self, mocker, job_template_nodes, job_nodes, job_nodes_dict):
mixin = WorkflowJobInheritNodesMixin()
-
+
mixin._get_workflow_job_node_by_id = lambda x: job_nodes_dict[x]
mixin._get_all_by_type = lambda x,node_type: x.success_nodes
@@ -87,6 +88,7 @@ class TestWorkflowJobInheritNodesMixin():
def workflow_job_unit():
return WorkflowJob(name='workflow', status='new')
+
@pytest.fixture
def workflow_job_template_unit():
return WorkflowJobTemplate(name='workflow')
@@ -101,6 +103,7 @@ def jt_ask(job_template_factory):
jt.ask_tags_on_launch = True
return jt
+
@pytest.fixture
def project_unit():
return Project(name='example-proj')
@@ -108,6 +111,7 @@ def project_unit():
example_prompts = dict(job_type='check', job_tags='quack', limit='duck', skip_tags='oink')
+
@pytest.fixture
def job_node_no_prompts(workflow_job_unit, jt_ask):
return WorkflowJobNode(workflow_job=workflow_job_unit, unified_job_template=jt_ask)
@@ -119,10 +123,12 @@ def job_node_with_prompts(job_node_no_prompts):
job_node_no_prompts.credential = Credential(name='example-inv', kind='ssh', username='asdf', password='asdf')
return job_node_no_prompts
+
@pytest.fixture
def wfjt_node_no_prompts(workflow_job_template_unit, jt_ask):
return WorkflowJobTemplateNode(workflow_job_template=workflow_job_template_unit, unified_job_template=jt_ask)
+
@pytest.fixture
def wfjt_node_with_prompts(wfjt_node_no_prompts):
wfjt_node_no_prompts.char_prompts = example_prompts
@@ -130,8 +136,8 @@ def wfjt_node_with_prompts(wfjt_node_no_prompts):
wfjt_node_no_prompts.credential = Credential(name='example-inv', kind='ssh', username='asdf', password='asdf')
return wfjt_node_no_prompts
-class TestWorkflowJobCreate:
+class TestWorkflowJobCreate:
def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker):
mock_create = mocker.MagicMock()
with mocker.patch('awx.main.models.WorkflowJobNode.objects.create', mock_create):
@@ -153,6 +159,7 @@ class TestWorkflowJobCreate:
unified_job_template=wfjt_node_with_prompts.unified_job_template,
workflow_job=workflow_job_unit)
+
@mock.patch('awx.main.models.workflow.WorkflowNodeBase.get_parent_nodes', lambda self: [])
class TestWorkflowJobNodeJobKWARGS:
"""
diff --git a/awx/main/tests/unit/scheduler/conftest.py b/awx/main/tests/unit/scheduler/conftest.py
index 8bbba71c46..56473323aa 100644
--- a/awx/main/tests/unit/scheduler/conftest.py
+++ b/awx/main/tests/unit/scheduler/conftest.py
@@ -20,6 +20,7 @@ from awx.main.scheduler import TaskManager
def epoch():
return tz_now()
+
@pytest.fixture
def scheduler_factory(mocker, epoch):
mocker.patch('awx.main.models.Instance.objects.total_capacity', return_value=10000)
@@ -51,6 +52,7 @@ def scheduler_factory(mocker, epoch):
return sched
return fn
+
@pytest.fixture
def project_update_factory(epoch):
def fn():
@@ -65,24 +67,28 @@ def project_update_factory(epoch):
})
return fn
+
@pytest.fixture
def pending_project_update(project_update_factory):
project_update = project_update_factory()
project_update['status'] = 'pending'
return project_update
+
@pytest.fixture
def waiting_project_update(epoch, project_update_factory):
project_update = project_update_factory()
project_update['status'] = 'waiting'
return project_update
+
@pytest.fixture
def running_project_update(epoch, project_update_factory):
project_update = project_update_factory()
project_update['status'] = 'running'
return project_update
+
@pytest.fixture
def successful_project_update(epoch, project_update_factory):
project_update = project_update_factory()
@@ -90,6 +96,7 @@ def successful_project_update(epoch, project_update_factory):
project_update['status'] = 'successful'
return project_update
+
@pytest.fixture
def successful_project_update_cache_expired(epoch, project_update_factory):
project_update = project_update_factory()
@@ -100,6 +107,7 @@ def successful_project_update_cache_expired(epoch, project_update_factory):
project_update['project__scm_update_cache_timeout'] = 1
return project_update
+
@pytest.fixture
def failed_project_update(epoch, project_update_factory):
project_update = project_update_factory()
@@ -107,6 +115,7 @@ def failed_project_update(epoch, project_update_factory):
project_update['status'] = 'failed'
return project_update
+
@pytest.fixture
def inventory_update_factory(epoch):
def fn():
@@ -122,6 +131,7 @@ def inventory_update_factory(epoch):
})
return fn
+
@pytest.fixture
def inventory_update_latest_factory(epoch):
def fn():
@@ -137,6 +147,7 @@ def inventory_update_latest_factory(epoch):
})
return fn
+
@pytest.fixture
def inventory_update_latest(inventory_update_latest_factory):
return inventory_update_latest_factory()
@@ -148,36 +159,42 @@ def successful_inventory_update_latest(inventory_update_latest_factory):
iu['finished'] = iu['created'] + timedelta(seconds=10)
return iu
+
@pytest.fixture
def failed_inventory_update_latest(inventory_update_latest_factory):
iu = inventory_update_latest_factory()
iu['status'] = 'failed'
return iu
+
@pytest.fixture
def pending_inventory_update(epoch, inventory_update_factory):
inventory_update = inventory_update_factory()
inventory_update['status'] = 'pending'
return inventory_update
+
@pytest.fixture
def waiting_inventory_update(epoch, inventory_update_factory):
inventory_update = inventory_update_factory()
inventory_update['status'] = 'waiting'
return inventory_update
+
@pytest.fixture
def failed_inventory_update(epoch, inventory_update_factory):
inventory_update = inventory_update_factory()
inventory_update['status'] = 'failed'
return inventory_update
+
@pytest.fixture
def running_inventory_update(epoch, inventory_update_factory):
inventory_update = inventory_update_factory()
inventory_update['status'] = 'running'
return inventory_update
+
@pytest.fixture
def successful_inventory_update(epoch, inventory_update_factory):
inventory_update = inventory_update_factory()
@@ -208,12 +225,14 @@ def job_factory(epoch):
})
return fn
+
@pytest.fixture
def pending_job(job_factory):
job = job_factory()
job['status'] = 'pending'
return job
+
@pytest.fixture
def running_job(job_factory):
job = job_factory()
@@ -232,6 +251,7 @@ def inventory_source_factory():
})
return fn
+
@pytest.fixture
def inventory_id_sources(inventory_source_factory):
return [
@@ -240,4 +260,3 @@ def inventory_id_sources(inventory_source_factory):
inventory_source_factory(id=2),
]),
]
-
diff --git a/awx/main/tests/unit/scheduler/test_dag.py b/awx/main/tests/unit/scheduler/test_dag.py
index 3aad7e9b27..e32dffe28f 100644
--- a/awx/main/tests/unit/scheduler/test_dag.py
+++ b/awx/main/tests/unit/scheduler/test_dag.py
@@ -8,6 +8,7 @@ from awx.main.scheduler.dag_workflow import WorkflowDAG
from awx.main.models import Job
from awx.main.models.workflow import WorkflowJobNode
+
@pytest.fixture
def dag_root():
dag = SimpleDAG()
@@ -25,9 +26,10 @@ def dag_root():
dag.add_edge(data[0], data[1])
dag.add_edge(data[2], data[3])
dag.add_edge(data[4], data[5])
-
+
return dag
+
@pytest.fixture
def dag_simple_edge_labels():
dag = SimpleDAG()
@@ -62,6 +64,7 @@ class TestSimpleDAG(object):
nodes = dag.get_dependencies(dag.nodes[0]['node_object'], 'two')
'''
+
@pytest.fixture
def factory_node():
def fn(id, status):
@@ -72,6 +75,7 @@ def factory_node():
return wfn
return fn
+
@pytest.fixture
def workflow_dag_level_2(factory_node):
dag = WorkflowDAG()
@@ -91,6 +95,7 @@ def workflow_dag_level_2(factory_node):
return (dag, data[3:6], False)
+
@pytest.fixture
def workflow_dag_multiple_roots(factory_node):
dag = WorkflowDAG()
@@ -111,6 +116,7 @@ def workflow_dag_multiple_roots(factory_node):
expected = data[0:3]
return (dag, expected, False)
+
@pytest.fixture
def workflow_dag_multiple_edges_labeled(factory_node):
dag = WorkflowDAG()
@@ -133,6 +139,7 @@ def workflow_dag_multiple_edges_labeled(factory_node):
expected = data[5:6]
return (dag, expected, False)
+
@pytest.fixture
def workflow_dag_finished(factory_node):
dag = WorkflowDAG()
@@ -155,6 +162,7 @@ def workflow_dag_finished(factory_node):
expected = []
return (dag, expected, True)
+
@pytest.fixture
def workflow_dag_always(factory_node):
dag = WorkflowDAG()
@@ -171,6 +179,7 @@ def workflow_dag_always(factory_node):
expected = data[2:3]
return (dag, expected, False)
+
@pytest.fixture(params=['workflow_dag_multiple_roots', 'workflow_dag_level_2',
'workflow_dag_multiple_edges_labeled', 'workflow_dag_finished',
'workflow_dag_always'])
@@ -185,4 +194,3 @@ class TestWorkflowDAG():
def test_is_workflow_done(self, workflow_dag):
dag, expected, is_done = workflow_dag
assert dag.is_workflow_done() == is_done
-
diff --git a/awx/main/tests/unit/scheduler/test_dependency_graph.py b/awx/main/tests/unit/scheduler/test_dependency_graph.py
index 081f175027..c1c92411ea 100644
--- a/awx/main/tests/unit/scheduler/test_dependency_graph.py
+++ b/awx/main/tests/unit/scheduler/test_dependency_graph.py
@@ -10,27 +10,31 @@ from django.utils.timezone import now as tz_now
from awx.main.scheduler.dependency_graph import DependencyGraph
from awx.main.scheduler.partial import ProjectUpdateDict
+
@pytest.fixture
def graph():
return DependencyGraph()
+
@pytest.fixture
def job():
return dict(project_id=1)
+
@pytest.fixture
def unsuccessful_last_project(graph, job):
- pu = ProjectUpdateDict(dict(id=1,
- project__scm_update_cache_timeout=999999,
- project_id=1,
- status='failed',
- created='3',
+ pu = ProjectUpdateDict(dict(id=1,
+ project__scm_update_cache_timeout=999999,
+ project_id=1,
+ status='failed',
+ created='3',
finished='3',))
graph.add_latest_project_update(pu)
return graph
+
@pytest.fixture
def last_dependent_project(graph):
now = tz_now()
@@ -39,15 +43,16 @@ def last_dependent_project(graph):
'project_id': 1,
'created': now,
}
- pu = ProjectUpdateDict(dict(id=1, project_id=1, status='waiting',
- project__scm_update_cache_timeout=0,
- launch_type='dependency',
+ pu = ProjectUpdateDict(dict(id=1, project_id=1, status='waiting',
+ project__scm_update_cache_timeout=0,
+ launch_type='dependency',
created=now - timedelta(seconds=1),))
-
+
graph.add_latest_project_update(pu)
return (graph, job)
+
@pytest.fixture
def timedout_project_update(graph, job):
now = tz_now()
@@ -56,16 +61,17 @@ def timedout_project_update(graph, job):
'project_id': 1,
'created': now,
}
- pu = ProjectUpdateDict(dict(id=1, project_id=1, status='successful',
- project__scm_update_cache_timeout=10,
- launch_type='dependency',
- created=now - timedelta(seconds=100),
+ pu = ProjectUpdateDict(dict(id=1, project_id=1, status='successful',
+ project__scm_update_cache_timeout=10,
+ launch_type='dependency',
+ created=now - timedelta(seconds=100),
finished=now - timedelta(seconds=11),))
-
+
graph.add_latest_project_update(pu)
return (graph, job)
+
@pytest.fixture
def not_timedout_project_update(graph, job):
now = tz_now()
@@ -74,19 +80,18 @@ def not_timedout_project_update(graph, job):
'project_id': 1,
'created': now,
}
- pu = ProjectUpdateDict(dict(id=1, project_id=1, status='successful',
- project__scm_update_cache_timeout=3600,
- launch_type='dependency',
- created=now - timedelta(seconds=100),
+ pu = ProjectUpdateDict(dict(id=1, project_id=1, status='successful',
+ project__scm_update_cache_timeout=3600,
+ launch_type='dependency',
+ created=now - timedelta(seconds=100),
finished=now - timedelta(seconds=11),))
-
+
graph.add_latest_project_update(pu)
return (graph, job)
class TestShouldUpdateRelatedProject():
-
def test_no_project_updates(self, graph, job):
actual = graph.should_update_related_project(job)
@@ -107,7 +112,7 @@ class TestShouldUpdateRelatedProject():
assert False is actual
def test_unsuccessful_last_project(self, unsuccessful_last_project, job):
- graph = unsuccessful_last_project
+ graph = unsuccessful_last_project
actual = graph.should_update_related_project(job)
@@ -115,7 +120,6 @@ class TestShouldUpdateRelatedProject():
def test_last_dependent_project(self, last_dependent_project):
(graph, job) = last_dependent_project
-
+
actual = graph.should_update_related_project(job)
assert False is actual
-
diff --git a/awx/main/tests/unit/scheduler/test_scheduler_inventory_update.py b/awx/main/tests/unit/scheduler/test_scheduler_inventory_update.py
index 09125df527..5e49eec729 100644
--- a/awx/main/tests/unit/scheduler/test_scheduler_inventory_update.py
+++ b/awx/main/tests/unit/scheduler/test_scheduler_inventory_update.py
@@ -3,10 +3,12 @@
import pytest
from datetime import timedelta
+
@pytest.fixture
def pending_job(job_factory):
return job_factory(project__scm_update_on_launch=False, inventory__inventory_sources=['1'])
+
@pytest.fixture
def successful_inventory_update_latest(inventory_update_latest_factory):
iu = inventory_update_latest_factory()
@@ -15,6 +17,7 @@ def successful_inventory_update_latest(inventory_update_latest_factory):
iu['finished'] = iu['created'] + timedelta(seconds=10)
return iu
+
@pytest.fixture
def successful_inventory_update_latest_cache_expired(inventory_update_latest_factory):
iu = inventory_update_latest_factory()
@@ -22,6 +25,7 @@ def successful_inventory_update_latest_cache_expired(inventory_update_latest_fac
iu['finished'] = iu['created'] + timedelta(seconds=2)
return iu
+
class TestStartInventoryUpdate():
def test_pending(self, scheduler_factory, pending_inventory_update):
scheduler = scheduler_factory(tasks=[pending_inventory_update])
@@ -30,6 +34,7 @@ class TestStartInventoryUpdate():
scheduler.start_task.assert_called_with(pending_inventory_update)
+
class TestInventoryUpdateBlocked():
def test_running_inventory_update(self, epoch, scheduler_factory, running_inventory_update, pending_inventory_update):
running_inventory_update['created'] = epoch - timedelta(seconds=100)
@@ -47,8 +52,8 @@ class TestInventoryUpdateBlocked():
scheduler._schedule()
-class TestCreateDependentInventoryUpdate():
+class TestCreateDependentInventoryUpdate():
def test(self, scheduler_factory, pending_job, waiting_inventory_update, inventory_id_sources):
scheduler = scheduler_factory(tasks=[pending_job],
create_inventory_update=waiting_inventory_update,
@@ -82,4 +87,3 @@ class TestCreateDependentInventoryUpdate():
scheduler._schedule()
scheduler.start_task.assert_called_with(waiting_inventory_update, [pending_job])
-
diff --git a/awx/main/tests/unit/scheduler/test_scheduler_job.py b/awx/main/tests/unit/scheduler/test_scheduler_job.py
index 5d045efaec..cac315af4b 100644
--- a/awx/main/tests/unit/scheduler/test_scheduler_job.py
+++ b/awx/main/tests/unit/scheduler/test_scheduler_job.py
@@ -3,6 +3,7 @@
import pytest
from datetime import timedelta
+
class TestJobBlocked():
def test_inventory_update_waiting(self, scheduler_factory, waiting_inventory_update, pending_job):
scheduler = scheduler_factory(tasks=[waiting_inventory_update, pending_job])
@@ -36,6 +37,7 @@ class TestJobBlocked():
scheduler.start_task.assert_not_called()
assert scheduler.create_project_update.call_count == 0
+
class TestJob():
@pytest.fixture
def successful_project_update(self, project_update_factory):
@@ -54,6 +56,7 @@ class TestJob():
scheduler.start_task.assert_called_with(pending_job)
+
class TestCapacity():
@pytest.fixture
def pending_job_high_impact(self, mocker, job_factory):
@@ -81,4 +84,3 @@ class TestCapacity():
calls = [mocker.call(job) for job in pending_jobs_impactful]
scheduler.start_task.assert_has_calls(calls)
-
diff --git a/awx/main/tests/unit/scheduler/test_scheduler_project_update.py b/awx/main/tests/unit/scheduler/test_scheduler_project_update.py
index 8122d93c09..e8a5af17c8 100644
--- a/awx/main/tests/unit/scheduler/test_scheduler_project_update.py
+++ b/awx/main/tests/unit/scheduler/test_scheduler_project_update.py
@@ -3,6 +3,7 @@
# ProjectUpdateDict. We should instead return a ProjectUpdateLatestDict()
# For now, this is ok since the fields on deviate that much.
+
class TestStartProjectUpdate():
def test(self, scheduler_factory, pending_project_update):
scheduler = scheduler_factory(tasks=[pending_project_update])
@@ -26,7 +27,6 @@ class TestStartProjectUpdate():
class TestCreateDependentProjectUpdate():
-
def test(self, scheduler_factory, pending_job, waiting_project_update):
scheduler = scheduler_factory(tasks=[pending_job],
create_project_update=waiting_project_update)
@@ -58,6 +58,7 @@ class TestCreateDependentProjectUpdate():
scheduler.start_task.assert_called_with(waiting_project_update, [pending_job])
+
class TestProjectUpdateBlocked():
def test_projct_update_running(self, scheduler_factory, running_project_update, pending_project_update):
scheduler = scheduler_factory(tasks=[running_project_update, pending_project_update])
@@ -72,4 +73,3 @@ class TestProjectUpdateBlocked():
scheduler._schedule()
scheduler.start_task.assert_not_called()
-
diff --git a/awx/main/tests/unit/settings/test_defaults.py b/awx/main/tests/unit/settings/test_defaults.py
index d1c80fce3e..894289002d 100644
--- a/awx/main/tests/unit/settings/test_defaults.py
+++ b/awx/main/tests/unit/settings/test_defaults.py
@@ -3,6 +3,7 @@ import pytest
from django.conf import settings
from datetime import timedelta
+
@pytest.mark.parametrize("job_name,function_path", [
('admin_checks', 'awx.main.tasks.run_administrative_checks'),
('tower_scheduler', 'awx.main.tasks.tower_periodic_scheduler'),
diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py
index 2bda14eea2..5dc3a5e57b 100644
--- a/awx/main/tests/unit/test_access.py
+++ b/awx/main/tests/unit/test_access.py
@@ -21,8 +21,8 @@ from awx.main.models import Credential, Inventory, Project, Role, Organization,
def user_unit():
return User(username='rando', password='raginrando', email='rando@redhat.com')
-class TestRelatedFieldAccess:
+class TestRelatedFieldAccess:
@pytest.fixture
def resource_good(self, mocker):
good_role = mocker.MagicMock(__contains__=lambda self, user: True)
@@ -107,6 +107,7 @@ class TestRelatedFieldAccess:
assert access.check_related(
'related', mocker.MagicMock, data, obj=resource, mandatory=True)
+
@pytest.fixture
def job_template_with_ids(job_template_factory):
# Create non-persisted objects with IDs to send to job_template_factory
@@ -122,6 +123,7 @@ def job_template_with_ids(job_template_factory):
persisted=False)
return jt_objects.job_template
+
def test_superuser(mocker):
user = mocker.MagicMock(spec=User, id=1, is_superuser=True)
access = BaseAccess(user)
@@ -129,6 +131,7 @@ def test_superuser(mocker):
can_add = check_superuser(BaseAccess.can_add)
assert can_add(access, None) is True
+
def test_not_superuser(mocker):
user = mocker.MagicMock(spec=User, id=1, is_superuser=False)
access = BaseAccess(user)
@@ -136,6 +139,7 @@ def test_not_superuser(mocker):
can_add = check_superuser(BaseAccess.can_add)
assert can_add(access, None) is False
+
def test_jt_existing_values_are_nonsensitive(job_template_with_ids, user_unit):
"""Assure that permission checks are not required if submitted data is
identical to what the job template already has."""
@@ -145,6 +149,7 @@ def test_jt_existing_values_are_nonsensitive(job_template_with_ids, user_unit):
assert access.changes_are_non_sensitive(job_template_with_ids, data)
+
def test_change_jt_sensitive_data(job_template_with_ids, mocker, user_unit):
"""Assure that can_add is called with all ForeignKeys."""
@@ -167,6 +172,7 @@ def test_change_jt_sensitive_data(job_template_with_ids, mocker, user_unit):
'network_credential': job_template_with_ids.network_credential.id
})
+
def test_jt_add_scan_job_check(job_template_with_ids, user_unit):
"Assure that permissions to add scan jobs work correctly"
@@ -196,23 +202,28 @@ def test_jt_add_scan_job_check(job_template_with_ids, user_unit):
'job_type': 'scan'
})
+
def mock_raise_license_forbids(self, add_host=False, feature=None, check_expiration=True):
raise LicenseForbids("Feature not enabled")
+
def mock_raise_none(self, add_host=False, feature=None, check_expiration=True):
return None
-
+
+
def test_jt_can_start_ha(job_template_with_ids):
with mock.patch.object(Instance.objects, 'active_count', return_value=2):
with mock.patch('awx.main.access.BaseAccess.check_license', new=mock_raise_license_forbids):
with pytest.raises(LicenseForbids):
JobTemplateAccess(user_unit).can_start(job_template_with_ids)
+
def test_jt_can_add_bad_data(user_unit):
"Assure that no server errors are returned if we call JT can_add with bad data"
access = JobTemplateAccess(user_unit)
assert not access.can_add({'asdf': 'asdf'})
+
class TestWorkflowAccessMethods:
@pytest.fixture
def workflow(self, workflow_job_template_factory):
@@ -259,6 +270,7 @@ def test_user_capabilities_method():
'copy': 'foobar'
}
+
def test_system_job_template_can_start(mocker):
user = mocker.MagicMock(spec=User, id=1, is_system_auditor=True, is_superuser=False)
assert user.is_system_auditor
diff --git a/awx/main/tests/unit/test_ha.py b/awx/main/tests/unit/test_ha.py
index 07249a67fb..ebd86cd3a2 100644
--- a/awx/main/tests/unit/test_ha.py
+++ b/awx/main/tests/unit/test_ha.py
@@ -6,10 +6,12 @@ import mock
# AWX
from awx.main.ha import is_ha_environment
+
@mock.patch('awx.main.models.Instance.objects.count', lambda: 2)
def test_multiple_instances():
assert is_ha_environment()
+
@mock.patch('awx.main.models.Instance.objects.count', lambda: 1)
def test_db_localhost():
assert is_ha_environment() is False
diff --git a/awx/main/tests/unit/test_python_requirements.py b/awx/main/tests/unit/test_python_requirements.py
index 8b7382dd74..7c5f671e78 100644
--- a/awx/main/tests/unit/test_python_requirements.py
+++ b/awx/main/tests/unit/test_python_requirements.py
@@ -4,8 +4,8 @@ from pip.operations import freeze
from django.conf import settings
-def test_env_matches_requirements_txt():
+def test_env_matches_requirements_txt():
def check_is_in(src, dests):
if src not in dests:
print("%s not in" % src)
@@ -33,7 +33,7 @@ def test_env_matches_requirements_txt():
continue
line.rstrip()
reqs_expected.append(line.rstrip().split('=='))
-
+
not_found = []
for r in reqs_expected:
res = check_is_in(r, reqs_actual)
diff --git a/awx/main/tests/unit/test_redact.py b/awx/main/tests/unit/test_redact.py
index 3535869ee1..931ef72ebc 100644
--- a/awx/main/tests/unit/test_redact.py
+++ b/awx/main/tests/unit/test_redact.py
@@ -78,7 +78,6 @@ TEST_CLEARTEXT.append({
})
-
# should redact sensitive usernames and passwords
def test_uri_scm_simple_redacted():
for uri in TEST_URIS:
@@ -88,12 +87,14 @@ def test_uri_scm_simple_redacted():
if uri.password:
assert uri.username not in redacted_str
+
# should replace secret data with safe string, UriCleaner.REPLACE_STR
def test_uri_scm_simple_replaced():
for uri in TEST_URIS:
redacted_str = UriCleaner.remove_sensitive(str(uri))
assert redacted_str.count(UriCleaner.REPLACE_STR) == uri.get_secret_count()
+
# should redact multiple uris in text
def test_uri_scm_multiple():
cleartext = ''
@@ -108,6 +109,7 @@ def test_uri_scm_multiple():
if uri.password:
assert uri.username not in redacted_str
+
# should replace multiple secret data with safe string
def test_uri_scm_multiple_replaced():
cleartext = ''
@@ -123,6 +125,7 @@ def test_uri_scm_multiple_replaced():
redacted_str = UriCleaner.remove_sensitive(cleartext)
assert redacted_str.count(UriCleaner.REPLACE_STR) == find_count
+
# should redact and replace multiple secret data within a complex cleartext blob
def test_uri_scm_cleartext_redact_and_replace():
for test_data in TEST_CLEARTEXT:
diff --git a/awx/main/tests/unit/test_settings.py b/awx/main/tests/unit/test_settings.py
index 2419431c78..d339262808 100644
--- a/awx/main/tests/unit/test_settings.py
+++ b/awx/main/tests/unit/test_settings.py
@@ -1,5 +1,6 @@
from split_settings.tools import include
+
def test_postprocess_auth_basic_enabled():
locals().update({'__file__': __file__})
diff --git a/awx/main/tests/unit/test_signals.py b/awx/main/tests/unit/test_signals.py
index c3830ee525..cc14824a67 100644
--- a/awx/main/tests/unit/test_signals.py
+++ b/awx/main/tests/unit/test_signals.py
@@ -1,5 +1,6 @@
from awx.main import signals
+
class TestCleanupDetachedLabels:
def test_cleanup_detached_labels_on_deleted_parent(self, mocker):
mock_labels = [mocker.MagicMock(), mocker.MagicMock()]
@@ -10,7 +11,7 @@ class TestCleanupDetachedLabels:
mock_labels[1].is_candidate_for_detach.return_value = False
signals.cleanup_detached_labels_on_deleted_parent(None, mock_instance)
-
+
mock_labels[0].is_candidate_for_detach.assert_called_with()
mock_labels[1].is_candidate_for_detach.assert_called_with()
mock_labels[0].delete.assert_called_with()
diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py
index 3c0f766553..b83772bb59 100644
--- a/awx/main/tests/unit/test_tasks.py
+++ b/awx/main/tests/unit/test_tasks.py
@@ -12,22 +12,26 @@ from awx.main.tasks import (
)
from awx.main.task_engine import TaskEnhancer
+
@contextmanager
def apply_patches(_patches):
[p.start() for p in _patches]
yield
[p.stop() for p in _patches]
+
def test_send_notifications_not_list():
with pytest.raises(TypeError):
send_notifications(None)
+
def test_send_notifications_job_id(mocker):
with mocker.patch('awx.main.models.UnifiedJob.objects.get'):
send_notifications([], job_id=1)
assert UnifiedJob.objects.get.called
assert UnifiedJob.objects.get.called_with(id=1)
+
def test_send_notifications_list(mocker):
patches = list()
@@ -46,6 +50,7 @@ def test_send_notifications_list(mocker):
assert mock_job.notifications.add.called
assert mock_job.notifications.add.called_with(mock_notification)
+
@pytest.mark.parametrize("current_instances,call_count", [(91, 2), (89,1)])
def test_run_admin_checks_usage(mocker, current_instances, call_count):
patches = list()
diff --git a/awx/main/tests/unit/test_unified_jobs.py b/awx/main/tests/unit/test_unified_jobs.py
index edd6978b47..592c4783b1 100644
--- a/awx/main/tests/unit/test_unified_jobs.py
+++ b/awx/main/tests/unit/test_unified_jobs.py
@@ -23,6 +23,7 @@ def test_result_stdout_raw_handle_file__found(exists, open):
assert result == 'my_file_handler'
+
# stdout file missing, job finished
@mock.patch('os.path.exists', return_value=False)
def test_result_stdout_raw_handle__missing(exists):
@@ -35,6 +36,7 @@ def test_result_stdout_raw_handle__missing(exists):
assert isinstance(result, StringIO)
assert result.read() == 'stdout capture is missing'
+
# stdout file missing, job not finished
@mock.patch('os.path.exists', return_value=False)
def test_result_stdout_raw_handle__pending(exists):
diff --git a/awx/main/tests/unit/test_validators.py b/awx/main/tests/unit/test_validators.py
index b62395424e..ea47785ed0 100644
--- a/awx/main/tests/unit/test_validators.py
+++ b/awx/main/tests/unit/test_validators.py
@@ -15,6 +15,7 @@ from awx.main.tests.data.ssh import (
import pytest
+
def test_valid_rsa_key():
valid_key = TEST_SSH_KEY_DATA
pem_objects = validate_private_key(valid_key)
@@ -26,6 +27,7 @@ def test_valid_rsa_key():
assert pem_objects[0]['key_type'] == 'rsa'
assert not pem_objects[0]['key_enc']
+
def test_valid_locked_rsa_key():
valid_key = TEST_SSH_KEY_DATA_LOCKED
pem_objects = validate_private_key(valid_key)
@@ -37,6 +39,7 @@ def test_valid_locked_rsa_key():
assert pem_objects[0]['key_type'] == 'rsa'
assert pem_objects[0]['key_enc']
+
def test_invalid_rsa_key():
invalid_key = TEST_SSH_KEY_DATA.replace('-----END', '----END')
with pytest.raises(ValidationError):
@@ -46,6 +49,7 @@ def test_invalid_rsa_key():
with pytest.raises(ValidationError):
validate_ssh_private_key(invalid_key)
+
def test_valid_openssh_key():
valid_key = TEST_OPENSSH_KEY_DATA
pem_objects = validate_private_key(valid_key)
@@ -57,6 +61,7 @@ def test_valid_openssh_key():
assert pem_objects[0]['key_type'] == 'ed25519'
assert not pem_objects[0]['key_enc']
+
def test_valid_locked_openssh_key():
valid_key = TEST_OPENSSH_KEY_DATA_LOCKED
pem_objects = validate_private_key(valid_key)
@@ -67,7 +72,8 @@ def test_valid_locked_openssh_key():
pem_objects = validate_ssh_private_key(valid_key)
assert pem_objects[0]['key_type'] == 'ed25519'
assert pem_objects[0]['key_enc']
-
+
+
def test_valid_rsa1_key():
valid_key = TEST_SSH_RSA1_KEY_DATA
pem_objects = validate_ssh_private_key(valid_key)
@@ -79,6 +85,7 @@ def test_valid_rsa1_key():
assert pem_objects[0]['key_type'] == 'rsa1'
assert not pem_objects[0]['key_enc']
+
def test_cert_with_key():
cert_with_key = TEST_SSH_CERT_KEY
with pytest.raises(ValidationError):
diff --git a/awx/main/utils.py b/awx/main/utils.py
index d03ee3b0d4..bacde40198 100644
--- a/awx/main/utils.py
+++ b/awx/main/utils.py
@@ -73,6 +73,7 @@ def get_object_or_403(klass, *args, **kwargs):
except queryset.model.MultipleObjectsReturned as e:
raise PermissionDenied(*e.args)
+
def to_python_boolean(value, allow_none=False):
value = unicode(value)
if value.lower() in ('true', '1', 't'):
@@ -84,6 +85,7 @@ def to_python_boolean(value, allow_none=False):
else:
raise ValueError(_(u'Unable to convert "%s" to boolean') % unicode(value))
+
def camelcase_to_underscore(s):
'''
Convert CamelCase names to lowercase_with_underscore.
@@ -131,6 +133,7 @@ def get_ansible_version():
except:
return 'unknown'
+
@memoize()
def get_ssh_version():
'''
@@ -144,6 +147,7 @@ def get_ssh_version():
except:
return 'unknown'
+
def get_awx_version():
'''
Return Ansible Tower version as reported by setuptools.
@@ -167,9 +171,11 @@ def get_encryption_key_for_pk(pk, field_name):
h.update(field_name)
return h.digest()[:16]
+
def get_encryption_key(instance, field_name):
return get_encryption_key_for_pk(instance.pk, field_name)
+
def encrypt_field(instance, field_name, ask=False, subfield=None):
'''
Return content of the given instance and field name encrypted.
@@ -188,6 +194,7 @@ def encrypt_field(instance, field_name, ask=False, subfield=None):
b64data = base64.b64encode(encrypted)
return '$encrypted$%s$%s' % ('AES', b64data)
+
def decrypt_value(encryption_key, value):
algo, b64data = value[len('$encrypted$'):].split('$', 1)
if algo != 'AES':
@@ -197,6 +204,7 @@ def decrypt_value(encryption_key, value):
value = cipher.decrypt(encrypted)
return value.rstrip('\x00')
+
def decrypt_field(instance, field_name, subfield=None):
'''
Return content of the given instance and field name decrypted.
@@ -210,10 +218,12 @@ def decrypt_field(instance, field_name, subfield=None):
return decrypt_value(key, value)
+
def decrypt_field_value(pk, field_name, value):
key = get_encryption_key_for_pk(pk, field_name)
return decrypt_value(key, value)
+
def update_scm_url(scm_type, url, username=True, password=True,
check_special_cases=True, scp_format=False):
'''
@@ -321,7 +331,6 @@ def update_scm_url(scm_type, url, username=True, password=True,
return new_url
-
def get_allowed_fields(obj, serializer_mapping):
from django.contrib.auth.models import User
@@ -337,6 +346,7 @@ def get_allowed_fields(obj, serializer_mapping):
return allowed_fields
+
def model_instance_diff(old, new, serializer_mapping=None):
"""
Calculate the differences between two model instances. One of the instances may be None (i.e., a newly
@@ -505,6 +515,7 @@ def parse_yaml_or_json(vars_str):
vars_dict = {}
return vars_dict
+
@memoize()
def get_system_task_capacity():
'''
@@ -549,6 +560,7 @@ def ignore_inventory_group_removal():
finally:
_inventory_updates.is_removing = previous_value
+
@memoize()
def check_proot_installed():
'''
@@ -564,6 +576,7 @@ def check_proot_installed():
except (OSError, ValueError):
return False
+
def build_proot_temp_dir():
'''
Create a temporary directory for proot to use.
@@ -573,6 +586,7 @@ def build_proot_temp_dir():
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return path
+
def wrap_args_with_proot(args, cwd, **kwargs):
'''
Wrap existing command line with proot to restrict access to:
@@ -616,6 +630,7 @@ def wrap_args_with_proot(args, cwd, **kwargs):
new_args.extend(args)
return new_args
+
def get_pk_from_dict(_dict, key):
'''
Helper for obtaining a pk from user data dict or None if not present.
@@ -625,6 +640,7 @@ def get_pk_from_dict(_dict, key):
except (TypeError, KeyError, ValueError):
return None
+
def build_url(*args, **kwargs):
get = kwargs.pop('get', {})
url = reverse(*args, **kwargs)
@@ -632,12 +648,14 @@ def build_url(*args, **kwargs):
url += '?' + urllib.urlencode(get)
return url
+
def timestamp_apiformat(timestamp):
timestamp = timestamp.isoformat()
if timestamp.endswith('+00:00'):
timestamp = timestamp[:-6] + 'Z'
return timestamp
+
# damn you python 2.6
def timedelta_total_seconds(timedelta):
return (
@@ -648,6 +666,7 @@ def timedelta_total_seconds(timedelta):
class NoDefaultProvided(object):
pass
+
def getattrd(obj, name, default=NoDefaultProvided):
"""
Same as getattr(), but allows dot notation lookup
@@ -664,10 +683,13 @@ def getattrd(obj, name, default=NoDefaultProvided):
current_apps = apps
+
+
def set_current_apps(apps):
global current_apps
current_apps = apps
+
def get_current_apps():
global current_apps
return current_apps
diff --git a/awx/main/validators.py b/awx/main/validators.py
index afe465c76e..1c92d9a645 100644
--- a/awx/main/validators.py
+++ b/awx/main/validators.py
@@ -172,6 +172,7 @@ def validate_ssh_private_key(data):
"""
return validate_pem(data, min_keys=1)
+
def vars_validate_or_raise(vars_str):
"""
Validate that fields like extra_vars or variables on resources like
diff --git a/awx/plugins/fact_caching/tower.py b/awx/plugins/fact_caching/tower.py
index 3e89ccef36..a956a5f50b 100755
--- a/awx/plugins/fact_caching/tower.py
+++ b/awx/plugins/fact_caching/tower.py
@@ -44,6 +44,7 @@ except ImportError:
print("pyzmq is required")
sys.exit(1)
+
class CacheModule(BaseCacheModule):
def __init__(self, *args, **kwargs):
diff --git a/awx/plugins/library/scan_files.py b/awx/plugins/library/scan_files.py
index 64c52c65bd..3ab092947d 100644
--- a/awx/plugins/library/scan_files.py
+++ b/awx/plugins/library/scan_files.py
@@ -99,6 +99,7 @@ EXAMPLES = '''
# },
'''
+
def main():
module = AnsibleModule(
argument_spec = dict(paths=dict(required=True, type='list'),
diff --git a/awx/plugins/library/scan_packages.py b/awx/plugins/library/scan_packages.py
index 3b94fb75ab..13b28542f6 100755
--- a/awx/plugins/library/scan_packages.py
+++ b/awx/plugins/library/scan_packages.py
@@ -39,6 +39,7 @@ EXAMPLES = '''
# }, ... ] } }
'''
+
def rpm_package_list():
import rpm
trans_set = rpm.TransactionSet()
@@ -53,6 +54,7 @@ def rpm_package_list():
installed_packages.append(package_details)
return installed_packages
+
def deb_package_list():
import apt
apt_cache = apt.Cache()
@@ -67,6 +69,7 @@ def deb_package_list():
installed_packages.append(package_details)
return installed_packages
+
def main():
module = AnsibleModule(
argument_spec = dict(os_family=dict(required=True))
diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py
index 778cdcc058..11a8edc745 100644
--- a/awx/plugins/library/scan_services.py
+++ b/awx/plugins/library/scan_services.py
@@ -43,12 +43,14 @@ EXAMPLES = '''
# }, .... ] } }
'''
+
class BaseService(object):
def __init__(self, module):
self.module = module
self.incomplete_warning = False
+
class ServiceScanService(BaseService):
def gather_services(self):
@@ -135,6 +137,7 @@ class ServiceScanService(BaseService):
services.append(service_data)
return services
+
class SystemctlScanService(BaseService):
def systemd_enabled(self):
@@ -170,6 +173,7 @@ class SystemctlScanService(BaseService):
"source": "systemd"})
return services
+
def main():
module = AnsibleModule(argument_spec = dict())
service_modules = (ServiceScanService, SystemctlScanService)
diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py
index 4dc7791324..69cd7f232b 100644
--- a/awx/settings/defaults.py
+++ b/awx/settings/defaults.py
@@ -24,6 +24,7 @@ for setting in dir(global_settings):
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+
def is_testing(argv=None):
import sys
'''Return True if running django or py.test unit tests.'''
@@ -34,6 +35,7 @@ def is_testing(argv=None):
return True
return False
+
def IS_TESTING(argv=None):
return is_testing(argv)
diff --git a/awx/sso/__init__.py b/awx/sso/__init__.py
index c7ec0d9321..aa65d65a11 100644
--- a/awx/sso/__init__.py
+++ b/awx/sso/__init__.py
@@ -11,6 +11,7 @@ xmlsec_initialized = False
import dm.xmlsec.binding # noqa
original_xmlsec_initialize = dm.xmlsec.binding.initialize
+
def xmlsec_initialize(*args, **kwargs):
global xmlsec_init_lock, xmlsec_initialized, original_xmlsec_initialize
with xmlsec_init_lock:
diff --git a/awx/sso/conf.py b/awx/sso/conf.py
index 533067a91c..48b58575c0 100644
--- a/awx/sso/conf.py
+++ b/awx/sso/conf.py
@@ -797,6 +797,7 @@ register(
# SAML AUTHENTICATION SETTINGS
###############################################################################
+
def get_saml_metadata_url():
return urlparse.urljoin(settings.TOWER_URL_BASE, reverse('sso:saml_metadata'))
diff --git a/awx/sso/views.py b/awx/sso/views.py
index 512caa0e75..a25aabf511 100644
--- a/awx/sso/views.py
+++ b/awx/sso/views.py
@@ -22,6 +22,7 @@ from awx.api.serializers import UserSerializer
logger = logging.getLogger('awx.sso.views')
+
class BaseRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
diff --git a/setup.py b/setup.py
index 78dde7b8ec..d7b2e925b0 100755
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ else:
#####################################################################
# Helper Functions
+
def explode_glob_path(path):
"""Take a glob and hand back the full recursive expansion,
ignoring links.
diff --git a/tools/data_generators/rbac_dummy_data_generator.py b/tools/data_generators/rbac_dummy_data_generator.py
index 06f4880750..24b1323aea 100755
--- a/tools/data_generators/rbac_dummy_data_generator.py
+++ b/tools/data_generators/rbac_dummy_data_generator.py
@@ -82,6 +82,7 @@ inventory_hosts = []
jobs = []
#job_events = []
+
def spread(n, m):
ret = []
# At least one in each slot, split up the rest exponentially so the first