Merge pull request #6291 from AlanCoding/node_identifier

Add Workflow Node Identifier

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-03-24 19:32:52 +00:00
committed by GitHub
18 changed files with 952 additions and 52 deletions

View File

@@ -3683,7 +3683,8 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer):
class Meta:
model = WorkflowJobTemplateNode
fields = ('*', 'workflow_job_template', '-name', '-description', 'id', 'url', 'related',
'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'all_parents_must_converge',)
'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'all_parents_must_converge',
'identifier',)
def get_related(self, obj):
res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj)
@@ -3723,7 +3724,7 @@ class WorkflowJobNodeSerializer(LaunchConfigurationBaseSerializer):
model = WorkflowJobNode
fields = ('*', 'job', 'workflow_job', '-name', '-description', 'id', 'url', 'related',
'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',
'all_parents_must_converge', 'do_not_run',)
'all_parents_must_converge', 'do_not_run', 'identifier')
def get_related(self, obj):
res = super(WorkflowJobNodeSerializer, self).get_related(obj)

View File

@@ -0,0 +1,61 @@
# Generated by Django 2.2.8 on 2020-03-14 02:29
from django.db import migrations, models
import uuid
import logging
logger = logging.getLogger('awx.main.migrations')
def create_uuid(apps, schema_editor):
WorkflowJobTemplateNode = apps.get_model('main', 'WorkflowJobTemplateNode')
ct = 0
for node in WorkflowJobTemplateNode.objects.iterator():
node.identifier = uuid.uuid4()
node.save(update_fields=['identifier'])
ct += 1
if ct:
logger.info(f'Automatically created uuid4 identifier for {ct} workflow nodes')
class Migration(migrations.Migration):
dependencies = [
('main', '0111_v370_delete_channelgroup'),
]
operations = [
migrations.AddField(
model_name='workflowjobnode',
name='identifier',
field=models.CharField(blank=True, help_text='An identifier coresponding to the workflow job template node that this node was created from.', max_length=512),
),
migrations.AddField(
model_name='workflowjobtemplatenode',
name='identifier',
field=models.CharField(blank=True, null=True, help_text='An identifier for this node that is unique within its workflow. It is copied to workflow job nodes corresponding to this node.', max_length=512),
),
migrations.RunPython(create_uuid, migrations.RunPython.noop), # this fixes the uuid4 issue
migrations.AlterField(
model_name='workflowjobtemplatenode',
name='identifier',
field=models.CharField(default=uuid.uuid4, help_text='An identifier for this node that is unique within its workflow. It is copied to workflow job nodes corresponding to this node.', max_length=512),
),
migrations.AlterUniqueTogether(
name='workflowjobtemplatenode',
unique_together={('identifier', 'workflow_job_template')},
),
migrations.AddIndex(
model_name='workflowjobnode',
index=models.Index(fields=['identifier', 'workflow_job'], name='main_workfl_identif_87b752_idx'),
),
migrations.AddIndex(
model_name='workflowjobnode',
index=models.Index(fields=['identifier'], name='main_workfl_identif_efdfe8_idx'),
),
migrations.AddIndex(
model_name='workflowjobtemplatenode',
index=models.Index(fields=['identifier'], name='main_workfl_identif_0cc025_idx'),
),
]

View File

