mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
when copying workflows w/ pause nodes, copy the WorkflowApprovalTemplate
This commit is contained in:
@@ -34,7 +34,8 @@ from rest_framework.negotiation import DefaultContentNegotiation
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.api.filters import FieldLookupBackend
|
from awx.api.filters import FieldLookupBackend
|
||||||
from awx.main.models import (
|
from awx.main.models import (
|
||||||
UnifiedJob, UnifiedJobTemplate, User, Role, Credential
|
UnifiedJob, UnifiedJobTemplate, User, Role, Credential,
|
||||||
|
WorkflowJobTemplateNode, WorkflowApprovalTemplate
|
||||||
)
|
)
|
||||||
from awx.main.access import access_registry
|
from awx.main.access import access_registry
|
||||||
from awx.main.utils import (
|
from awx.main.utils import (
|
||||||
@@ -882,6 +883,21 @@ class CopyAPIView(GenericAPIView):
|
|||||||
create_kwargs[field.name] = CopyAPIView._decrypt_model_field_if_needed(
|
create_kwargs[field.name] = CopyAPIView._decrypt_model_field_if_needed(
|
||||||
obj, field.name, field_val
|
obj, field.name, field_val
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# WorkflowJobTemplateNodes that represent an approval are *special*;
|
||||||
|
# when we copy them, we actually want to *copy* the UJT they point at
|
||||||
|
# rather than share the template reference between nodes in disparate
|
||||||
|
# workflows
|
||||||
|
if (
|
||||||
|
isinstance(obj, WorkflowJobTemplateNode) and
|
||||||
|
isinstance(getattr(obj, 'unified_job_template'), WorkflowApprovalTemplate)
|
||||||
|
):
|
||||||
|
new_approval_template, sub_objs = CopyAPIView.copy_model_obj(
|
||||||
|
None, None, WorkflowApprovalTemplate,
|
||||||
|
obj.unified_job_template, creater
|
||||||
|
)
|
||||||
|
create_kwargs['unified_job_template'] = new_approval_template
|
||||||
|
|
||||||
new_obj = model.objects.create(**create_kwargs)
|
new_obj = model.objects.create(**create_kwargs)
|
||||||
logger.debug('Deep copy: Created new object {}({})'.format(
|
logger.debug('Deep copy: Created new object {}({})'.format(
|
||||||
new_obj, model
|
new_obj, model
|
||||||
|
|||||||
@@ -2016,7 +2016,7 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
|||||||
if self.user not in cred.use_role:
|
if self.user not in cred.use_role:
|
||||||
missing_credentials.append(cred.name)
|
missing_credentials.append(cred.name)
|
||||||
ujt = node.unified_job_template
|
ujt = node.unified_job_template
|
||||||
if ujt and not isinstance(ujt, WorkflowApprovalTemplate) and not self.user.can_access(UnifiedJobTemplate, 'start', ujt, validate_license=False):
|
if ujt and not self.user.can_access(UnifiedJobTemplate, 'start', ujt, validate_license=False):
|
||||||
missing_ujt.append(ujt.name)
|
missing_ujt.append(ujt.name)
|
||||||
if missing_ujt:
|
if missing_ujt:
|
||||||
self.messages['templates_unable_to_copy'] = missing_ujt
|
self.messages['templates_unable_to_copy'] = missing_ujt
|
||||||
@@ -2829,6 +2829,13 @@ class WorkflowApprovalTemplateAccess(BaseAccess):
|
|||||||
else:
|
else:
|
||||||
return (self.check_related('workflow_approval_template', UnifiedJobTemplate, role_field='admin_role'))
|
return (self.check_related('workflow_approval_template', UnifiedJobTemplate, role_field='admin_role'))
|
||||||
|
|
||||||
|
def can_start(self, obj, validate_license=False):
|
||||||
|
# Super users can start any job
|
||||||
|
if self.user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.user in obj.workflow_job_template.execute_role
|
||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return self.model.objects.filter(
|
return self.model.objects.filter(
|
||||||
workflowjobtemplatenodes__workflow_job_template__in=WorkflowJobTemplate.accessible_pk_qs(
|
workflowjobtemplatenodes__workflow_job_template__in=WorkflowJobTemplate.accessible_pk_qs(
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
|
|||||||
|
|
||||||
|
|
||||||
class WorkflowApprovalTemplate(UnifiedJobTemplate):
|
class WorkflowApprovalTemplate(UnifiedJobTemplate):
|
||||||
|
|
||||||
|
FIELDS_TO_PRESERVE_AT_COPY = ['description', 'timeout',]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ from unittest import mock
|
|||||||
|
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
from awx.main.utils import decrypt_field
|
from awx.main.utils import decrypt_field
|
||||||
from awx.main.models.workflow import WorkflowJobTemplateNode
|
from awx.main.models.workflow import (
|
||||||
|
WorkflowJobTemplate, WorkflowJobTemplateNode, WorkflowApprovalTemplate
|
||||||
|
)
|
||||||
from awx.main.models.jobs import JobTemplate
|
from awx.main.models.jobs import JobTemplate
|
||||||
from awx.main.tasks import deep_copy_model_obj
|
from awx.main.tasks import deep_copy_model_obj
|
||||||
|
|
||||||
@@ -175,6 +177,76 @@ def test_workflow_job_template_copy(workflow_job_template, post, get, admin, org
|
|||||||
assert copied_node_list[4] in copied_node_list[3].failure_nodes.all()
|
assert copied_node_list[4] in copied_node_list[3].failure_nodes.all()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_workflow_approval_node_copy(workflow_job_template, post, get, admin, organization):
|
||||||
|
workflow_job_template.organization = organization
|
||||||
|
workflow_job_template.save()
|
||||||
|
ajts = [
|
||||||
|
WorkflowApprovalTemplate.objects.create(
|
||||||
|
name='test-approval-{}'.format(i),
|
||||||
|
description='description-{}'.format(i),
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
for i in range(0, 5)
|
||||||
|
]
|
||||||
|
nodes = [
|
||||||
|
WorkflowJobTemplateNode.objects.create(
|
||||||
|
workflow_job_template=workflow_job_template, unified_job_template=ajts[i]
|
||||||
|
) for i in range(0, 5)
|
||||||
|
]
|
||||||
|
nodes[0].success_nodes.add(nodes[1])
|
||||||
|
nodes[1].success_nodes.add(nodes[2])
|
||||||
|
nodes[0].failure_nodes.add(nodes[3])
|
||||||
|
nodes[3].failure_nodes.add(nodes[4])
|
||||||
|
assert WorkflowJobTemplate.objects.count() == 1
|
||||||
|
assert WorkflowJobTemplateNode.objects.count() == 5
|
||||||
|
assert WorkflowApprovalTemplate.objects.count() == 5
|
||||||
|
|
||||||
|
with mock.patch('awx.api.generics.trigger_delayed_deep_copy') as deep_copy_mock:
|
||||||
|
wfjt_copy_id = post(
|
||||||
|
reverse('api:workflow_job_template_copy', kwargs={'pk': workflow_job_template.pk}),
|
||||||
|
{'name': 'new wfjt name'}, admin, expect=201
|
||||||
|
).data['id']
|
||||||
|
wfjt_copy = type(workflow_job_template).objects.get(pk=wfjt_copy_id)
|
||||||
|
args, kwargs = deep_copy_mock.call_args
|
||||||
|
deep_copy_model_obj(*args, **kwargs)
|
||||||
|
assert wfjt_copy.organization == organization
|
||||||
|
assert wfjt_copy.created_by == admin
|
||||||
|
assert wfjt_copy.name == 'new wfjt name'
|
||||||
|
|
||||||
|
assert WorkflowJobTemplate.objects.count() == 2
|
||||||
|
assert WorkflowJobTemplateNode.objects.count() == 10
|
||||||
|
assert WorkflowApprovalTemplate.objects.count() == 10
|
||||||
|
original_templates = [
|
||||||
|
x.unified_job_template for x in workflow_job_template.workflow_job_template_nodes.all()
|
||||||
|
]
|
||||||
|
copied_templates = [
|
||||||
|
x.unified_job_template for x in wfjt_copy.workflow_job_template_nodes.all()
|
||||||
|
]
|
||||||
|
|
||||||
|
# make sure shallow fields like `timeout` are copied properly
|
||||||
|
for i, t in enumerate(original_templates):
|
||||||
|
assert t.timeout == 30
|
||||||
|
assert t.description == 'description-{}'.format(i)
|
||||||
|
|
||||||
|
for i, t in enumerate(copied_templates):
|
||||||
|
assert t.timeout == 30
|
||||||
|
assert t.description == 'description-{}'.format(i)
|
||||||
|
|
||||||
|
# the Approval Template IDs on the *original* WFJT should not match *any*
|
||||||
|
# of the Approval Template IDs on the *copied* WFJT
|
||||||
|
assert not set([x.id for x in original_templates]).intersection(
|
||||||
|
set([x.id for x in copied_templates])
|
||||||
|
)
|
||||||
|
|
||||||
|
# if you remove the " copy" suffix from the copied template names, they
|
||||||
|
# should match the original templates
|
||||||
|
assert (
|
||||||
|
set([x.name for x in original_templates]) ==
|
||||||
|
set([x.name.replace(' copy', '') for x in copied_templates])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_credential_copy(post, get, machine_credential, credentialtype_ssh, admin):
|
def test_credential_copy(post, get, machine_credential, credentialtype_ssh, admin):
|
||||||
assert get(
|
assert get(
|
||||||
|
|||||||
Reference in New Issue
Block a user