mirror of
https://github.com/ansible/awx.git
synced 2026-01-23 23:41:23 -03:30
Add API endpoints for workflow approvals
This commit is contained in:
parent
72a65f74fd
commit
9024a514a6
@ -50,16 +50,16 @@ from awx.main.constants import (
|
||||
CENSOR_VALUE,
|
||||
)
|
||||
from awx.main.models import (
|
||||
ActivityStream, AdHocCommand, AdHocCommandEvent, Credential,
|
||||
CredentialInputSource, CredentialType, CustomInventoryScript,
|
||||
Group, Host, Instance, InstanceGroup, Inventory, InventorySource,
|
||||
InventoryUpdate, InventoryUpdateEvent, Job, JobEvent, JobHostSummary,
|
||||
JobLaunchConfig, JobNotificationMixin, JobTemplate, Label, Notification,
|
||||
NotificationTemplate, OAuth2AccessToken, OAuth2Application, Organization,
|
||||
Project, ProjectUpdate, ProjectUpdateEvent, RefreshToken, Role, Schedule,
|
||||
StdoutMaxBytesExceeded, SystemJob, SystemJobEvent, SystemJobTemplate,
|
||||
Team, UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode,
|
||||
WorkflowJobTemplate, WorkflowJobTemplateNode
|
||||
ActivityStream, AdHocCommand, AdHocCommandEvent, Credential, CredentialInputSource,
|
||||
CredentialType, CustomInventoryScript, Group, Host, Instance,
|
||||
InstanceGroup, Inventory, InventorySource, InventoryUpdate,
|
||||
InventoryUpdateEvent, Job, JobEvent, JobHostSummary, JobLaunchConfig,
|
||||
JobNotificationMixin, JobTemplate, Label, Notification, NotificationTemplate,
|
||||
OAuth2AccessToken, OAuth2Application, Organization, Project,
|
||||
ProjectUpdate, ProjectUpdateEvent, RefreshToken, Role, Schedule,
|
||||
SystemJob, SystemJobEvent, SystemJobTemplate, Team, UnifiedJob,
|
||||
UnifiedJobTemplate, WorkflowApproval, WorkflowApprovalTemplate, WorkflowJob,
|
||||
WorkflowJobNode, WorkflowJobTemplate, WorkflowJobTemplateNode, StdoutMaxBytesExceeded
|
||||
)
|
||||
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
|
||||
from awx.main.models.rbac import (
|
||||
@ -681,6 +681,8 @@ class UnifiedJobTemplateSerializer(BaseSerializer):
|
||||
serializer_class = SystemJobTemplateSerializer
|
||||
elif isinstance(obj, WorkflowJobTemplate):
|
||||
serializer_class = WorkflowJobTemplateSerializer
|
||||
elif isinstance(obj, WorkflowApprovalTemplate):
|
||||
serializer_class = WorkflowApprovalTemplateSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@ -782,6 +784,8 @@ class UnifiedJobSerializer(BaseSerializer):
|
||||
serializer_class = SystemJobSerializer
|
||||
elif isinstance(obj, WorkflowJob):
|
||||
serializer_class = WorkflowJobSerializer
|
||||
elif isinstance(obj, WorkflowApproval):
|
||||
serializer_class = WorkflowApprovalSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@ -838,6 +842,8 @@ class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
serializer_class = SystemJobListSerializer
|
||||
elif isinstance(obj, WorkflowJob):
|
||||
serializer_class = WorkflowJobListSerializer
|
||||
elif isinstance(obj, WorkflowApproval):
|
||||
serializer_class = WorkflowApprovalListSerializer
|
||||
return serializer_class
|
||||
|
||||
def to_representation(self, obj):
|
||||
@ -3395,6 +3401,51 @@ class WorkflowJobCancelSerializer(WorkflowJobSerializer):
|
||||
fields = ('can_cancel',)
|
||||
|
||||
|
||||
class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApproval
|
||||
fields = ('*', 'result_stdout', '-controller_node', '-execution_node',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(WorkflowApprovalSerializer, self).get_related(obj)
|
||||
|
||||
if obj.workflow_approval_template:
|
||||
res['workflow_approval_template'] = self.reverse('api:workflow_approval_template_detail',
|
||||
kwargs={'pk': obj.workflow_approval_template.pk})
|
||||
res['notifications'] = self.reverse('api:workflow_approval_notifications_list', kwargs={'pk': obj.pk})
|
||||
return res
|
||||
|
||||
def get_result_stdout(self, obj):
|
||||
return obj.result_stdout
|
||||
|
||||
|
||||
class WorkflowApprovalListSerializer(WorkflowApprovalSerializer, UnifiedJobListSerializer):
|
||||
|
||||
class Meta:
|
||||
fields = ('*', '-execution_node', '-controller_node',)
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WorkflowApprovalTemplate
|
||||
fields = ('*',)
|
||||
|
||||
def get_related(self, obj):
|
||||
res = super(WorkflowApprovalTemplateSerializer, self).get_related(obj)
|
||||
if 'last_job' in res:
|
||||
del res['last_job']
|
||||
|
||||
res.update(dict(
|
||||
jobs = self.reverse('api:workflow_approval_template_jobs_list', kwargs={'pk': obj.pk}),
|
||||
notification_templates_started = self.reverse('api:workflow_approval_template_notification_templates_started_list', kwargs={'pk': obj.pk}),
|
||||
notification_templates_success = self.reverse('api:workflow_approval_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
|
||||
notification_templates_error = self.reverse('api:workflow_approval_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
|
||||
))
|
||||
return res
|
||||
|
||||
|
||||
class LaunchConfigurationBaseSerializer(BaseSerializer):
|
||||
scm_branch = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
|
||||
job_type = serializers.ChoiceField(allow_blank=True, allow_null=True, required=False, default=None,
|
||||
|
||||
@ -71,6 +71,8 @@ from .instance import urls as instance_urls
|
||||
from .instance_group import urls as instance_group_urls
|
||||
from .oauth2 import urls as oauth2_urls
|
||||
from .oauth2_root import urls as oauth2_root_urls
|
||||
from .workflow_approval_template import urls as workflow_approval_template_urls
|
||||
from .workflow_approval import urls as workflow_approval_urls
|
||||
|
||||
|
||||
v2_urls = [
|
||||
@ -131,8 +133,11 @@ v2_urls = [
|
||||
url(r'^unified_job_templates/$', UnifiedJobTemplateList.as_view(), name='unified_job_template_list'),
|
||||
url(r'^unified_jobs/$', UnifiedJobList.as_view(), name='unified_job_list'),
|
||||
url(r'^activity_stream/', include(activity_stream_urls)),
|
||||
url(r'^workflow_approval_templates/', include(workflow_approval_template_urls)),
|
||||
url(r'^workflow_approval/', include(workflow_approval_urls)),
|
||||
]
|
||||
|
||||
|
||||
app_name = 'api'
|
||||
urlpatterns = [
|
||||
url(r'^$', ApiRootView.as_view(), name='api_root_view'),
|
||||
|
||||
21
awx/api/urls/workflow_approval.py
Normal file
21
awx/api/urls/workflow_approval.py
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2017 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from awx.api.views import (
|
||||
WorkflowApprovalList,
|
||||
WorkflowApprovalDetail,
|
||||
WorkflowApprovalNotificationsList,
|
||||
)
|
||||
|
||||
|
||||
urls = [
|
||||
url(r'^$', WorkflowApprovalList.as_view(), name='workflow_approval_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', WorkflowApprovalDetail.as_view(), name='workflow_approval_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/approve/$', WorkflowApprovalDetail.as_view(), name='approved_workflow'),
|
||||
url(r'^(?P<pk>[0-9]+)/reject/$', WorkflowApprovalDetail.as_view(), name='rejected_workflow'),
|
||||
url(r'^(?P<pk>[0-9]+)/notifications/$', WorkflowApprovalNotificationsList.as_view(), name='workflow_approval_notifications_list'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
28
awx/api/urls/workflow_approval_template.py
Normal file
28
awx/api/urls/workflow_approval_template.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2017 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from awx.api.views import (
|
||||
WorfklowApprovalTemplateList,
|
||||
WorfklowApprovalTemplateDetail,
|
||||
WorkflowApprovalTemplateJobsList,
|
||||
WorkflowApprovalTemplateNotificationTemplatesErrorList,
|
||||
WorkflowApprovalTemplateNotificationTemplatesStartedList,
|
||||
WorkflowApprovalTemplateNotificationTemplatesSuccessList,
|
||||
)
|
||||
|
||||
|
||||
urls = [
|
||||
url(r'^$', WorfklowApprovalTemplateList.as_view(), name='workflow_approval_template_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', WorfklowApprovalTemplateDetail.as_view(), name='workflow_approval_template_detail'),
|
||||
url(r'^(?P<pk>[0-9]+)/approvals/$', WorkflowApprovalTemplateJobsList.as_view(), name='workflow_approval_template_jobs_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', WorkflowApprovalTemplateNotificationTemplatesStartedList.as_view(),
|
||||
name='workflow_approval_template_notification_templates_started_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', WorkflowApprovalTemplateNotificationTemplatesErrorList.as_view(),
|
||||
name='workflow_approval_template_notification_templates_error_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', WorkflowApprovalTemplateNotificationTemplatesSuccessList.as_view(),
|
||||
name='workflow_approval_template_notification_templates_success_list'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
@ -4405,3 +4405,97 @@ for attr, value in list(locals().items()):
|
||||
name = camelcase_to_underscore(attr)
|
||||
view = value.as_view()
|
||||
setattr(this_module, name, view)
|
||||
|
||||
|
||||
class WorfklowApprovalTemplateList(ListAPIView):
|
||||
|
||||
model = models.WorkflowApprovalTemplate
|
||||
serializer_class = serializers.WorkflowApprovalTemplateSerializer
|
||||
|
||||
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(WorfklowApprovalTemplateList, self).get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WorfklowApprovalTemplateDetail(RetrieveAPIView):
|
||||
|
||||
model = models.WorkflowApprovalTemplate
|
||||
serializer_class = serializers.WorkflowApprovalTemplateSerializer
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = models.NotificationTemplate
|
||||
serializer_class = serializers.NotificationTemplateSerializer
|
||||
parent_model = models.WorkflowApprovalTemplate
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateNotificationTemplatesStartedList(WorkflowApprovalTemplateNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_started'
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateNotificationTemplatesErrorList(WorkflowApprovalTemplateNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_error'
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateNotificationTemplatesSuccessList(WorkflowApprovalTemplateNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_success'
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateLaunch(GenericAPIView):
|
||||
|
||||
model = models.WorkflowApprovalTemplate
|
||||
obj_permission_type = 'start'
|
||||
serializer_class = serializers.EmptySerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
return Response({})
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
new_job = obj.create_unified_job(extra_vars=request.data.get('extra_vars', {}))
|
||||
new_job.signal_start()
|
||||
data = OrderedDict()
|
||||
data['workflow_approval'] = new_job.id
|
||||
data.update(serializers.WorkflowApprovalSerializer(new_job, context=self.get_serializer_context()).to_representation(new_job))
|
||||
headers = {'Location': new_job.get_absolute_url(request)}
|
||||
return Response(data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateJobsList(SubListAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalListSerializer
|
||||
parent_model = models.WorkflowApprovalTemplate
|
||||
relationship = 'approvals'
|
||||
parent_key = 'workflow_approval_template'
|
||||
|
||||
|
||||
class WorkflowApprovalList(ListCreateAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalListSerializer
|
||||
|
||||
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(WorkflowApprovalList, self).get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WorkflowApprovalDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
||||
|
||||
model = models.WorkflowApproval
|
||||
serializer_class = serializers.WorkflowApprovalSerializer
|
||||
|
||||
|
||||
class WorkflowApprovalNotificationsList(SubListAPIView):
|
||||
|
||||
model = models.Notification
|
||||
serializer_class = serializers.NotificationSerializer
|
||||
parent_model = models.WorkflowApproval
|
||||
relationship = 'notifications'
|
||||
search_fields = ('subject', 'notification_type', 'body',)
|
||||
|
||||
@ -124,6 +124,8 @@ class ApiVersionRootView(APIView):
|
||||
data['activity_stream'] = reverse('api:activity_stream_list', request=request)
|
||||
data['workflow_job_templates'] = reverse('api:workflow_job_template_list', request=request)
|
||||
data['workflow_jobs'] = reverse('api:workflow_job_list', request=request)
|
||||
data['workflow_approval_templates'] = reverse('api:workflow_approval_template_list', request=request)
|
||||
data['workflow_approval'] = reverse('api:workflow_approval_list', request=request)
|
||||
data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request)
|
||||
data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request)
|
||||
return Response(data)
|
||||
|
||||
@ -37,6 +37,7 @@ from awx.main.models import (
|
||||
ProjectUpdateEvent, Role, Schedule, SystemJob, SystemJobEvent,
|
||||
SystemJobTemplate, Team, UnifiedJob, UnifiedJobTemplate, WorkflowJob,
|
||||
WorkflowJobNode, WorkflowJobTemplate, WorkflowJobTemplateNode,
|
||||
WorkflowApproval, WorkflowApprovalTemplate,
|
||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR
|
||||
)
|
||||
from awx.main.models.mixins import ResourceMixin
|
||||
@ -2769,5 +2770,50 @@ class RoleAccess(BaseAccess):
|
||||
return False
|
||||
|
||||
|
||||
class WorkflowApprovalAccess(BaseAccess):
|
||||
'''
|
||||
I can approve workflows when:
|
||||
- I'm authenticated
|
||||
I can create when:
|
||||
- I'm a superuser:
|
||||
'''
|
||||
|
||||
model = WorkflowApproval
|
||||
prefetch_related = ('created_by', 'modified_by',)
|
||||
|
||||
def can_read(self, obj):
|
||||
return True
|
||||
|
||||
def can_use(self, obj):
|
||||
return True
|
||||
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.all()
|
||||
|
||||
def can_start(self, obj, validate_license=True):
|
||||
return False
|
||||
|
||||
|
||||
class WorkflowApprovalTemplateAccess(BaseAccess):
|
||||
'''
|
||||
I can approve workflows when:
|
||||
- I'm authenticated
|
||||
I can create when:
|
||||
- I'm a superuser:
|
||||
'''
|
||||
|
||||
model = WorkflowApprovalTemplate
|
||||
prefetch_related = ('created_by', 'modified_by',)
|
||||
|
||||
def can_read(self, obj):
|
||||
return True
|
||||
|
||||
def can_use(self, obj):
|
||||
return True
|
||||
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.all()
|
||||
|
||||
|
||||
for cls in BaseAccess.__subclasses__():
|
||||
access_registry[cls.model] = cls
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-07-01 19:41
|
||||
# Generated by Django 1.11.20 on 2019-07-03 14:38
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
@ -33,4 +33,9 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
bases=('main.unifiedjobtemplate',),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='workflowapproval',
|
||||
name='workflow_approval_template',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='approvals', to='main.WorkflowApprovalTemplate'),
|
||||
),
|
||||
]
|
||||
|
||||
@ -55,7 +55,7 @@ from awx.main.models.notifications import ( # noqa
|
||||
from awx.main.models.label import Label # noqa
|
||||
from awx.main.models.workflow import ( # noqa
|
||||
WorkflowJob, WorkflowJobNode, WorkflowJobOptions, WorkflowJobTemplate,
|
||||
WorkflowJobTemplateNode,
|
||||
WorkflowJobTemplateNode, WorkflowApproval, WorkflowApprovalTemplate,
|
||||
)
|
||||
from awx.main.models.channels import ChannelGroup # noqa
|
||||
from awx.api.versioning import reverse
|
||||
@ -212,4 +212,3 @@ prevent_search(RefreshToken._meta.get_field('token'))
|
||||
prevent_search(OAuth2Application._meta.get_field('client_secret'))
|
||||
prevent_search(OAuth2Application._meta.get_field('client_id'))
|
||||
prevent_search(Grant._meta.get_field('code'))
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ from awx.main.models.jobs import LaunchTimeConfigBase, LaunchTimeConfig, JobTemp
|
||||
from awx.main.models.credential import Credential
|
||||
from awx.main.redact import REPLACE_STR
|
||||
from awx.main.fields import JSONField
|
||||
from awx.main.utils import schedule_task_manager
|
||||
|
||||
from copy import copy
|
||||
from urllib.parse import urljoin
|
||||
@ -604,7 +605,6 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
|
||||
|
||||
|
||||
class WorkflowApprovalTemplate(UnifiedJobTemplate):
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
@ -616,8 +616,42 @@ class WorkflowApprovalTemplate(UnifiedJobTemplate):
|
||||
def _get_unified_job_field_names(cls):
|
||||
return ['name', 'description']
|
||||
|
||||
def get_absolute_url(self, request=None):
|
||||
return reverse('api:workflow_approval_template_detail', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
|
||||
class WorkflowApproval(UnifiedJob):
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
|
||||
workflow_approval_template = models.ForeignKey(
|
||||
'WorkflowApprovalTemplate',
|
||||
related_name='approvals',
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_unified_job_template_class(cls):
|
||||
return WorkflowApprovalTemplate
|
||||
|
||||
def get_absolute_url(self, request=None):
|
||||
return reverse('api:workflow_approval_detail', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
@property
|
||||
def event_class(self):
|
||||
return None
|
||||
|
||||
def approve(self, request=None):
|
||||
self.status = 'successful'
|
||||
self.save()
|
||||
schedule_task_manager()
|
||||
return reverse('api:approved_workflow', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
def reject(self, request=None):
|
||||
self.status = 'failed'
|
||||
self.save()
|
||||
schedule_task_manager()
|
||||
return reverse('api:rejected_workflow', kwargs={'pk': self.pk}, request=request)
|
||||
|
||||
@ -10,7 +10,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
# AWX
|
||||
from awx.main.models import (
|
||||
UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate,
|
||||
Project, WorkflowJob, Schedule,
|
||||
WorkflowApprovalTemplate, Project, WorkflowJob, Schedule,
|
||||
Credential
|
||||
)
|
||||
|
||||
@ -20,7 +20,9 @@ def test_subclass_types(rando):
|
||||
assert set(UnifiedJobTemplate._submodels_with_roles()) == set([
|
||||
ContentType.objects.get_for_model(JobTemplate).id,
|
||||
ContentType.objects.get_for_model(Project).id,
|
||||
ContentType.objects.get_for_model(WorkflowJobTemplate).id
|
||||
ContentType.objects.get_for_model(WorkflowJobTemplate).id,
|
||||
ContentType.objects.get_for_model(WorkflowApprovalTemplate).id
|
||||
|
||||
])
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user