@@ -4,6 +4,7 @@
# Python
import json
import logging
from uuid import uuid4
from copy import copy
from urllib.parse import urljoin
@@ -121,6 +122,7 @@ class WorkflowNodeBase(CreatedModifiedModel, LaunchTimeConfig):
create_kwargs[field_name] = kwargs[field_name]
elif hasattr(self, field_name):
create_kwargs[field_name] = getattr(self, field_name)
create_kwargs['identifier'] = self.identifier
new_node = WorkflowJobNode.objects.create(**create_kwargs)
if self.pk:
allowed_creds = self.credentials.all()
@@ -135,7 +137,7 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
FIELDS_TO_PRESERVE_AT_COPY = [
'unified_job_template', 'workflow_job_template', 'success_nodes', 'failure_nodes',
'always_nodes', 'credentials', 'inventory', 'extra_data', 'survey_passwords',
'char_prompts', 'all_parents_must_converge'
'char_prompts', 'all_parents_must_converge', 'identifier'
]
REENCRYPTION_BLACKLIST_AT_COPY = ['extra_data', 'survey_passwords']
@@ -144,6 +146,21 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
related_name='workflow_job_template_nodes',
on_delete=models.CASCADE,
)
identifier = models.CharField(
max_length=512,
default=uuid4,
blank=False,
help_text=_(
'An identifier for this node that is unique within its workflow. '
'It is copied to workflow job nodes corresponding to this node.'),
)
class Meta:
app_label = 'main'
unique_together = (("identifier", "workflow_job_template"),)
indexes = [
models.Index(fields=['identifier']),
]
def get_absolute_url(self, request=None):
return reverse('api:workflow_job_template_node_detail', kwargs={'pk': self.pk}, request=request)
@@ -213,6 +230,18 @@ class WorkflowJobNode(WorkflowNodeBase):
"semantics will mark this True if the node is in a path that will "
"decidedly not be ran. A value of False means the node may not run."),
)
identifier = models.CharField(
max_length=512,
blank=True, # blank denotes pre-migration job nodes
help_text=_('An identifier coresponding to the workflow job template node that this node was created from.'),
)
class Meta:
app_label = 'main'
indexes = [
models.Index(fields=["identifier", "workflow_job"]),
models.Index(fields=['identifier']),
]
def get_absolute_url(self, request=None):
return reverse('api:workflow_job_node_detail', kwargs={'pk': self.pk}, request=request)

View File

@@ -177,7 +177,8 @@ class TestWorkflowJobCreate:
char_prompts=wfjt_node_no_prompts.char_prompts,
inventory=None,
unified_job_template=wfjt_node_no_prompts.unified_job_template,
workflow_job=workflow_job_unit)
workflow_job=workflow_job_unit,
identifier=mocker.ANY)
def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, credential, mocker):
mock_create = mocker.MagicMock()
@@ -192,7 +193,8 @@ class TestWorkflowJobCreate:
char_prompts=wfjt_node_with_prompts.char_prompts,
inventory=wfjt_node_with_prompts.inventory,
unified_job_template=wfjt_node_with_prompts.unified_job_template,
workflow_job=workflow_job_unit)
workflow_job=workflow_job_unit,
identifier=mocker.ANY)
@mock.patch('awx.main.models.workflow.WorkflowNodeBase.get_parent_nodes', lambda self: [])

View File

@@ -77,6 +77,8 @@ class GraphNode(object):
Performance assured: http://stackoverflow.com/a/27086669
'''
for c in URL_PATH_RESERVED_CHARSET:
if not isinstance(text, str):
text = str(text) # needed for WFJT node creation, identifier temporarily UUID4 type
if c in text:
text = text.replace(c, URL_PATH_RESERVED_CHARSET[c])
text = text.replace(NAMED_URL_RES_INNER_DILIMITER,
@@ -200,14 +202,14 @@ def _get_all_unique_togethers(model):
def _check_unique_together_fields(model, ut):
has_name = False
name_field = None
fk_names = []
fields = []
is_valid = True
for field_name in ut:
field = model._meta.get_field(field_name)
if field_name == 'name':
has_name = True
if field_name in ('name', 'identifier'):
name_field = field_name
elif type(field) == models.ForeignKey and field.related_model != model:
fk_names.append(field_name)
elif issubclass(type(field), models.CharField) and field.choices:
@@ -219,8 +221,8 @@ def _check_unique_together_fields(model, ut):
return (), (), is_valid
fk_names.sort()
fields.sort(reverse=True)
if has_name:
fields.append('name')
if name_field:
fields.append(name_field)
fields.reverse()
return tuple(fk_names), tuple(fields), is_valid