mirror of
https://github.com/ansible/awx.git
synced 2026-09-16 00:40:11 -02:30
Add workflow node identifier
Generate new modules WFJT and WFJT node Touch up generated syntax, test new modules Add utility method in awxkit Fix some issues with non-name identifier in AWX collection module_utils Update workflow docs for workflow node identifier Test and fix WFJT modules survey_spec Plug in survey spec for the new module Handle survey spec idempotency and test add associations for node connections Handle node credential prompts as well Add indexes for new identifier field Test with unicode dragon in name
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# 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', 'workflow_job_template'], name='main_workfl_identif_6fda75_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='workflowjobtemplatenode',
|
||||
index=models.Index(fields=['identifier'], name='main_workfl_identif_0cc025_idx'),
|
||||
),
|
||||
]
|
||||
@@ -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,22 @@ 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", "workflow_job_template"]),
|
||||
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 +231,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)
|
||||
|
||||
@@ -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: [])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user