initial models and endpoints added for workflows

This commit is contained in:
Chris Meyers
2016-08-16 17:45:18 -04:00
parent 0007df0976
commit 4c876b40e4
16 changed files with 766 additions and 19 deletions

View File

@@ -528,6 +528,8 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
serializer_class = JobTemplateSerializer
elif isinstance(obj, SystemJobTemplate):
serializer_class = SystemJobTemplateSerializer
elif isinstance(obj, WorkflowJobTemplateSerializer):
serializer_class = WorkflowJobTemplateSerializer
if serializer_class:
serializer = serializer_class(instance=obj, context=self.context)
return serializer.to_representation(obj)
@@ -2168,6 +2170,95 @@ class SystemJobCancelSerializer(SystemJobSerializer):
class Meta:
fields = ('can_cancel',)
# TODO:
class WorkflowJobSerializer(UnifiedJobSerializer):
class Meta:
model = WorkflowJob
fields = ('*', 'workflow_job_template', 'extra_vars')
def get_related(self, obj):
res = super(WorkflowJobSerializer, self).get_related(obj)
if obj.system_job_template:
res['workflow_job_template'] = reverse('api:workflow_job_template_detail',
args=(obj.workflow_job_template.pk,))
# TODO:
#res['notifications'] = reverse('api:system_job_notifications_list', args=(obj.pk,))
if obj.can_cancel or True:
res['cancel'] = reverse('api:workflow_job_cancel', args=(obj.pk,))
return res
# TODO:
class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer):
pass
# TODO:
class WorkflowJobTemplateListSerializer(UnifiedJobTemplateSerializer):
class Meta:
model = WorkflowJobTemplate
fields = ('*',)
def get_related(self, obj):
res = super(WorkflowJobTemplateListSerializer, self).get_related(obj)
res.update(dict(
jobs = reverse('api:workflow_job_template_jobs_list', args=(obj.pk,)),
#schedules = reverse('api:workflow_job_template_schedules_list', args=(obj.pk,)),
launch = reverse('api:workflow_job_template_launch', args=(obj.pk,)),
workflow_nodes = reverse('api:workflow_job_template_workflow_nodes_list', args=(obj.pk,)),
# TODO: Implement notifications
#notification_templates_any = reverse('api:system_job_template_notification_templates_any_list', args=(obj.pk,)),
#notification_templates_success = reverse('api:system_job_template_notification_templates_success_list', args=(obj.pk,)),
#notification_templates_error = reverse('api:system_job_template_notification_templates_error_list', args=(obj.pk,)),
))
return res
class WorkflowJobTemplateSerializer(WorkflowJobTemplateListSerializer):
pass
class WorkflowNodeSerializer(BaseSerializer):
#workflow_job_template = UnifiedJobTemplateSerializer()
class Meta:
model = WorkflowNode
fields = ('id', 'url', 'related', 'workflow_job_template', 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',)
def get_related(self, obj):
res = super(WorkflowNodeSerializer, self).get_related(obj)
res['workflow_job_template'] = reverse('api:workflow_job_template_detail', args=(obj.workflow_job_template.pk,))
if obj.unified_job_template:
res['unified_job_template'] = obj.unified_job_template.get_absolute_url()
res['success_nodes'] = reverse('api:workflow_node_success_nodes_list', args=(obj.pk,))
res['failure_nodes'] = reverse('api:workflow_node_failure_nodes_list', args=(obj.pk,))
res['always_nodes'] = reverse('api:workflow_node_always_nodes_list', args=(obj.pk,))
return res
class WorkflowNodeDetailSerializer(WorkflowNodeSerializer):
'''
Influence the api browser sample data to not include workflow_job_template
when editing a WorkflowNode.
Note: I was not able to accomplish this trough the use of extra_kwargs.
Maybe something to do with workflow_job_template being a relational field?
'''
def build_relational_field(self, field_name, relation_info):
field_class, field_kwargs = super(WorkflowNodeDetailSerializer, self).build_relational_field(field_name, relation_info)
if self.instance and field_name == 'workflow_job_template':
field_kwargs['read_only'] = True
field_kwargs.pop('queryset', None)
return field_class, field_kwargs
class WorkflowNodeListSerializer(WorkflowNodeSerializer):
pass
class JobListSerializer(JobSerializer, UnifiedJobListSerializer):
pass

View File

@@ -255,6 +255,23 @@ system_job_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/notifications/$', 'system_job_notifications_list'),
)
workflow_job_template_urls = patterns('awx.api.views',
url(r'^$', 'workflow_job_template_list'),
url(r'^(?P<pk>[0-9]+)/$', 'workflow_job_template_detail'),
url(r'^(?P<pk>[0-9]+)/jobs/$', 'workflow_job_template_jobs_list'),
url(r'^(?P<pk>[0-9]+)/launch/$', 'workflow_job_template_launch'),
url(r'^(?P<pk>[0-9]+)/workflow_nodes/$', 'workflow_job_template_workflow_nodes_list'),
# url(r'^(?P<pk>[0-9]+)/cancel/$', 'workflow_job_template_cancel'),
#url(r'^(?P<pk>[0-9]+)/nodes/$', 'workflow_job_template_node_list'),
)
workflow_job_urls = patterns('awx.api.views',
url(r'^$', 'workflow_job_list'),
url(r'^(?P<pk>[0-9]+)/$', 'workflow_job_detail'),
# url(r'^(?P<pk>[0-9]+)/cancel/$', 'workflow_job_cancel'),
#url(r'^(?P<pk>[0-9]+)/notifications/$', 'workflow_job_notifications_list'),
)
notification_template_urls = patterns('awx.api.views',
url(r'^$', 'notification_template_list'),
url(r'^(?P<pk>[0-9]+)/$', 'notification_template_detail'),
@@ -272,6 +289,14 @@ label_urls = patterns('awx.api.views',
url(r'^(?P<pk>[0-9]+)/$', 'label_detail'),
)
workflow_node_urls = patterns('awx.api.views',
url(r'^$', 'workflow_node_list'),
url(r'^(?P<pk>[0-9]+)/$', 'workflow_node_detail'),
url(r'^(?P<pk>[0-9]+)/success_nodes/$', 'workflow_node_success_nodes_list'),
url(r'^(?P<pk>[0-9]+)/failure_nodes/$', 'workflow_node_failure_nodes_list'),
url(r'^(?P<pk>[0-9]+)/always_nodes/$', 'workflow_node_always_nodes_list'),
)
schedule_urls = patterns('awx.api.views',
url(r'^$', 'schedule_list'),
url(r'^(?P<pk>[0-9]+)/$', 'schedule_detail'),
@@ -321,7 +346,10 @@ v1_urls = patterns('awx.api.views',
url(r'^system_jobs/', include(system_job_urls)),
url(r'^notification_templates/', include(notification_template_urls)),
url(r'^notifications/', include(notification_urls)),
url(r'^workflow_job_templates/',include(workflow_job_template_urls)),
url(r'^workflow_jobs/' ,include(workflow_job_urls)),
url(r'^labels/', include(label_urls)),
url(r'^workflow_nodes/', include(workflow_node_urls)),
url(r'^unified_job_templates/$','unified_job_template_list'),
url(r'^unified_jobs/$', 'unified_job_list'),
url(r'^activity_stream/', include(activity_stream_urls)),

View File

@@ -11,6 +11,7 @@ import socket
import sys
import errno
import logging
import copy
from base64 import b64encode
from collections import OrderedDict
@@ -145,6 +146,8 @@ class ApiV1RootView(APIView):
data['unified_job_templates'] = reverse('api:unified_job_template_list')
data['unified_jobs'] = reverse('api:unified_job_list')
data['activity_stream'] = reverse('api:activity_stream_list')
data['workflow_job_templates'] = reverse('api:workflow_job_template_list')
data['workflow_jobs'] = reverse('api:workflow_job_list')
return Response(data)
@@ -1747,16 +1750,24 @@ class GroupList(ListCreateAPIView):
model = Group
serializer_class = GroupSerializer
class GroupChildrenList(SubListCreateAttachDetachAPIView):
'''
Useful when you have a self-refering ManyToManyRelationship.
* Tower uses a shallow (2-deep only) url pattern. For example:
model = Group
serializer_class = GroupSerializer
parent_model = Group
relationship = 'children'
When an object hangs off of a parent object you would have the url of the
form /api/v1/parent_model/34/child_model. If you then wanted a child of the
child model you would NOT do /api/v1/parent_model/34/child_model/87/child_child_model
Instead, you would access the child_child_model via /api/v1/child_child_model/87/
and you would create child_child_model's off of /api/v1/child_model/87/child_child_model_set
Now, when creating child_child_model related to child_model you still want to
link child_child_model to parent_model. That's what this class is for
'''
class EnforceParentRelationshipMixin(object):
enforce_parent_relationship = ''
def update_raw_data(self, data):
data.pop('inventory', None)
return super(GroupChildrenList, self).update_raw_data(data)
data.pop(self.enforce_parent_relationship, None)
return super(EnforceParentRelationshipMixin, self).update_raw_data(data)
def create(self, request, *args, **kwargs):
# Inject parent group inventory ID into new group data.
@@ -1764,16 +1775,16 @@ class GroupChildrenList(SubListCreateAttachDetachAPIView):
# HACK: Make request data mutable.
if getattr(data, '_mutable', None) is False:
data._mutable = True
data['inventory'] = self.get_parent_object().inventory_id
return super(GroupChildrenList, self).create(request, *args, **kwargs)
data[self.enforce_parent_relationship] = getattr(self.get_parent_object(), '%s_id' % relationship)
return super(EnforceParentRelationshipMixin, self).create(request, *args, **kwargs)
def unattach(self, request, *args, **kwargs):
sub_id = request.data.get('id', None)
if sub_id is not None:
return super(GroupChildrenList, self).unattach(request, *args, **kwargs)
parent = self.get_parent_object()
parent.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class GroupChildrenList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
model = Group
serializer_class = GroupSerializer
parent_model = Group
relationship = 'children'
enforce_parent_relationship = 'inventory'
class GroupPotentialChildrenList(SubListAPIView):
@@ -2604,6 +2615,131 @@ 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)
# TODO:
class WorkflowNodeList(ListCreateAPIView):
model = WorkflowNode
serializer_class = WorkflowNodeSerializer
new_in_310 = True
# TODO:
class WorkflowNodeDetail(RetrieveUpdateDestroyAPIView):
model = WorkflowNode
serializer_class = WorkflowNodeDetailSerializer
parent_model = WorkflowJobTemplate
relationship = 'workflow_job_template'
new_in_310 = True
class WorkflowNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
model = WorkflowNode
serializer_class = WorkflowNodeListSerializer
always_allow_superuser = True # TODO: RBAC
parent_model = WorkflowNode
relationship = ''
enforce_parent_relationship = 'workflow_job_template'
new_in_310 = True
'''
Limit the set of WorkflowNodes to the related nodes of specified by
'relationship'
'''
def get_queryset(self):
parent = self.get_parent_object()
self.check_parent_access(parent)
return getattr(parent, self.relationship).all()
class WorkflowNodeSuccessNodesList(WorkflowNodeChildrenBaseList):
relationship = 'success_nodes'
class WorkflowNodeFailureNodesList(WorkflowNodeChildrenBaseList):
relationship = 'failure_nodes'
class WorkflowNodeAlwaysNodesList(WorkflowNodeChildrenBaseList):
relationship = 'always_nodes'
# TODO:
class WorkflowJobTemplateList(ListCreateAPIView):
model = WorkflowJobTemplate
serializer_class = WorkflowJobTemplateListSerializer
always_allow_superuser = False
# TODO: RBAC
'''
def post(self, request, *args, **kwargs):
ret = super(WorkflowJobTemplateList, self).post(request, *args, **kwargs)
if ret.status_code == 201:
workflow_job_template = WorkflowJobTemplate.objects.get(id=ret.data['id'])
workflow_job_template.admin_role.members.add(request.user)
return ret
'''
# TODO:
class WorkflowJobTemplateDetail(RetrieveUpdateDestroyAPIView):
model = WorkflowJobTemplate
serializer_class = WorkflowJobTemplateSerializer
always_allow_superuser = False
# TODO:
class WorkflowJobTemplateLaunch(GenericAPIView):
model = WorkflowJobTemplate
serializer_class = EmptySerializer
def get(self, request, *args, **kwargs):
return Response({})
def post(self, request, *args, **kwargs):
obj = self.get_object()
if not request.user.can_access(self.model, 'start', obj):
raise PermissionDenied()
new_job = obj.create_unified_job(**request.data)
new_job.signal_start(**request.data)
data = dict(system_job=new_job.id)
return Response(data, status=status.HTTP_201_CREATED)
# TODO:
class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView):
model = WorkflowNode
serializer_class = WorkflowNodeListSerializer
always_allow_superuser = True # TODO: RBAC
parent_model = WorkflowJobTemplate
relationship = 'workflow_nodes'
parent_key = 'workflow_job_template'
# TODO:
class WorkflowJobTemplateJobsList(SubListAPIView):
model = WorkflowJob
serializer_class = WorkflowJobListSerializer
parent_model = WorkflowJobTemplate
relationship = 'jobs'
parent_key = 'workflow_job_template'
# TODO:
class WorkflowJobList(ListCreateAPIView):
model = WorkflowJob
serializer_class = WorkflowJobListSerializer
def get(self, request, *args, **kwargs):
if not request.user.is_superuser and not request.user.is_system_auditor:
raise PermissionDenied("Superuser privileges needed.")
return super(WorkflowJobList, self).get(request, *args, **kwargs)
# TODO:
class WorkflowJobDetail(RetrieveDestroyAPIView):
model = WorkflowJob
serializer_class = WorkflowJobSerializer
class SystemJobTemplateList(ListAPIView):
model = SystemJobTemplate