summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSarabraj Singh <sarsingh@redhat.com>2022-08-03 20:27:35 +0200
committerAlan Rominger <arominge@redhat.com>2022-09-22 21:18:47 +0200
commit663ef2cc6413c0cdb26392bb046b37fe564fb546 (patch)
treef02e61637bbc0e1a1b2f0ae74fca43a7ce710e50
parentChange ask_job_slicing_on_launch to ask_job_slice_count_on_launch to match api (diff)
downloadawx-663ef2cc6413c0cdb26392bb046b37fe564fb546.tar.xz
awx-663ef2cc6413c0cdb26392bb046b37fe564fb546.zip
adding prompt-to-launch field on Labels field in Workflow Templates; with necessary UI and testing changes
Co-authored-by: Keith Grant <keithjgrant@gmail.com>
-rw-r--r--awx/api/serializers.py47
-rw-r--r--awx/api/views/__init__.py14
-rw-r--r--awx/main/migrations/0167_jt_prompt_everything_on_launch.py16
-rw-r--r--awx/main/models/jobs.py18
-rw-r--r--awx/main/models/mixins.py27
-rw-r--r--awx/main/models/unified_jobs.py1
-rw-r--r--awx/main/models/workflow.py32
-rw-r--r--awx/main/tests/factories/fixtures.py2
-rw-r--r--awx/main/tests/functional/conftest.py2
-rw-r--r--awx/main/tests/functional/models/test_workflow.py96
-rw-r--r--awx/main/tests/unit/api/serializers/test_workflow_serializers.py11
-rw-r--r--awx/main/tests/unit/models/test_survey_models.py7
-rw-r--r--awx/main/tests/unit/models/test_workflow_unit.py14
-rw-r--r--awx/main/tests/unit/test_access.py1
-rw-r--r--awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js13
-rw-r--r--awx/ui/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.js6
-rw-r--r--awx/ui/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.js5
-rw-r--r--awx/ui/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.js9
-rw-r--r--awx/ui/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.js11
-rw-r--r--awx/ui/src/screens/Template/shared/WorkflowJobTemplate.helptext.js1
-rw-r--r--awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.js59
-rw-r--r--awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.test.js4
-rw-r--r--awx_collection/plugins/modules/workflow_job_template.py34
-rw-r--r--awx_collection/test/awx/test_workflow_job_template.py13
24 files changed, 365 insertions, 78 deletions
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index 01664f03fd..5d7b90ae51 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -3199,7 +3199,7 @@ class JobRelaunchSerializer(BaseSerializer):
return attrs
-class JobCreateScheduleSerializer(BaseSerializer):
+class JobCreateScheduleSerializer(LabelsListMixin, BaseSerializer):
can_schedule = serializers.SerializerMethodField()
prompts = serializers.SerializerMethodField()
@@ -3230,6 +3230,8 @@ class JobCreateScheduleSerializer(BaseSerializer):
if 'credentials' in ret:
all_creds = [self._summarize('credential', cred) for cred in ret['credentials']]
ret['credentials'] = all_creds
+ if 'labels' in ret:
+ ret['labels'] = self._summary_field_labels(obj)
return ret
except JobLaunchConfig.DoesNotExist:
return {'all': _('Unknown, job may have been ran before launch configurations were saved.')}
@@ -3402,6 +3404,9 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
scm_branch = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+ skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+ job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+
class Meta:
model = WorkflowJobTemplate
fields = (
@@ -3420,6 +3425,11 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
'webhook_service',
'webhook_credential',
'-execution_environment',
+ 'ask_labels_on_launch',
+ 'ask_skip_tags_on_launch',
+ 'ask_tags_on_launch',
+ 'skip_tags',
+ 'job_tags',
)
def get_related(self, obj):
@@ -3458,12 +3468,13 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
def validate_extra_vars(self, value):
return vars_validate_or_raise(value)
+ # posting
def validate(self, attrs):
attrs = super(WorkflowJobTemplateSerializer, self).validate(attrs)
# process char_prompts, these are not direct fields on the model
mock_obj = self.Meta.model()
- for field_name in ('scm_branch', 'limit'):
+ for field_name in ('scm_branch', 'limit', 'skip_tags', 'job_tags'):
if field_name in attrs:
setattr(mock_obj, field_name, attrs[field_name])
attrs.pop(field_name)
@@ -3489,6 +3500,9 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
scm_branch = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+ skip_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+ job_tags = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None)
+
class Meta:
model = WorkflowJob
fields = (
@@ -3508,6 +3522,8 @@ class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer):
'webhook_service',
'webhook_credential',
'webhook_guid',
+ 'skip_tags',
+ 'job_tags',
)
def get_related(self, obj):
@@ -4333,6 +4349,10 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
scm_branch = serializers.CharField(required=False, write_only=True, allow_blank=True)
workflow_job_template_data = serializers.SerializerMethodField()
+ labels = serializers.PrimaryKeyRelatedField(many=True, queryset=Label.objects.all(), required=False, write_only=True)
+ skip_tags = serializers.CharField(required=False, write_only=True, allow_blank=True)
+ job_tags = serializers.CharField(required=False, write_only=True, allow_blank=True)
+
class Meta:
model = WorkflowJobTemplate
fields = (
@@ -4352,8 +4372,22 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
'workflow_job_template_data',
'survey_enabled',
'ask_variables_on_launch',
+ 'ask_labels_on_launch',
+ 'labels',
+ 'ask_skip_tags_on_launch',
+ 'ask_tags_on_launch',
+ 'skip_tags',
+ 'job_tags',
+ )
+ read_only_fields = (
+ 'ask_inventory_on_launch',
+ 'ask_variables_on_launch',
+ 'ask_skip_tags_on_launch',
+ 'ask_labels_on_launch',
+ 'ask_limit_on_launch',
+ 'ask_scm_branch_on_launch',
+ 'ask_tags_on_launch',
)
- read_only_fields = ('ask_inventory_on_launch', 'ask_variables_on_launch')
def get_survey_enabled(self, obj):
if obj:
@@ -4361,10 +4395,15 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
return False
def get_defaults(self, obj):
+
defaults_dict = {}
for field_name in WorkflowJobTemplate.get_ask_mapping().keys():
if field_name == 'inventory':
defaults_dict[field_name] = dict(name=getattrd(obj, '%s.name' % field_name, None), id=getattrd(obj, '%s.pk' % field_name, None))
+ elif field_name == 'labels':
+ for label in obj.labels.all():
+ label_dict = {"id": label.id, "name": label.name}
+ defaults_dict.setdefault(field_name, []).append(label_dict)
else:
defaults_dict[field_name] = getattr(obj, field_name)
return defaults_dict
@@ -4373,6 +4412,7 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
return dict(name=obj.name, id=obj.id, description=obj.description)
def validate(self, attrs):
+
template = self.instance
accepted, rejected, errors = template._accept_or_ignore_job_kwargs(**attrs)
@@ -4390,6 +4430,7 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
WFJT_inventory = template.inventory
WFJT_limit = template.limit
WFJT_scm_branch = template.scm_branch
+
super(WorkflowJobLaunchSerializer, self).validate(attrs)
template.extra_vars = WFJT_extra_vars
template.inventory = WFJT_inventory
diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py
index 00d59484d8..012e320bc8 100644
--- a/awx/api/views/__init__.py
+++ b/awx/api/views/__init__.py
@@ -3197,13 +3197,17 @@ class WorkflowJobTemplateLaunch(RetrieveAPIView):
data['extra_vars'] = extra_vars
modified_ask_mapping = models.WorkflowJobTemplate.get_ask_mapping()
modified_ask_mapping.pop('extra_vars')
- for field_name, ask_field_name in obj.get_ask_mapping().items():
+
+ for field, ask_field_name in modified_ask_mapping.items():
if not getattr(obj, ask_field_name):
- data.pop(field_name, None)
- elif field_name == 'inventory':
- data[field_name] = getattrd(obj, "%s.%s" % (field_name, 'id'), None)
+ data.pop(field, None)
+ elif isinstance(getattr(obj.__class__, field).field, ForeignKey):
+ data[field] = getattrd(obj, "%s.%s" % (field, 'id'), None)
+ elif isinstance(getattr(obj.__class__, field).field, ManyToManyField):
+ data[field] = [item.id for item in getattr(obj, field).all()]
else:
- data[field_name] = getattr(obj, field_name)
+ data[field] = getattr(obj, field)
+
return data
def post(self, request, *args, **kwargs):
diff --git a/awx/main/migrations/0167_jt_prompt_everything_on_launch.py b/awx/main/migrations/0167_jt_prompt_everything_on_launch.py
index e0257e7103..b03f42235e 100644
--- a/awx/main/migrations/0167_jt_prompt_everything_on_launch.py
+++ b/awx/main/migrations/0167_jt_prompt_everything_on_launch.py
@@ -107,4 +107,20 @@ class Migration(migrations.Migration):
blank=True, editable=False, related_name='joblaunchconfigs', through='main.JobLaunchConfigInstanceGroupMembership', to='main.InstanceGroup'
),
),
+ # added WFJT prompts
+ migrations.AddField(
+ model_name='workflowjobtemplate',
+ name='ask_labels_on_launch',
+ field=awx.main.fields.AskForField(blank=True, default=False),
+ ),
+ migrations.AddField(
+ model_name='workflowjobtemplate',
+ name='ask_skip_tags_on_launch',
+ field=awx.main.fields.AskForField(blank=True, default=False),
+ ),
+ migrations.AddField(
+ model_name='workflowjobtemplate',
+ name='ask_tags_on_launch',
+ field=awx.main.fields.AskForField(blank=True, default=False),
+ ),
]
diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py
index d71dbc078f..731a3eaf65 100644
--- a/awx/main/models/jobs.py
+++ b/awx/main/models/jobs.py
@@ -227,15 +227,6 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
blank=True,
default=False,
)
- ask_limit_on_launch = AskForField(
- blank=True,
- default=False,
- )
- ask_tags_on_launch = AskForField(blank=True, default=False, allows_field='job_tags')
- ask_skip_tags_on_launch = AskForField(
- blank=True,
- default=False,
- )
ask_job_type_on_launch = AskForField(
blank=True,
default=False,
@@ -244,20 +235,11 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
blank=True,
default=False,
)
- ask_inventory_on_launch = AskForField(
- blank=True,
- default=False,
- )
ask_credential_on_launch = AskForField(blank=True, default=False, allows_field='credentials')
- ask_scm_branch_on_launch = AskForField(blank=True, default=False, allows_field='scm_branch')
ask_execution_environment_on_launch = AskForField(
blank=True,
default=False,
)
- ask_labels_on_launch = AskForField(
- blank=True,
- default=False,
- )
ask_forks_on_launch = AskForField(
blank=True,
default=False,
diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py
index 0e38d7288c..df10f0b29f 100644
--- a/awx/main/models/mixins.py
+++ b/awx/main/models/mixins.py
@@ -104,6 +104,33 @@ class SurveyJobTemplateMixin(models.Model):
default=False,
)
survey_spec = prevent_search(JSONBlob(default=dict, blank=True))
+
+ ask_inventory_on_launch = AskForField(
+ blank=True,
+ default=False,
+ )
+ ask_limit_on_launch = AskForField(
+ blank=True,
+ default=False,
+ )
+ ask_scm_branch_on_launch = AskForField(
+ blank=True,
+ default=False,
+ allows_field='scm_branch',
+ )
+ ask_labels_on_launch = AskForField(
+ blank=True,
+ default=False,
+ )
+ ask_tags_on_launch = AskForField(
+ blank=True,
+ default=False,
+ allows_field='job_tags',
+ )
+ ask_skip_tags_on_launch = AskForField(
+ blank=True,
+ default=False,
+ )
ask_variables_on_launch = AskForField(blank=True, default=False, allows_field='extra_vars')
def survey_password_variables(self):
diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py
index 21b4f4361b..b3d8bc4bee 100644
--- a/awx/main/models/unified_jobs.py
+++ b/awx/main/models/unified_jobs.py
@@ -422,6 +422,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, ExecutionEn
if unified_job.__class__ in activity_stream_registrar.models:
activity_stream_create(None, unified_job, True)
unified_job.log_lifecycle("created")
+
return unified_job
@classmethod
diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py
index 4f52ade6b4..4417807cbd 100644
--- a/awx/main/models/workflow.py
+++ b/awx/main/models/workflow.py
@@ -29,7 +29,7 @@ from awx.main.models import prevent_search, accepts_json, UnifiedJobTemplate, Un
from awx.main.models.notifications import NotificationTemplate, JobNotificationMixin
from awx.main.models.base import CreatedModifiedModel, VarsDictProperty
from awx.main.models.rbac import ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR
-from awx.main.fields import ImplicitRoleField, AskForField, JSONBlob
+from awx.main.fields import ImplicitRoleField, JSONBlob
from awx.main.models.mixins import (
ResourceMixin,
SurveyJobTemplateMixin,
@@ -385,7 +385,7 @@ class WorkflowJobOptions(LaunchTimeConfigBase):
@classmethod
def _get_unified_job_field_names(cls):
r = set(f.name for f in WorkflowJobOptions._meta.fields) | set(
- ['name', 'description', 'organization', 'survey_passwords', 'labels', 'limit', 'scm_branch']
+ ['name', 'description', 'organization', 'survey_passwords', 'labels', 'limit', 'scm_branch', 'job_tags', 'skip_tags']
)
r.remove('char_prompts') # needed due to copying launch config to launch config
return r
@@ -425,26 +425,28 @@ class WorkflowJobOptions(LaunchTimeConfigBase):
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin, RelatedJobsMixin, WebhookTemplateMixin):
SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'organization')]
- FIELDS_TO_PRESERVE_AT_COPY = ['labels', 'organization', 'instance_groups', 'workflow_job_template_nodes', 'credentials', 'survey_spec']
+ FIELDS_TO_PRESERVE_AT_COPY = [
+ 'labels',
+ 'organization',
+ 'instance_groups',
+ 'workflow_job_template_nodes',
+ 'credentials',
+ 'survey_spec',
+ 'skip_tags',
+ 'job_tags',
+ ]
class Meta:
app_label = 'main'
- ask_inventory_on_launch = AskForField(
- blank=True,
- default=False,
- )
- ask_limit_on_launch = AskForField(
+ notification_templates_approvals = models.ManyToManyField(
+ "NotificationTemplate",
blank=True,
- default=False,
+ related_name='%(class)s_notification_templates_for_approvals',
)
- ask_scm_branch_on_launch = AskForField(
- blank=True,
- default=False,
+ admin_role = ImplicitRoleField(
+ parent_role=['singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'organization.workflow_admin_role'],
)
- notification_templates_approvals = models.ManyToManyField("NotificationTemplate", blank=True, related_name='%(class)s_notification_templates_for_approvals')
-
- admin_role = ImplicitRoleField(parent_role=['singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'organization.workflow_admin_role'])
execute_role = ImplicitRoleField(
parent_role=[
'admin_role',
diff --git a/awx/main/tests/factories/fixtures.py b/awx/main/tests/factories/fixtures.py
index 200fa0f195..27556d6efe 100644
--- a/awx/main/tests/factories/fixtures.py
+++ b/awx/main/tests/factories/fixtures.py
@@ -210,7 +210,7 @@ def mk_workflow_job_template(name, extra_vars='', spec=None, organization=None,
if extra_vars:
extra_vars = json.dumps(extra_vars)
- wfjt = WorkflowJobTemplate(name=name, extra_vars=extra_vars, organization=organization, webhook_service=webhook_service)
+ wfjt = WorkflowJobTemplate.objects.create(name=name, extra_vars=extra_vars, organization=organization, webhook_service=webhook_service)
if spec:
wfjt.survey_spec = spec
diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py
index 2e3563a2b6..4f8b6bc83c 100644
--- a/awx/main/tests/functional/conftest.py
+++ b/awx/main/tests/functional/conftest.py
@@ -706,7 +706,7 @@ def jt_linked(organization, project, inventory, machine_credential, credential,
@pytest.fixture
def workflow_job_template(organization):
- wjt = WorkflowJobTemplate(name='test-workflow_job_template', organization=organization)
+ wjt = WorkflowJobTemplate.objects.create(name='test-workflow_job_template', organization=organization)
wjt.save()
return wjt
diff --git a/awx/main/tests/functional/models/test_workflow.py b/awx/main/tests/functional/models/test_workflow.py
index d8fa495c6c..b6df98fe59 100644
--- a/awx/main/tests/functional/models/test_workflow.py
+++ b/awx/main/tests/functional/models/test_workflow.py
@@ -287,12 +287,25 @@ class TestWorkflowJobTemplatePrompts:
@pytest.fixture
def wfjt_prompts(self):
return WorkflowJobTemplate.objects.create(
- ask_inventory_on_launch=True, ask_variables_on_launch=True, ask_limit_on_launch=True, ask_scm_branch_on_launch=True
+ ask_variables_on_launch=True,
+ ask_inventory_on_launch=True,
+ ask_tags_on_launch=True,
+ ask_labels_on_launch=True,
+ ask_limit_on_launch=True,
+ ask_scm_branch_on_launch=True,
+ ask_skip_tags_on_launch=True,
)
@pytest.fixture
def prompts_data(self, inventory):
- return dict(inventory=inventory, extra_vars={'foo': 'bar'}, limit='webservers', scm_branch='release-3.3')
+ return dict(
+ inventory=inventory,
+ extra_vars={'foo': 'bar'},
+ limit='webservers',
+ scm_branch='release-3.3',
+ job_tags='foo',
+ skip_tags='bar',
+ )
def test_apply_workflow_job_prompts(self, workflow_job_template, wfjt_prompts, prompts_data, inventory):
# null or empty fields used
@@ -300,6 +313,9 @@ class TestWorkflowJobTemplatePrompts:
assert workflow_job.limit is None
assert workflow_job.inventory is None
assert workflow_job.scm_branch is None
+ assert workflow_job.job_tags is None
+ assert workflow_job.skip_tags is None
+ assert len(workflow_job.labels.all()) is 0
# fields from prompts used
workflow_job = workflow_job_template.create_unified_job(**prompts_data)
@@ -307,15 +323,21 @@ class TestWorkflowJobTemplatePrompts:
assert workflow_job.limit == 'webservers'
assert workflow_job.inventory == inventory
assert workflow_job.scm_branch == 'release-3.3'
+ assert workflow_job.job_tags == 'foo'
+ assert workflow_job.skip_tags == 'bar'
# non-null fields from WFJT used
workflow_job_template.inventory = inventory
workflow_job_template.limit = 'fooo'
workflow_job_template.scm_branch = 'bar'
+ workflow_job_template.job_tags = 'baz'
+ workflow_job_template.skip_tags = 'dinosaur'
workflow_job = workflow_job_template.create_unified_job()
assert workflow_job.limit == 'fooo'
assert workflow_job.inventory == inventory
assert workflow_job.scm_branch == 'bar'
+ assert workflow_job.job_tags == 'baz'
+ assert workflow_job.skip_tags == 'dinosaur'
@pytest.mark.django_db
def test_process_workflow_job_prompts(self, inventory, workflow_job_template, wfjt_prompts, prompts_data):
@@ -340,12 +362,19 @@ class TestWorkflowJobTemplatePrompts:
ask_limit_on_launch=True,
scm_branch='bar',
ask_scm_branch_on_launch=True,
+ job_tags='foo',
+ skip_tags='bar',
),
user=org_admin,
expect=201,
)
wfjt = WorkflowJobTemplate.objects.get(id=r.data['id'])
- assert wfjt.char_prompts == {'limit': 'foooo', 'scm_branch': 'bar'}
+ assert wfjt.char_prompts == {
+ 'limit': 'foooo',
+ 'scm_branch': 'bar',
+ 'job_tags': 'foo',
+ 'skip_tags': 'bar',
+ }
assert wfjt.ask_scm_branch_on_launch is True
assert wfjt.ask_limit_on_launch is True
@@ -355,6 +384,67 @@ class TestWorkflowJobTemplatePrompts:
assert r.data['limit'] == 'prompt_limit'
assert r.data['scm_branch'] == 'prompt_branch'
+ @pytest.mark.django_db
+ def test_set_all_ask_for_prompts_false_from_post(self, post, organization, inventory, org_admin):
+ '''
+ Tests default behaviour and values of ask_for_* fields on WFJT via POST
+ '''
+ r = post(
+ url=reverse('api:workflow_job_template_list'),
+ data=dict(
+ name='workflow that tests ask_for prompts',
+ organization=organization.id,
+ inventory=inventory.id,
+ job_tags='',
+ skip_tags='',
+ ),
+ user=org_admin,
+ expect=201,
+ )
+ wfjt = WorkflowJobTemplate.objects.get(id=r.data['id'])
+
+ assert wfjt.ask_inventory_on_launch is False
+ assert wfjt.ask_labels_on_launch is False
+ assert wfjt.ask_limit_on_launch is False
+ assert wfjt.ask_scm_branch_on_launch is False
+ assert wfjt.ask_skip_tags_on_launch is False
+ assert wfjt.ask_tags_on_launch is False
+ assert wfjt.ask_variables_on_launch is False
+
+ @pytest.mark.django_db
+ def test_set_all_ask_for_prompts_true_from_post(self, post, organization, inventory, org_admin):
+ '''
+ Tests behaviour and values of ask_for_* fields on WFJT via POST
+ '''
+ r = post(
+ url=reverse('api:workflow_job_template_list'),
+ data=dict(
+ name='workflow that tests ask_for prompts',
+ organization=organization.id,
+ inventory=inventory.id,
+ job_tags='',
+ skip_tags='',
+ ask_inventory_on_launch=True,
+ ask_labels_on_launch=True,
+ ask_limit_on_launch=True,
+ ask_scm_branch_on_launch=True,
+ ask_skip_tags_on_launch=True,
+ ask_tags_on_launch=True,
+ ask_variables_on_launch=True,
+ ),
+ user=org_admin,
+ expect=201,
+ )
+ wfjt = WorkflowJobTemplate.objects.get(id=r.data['id'])
+
+ assert wfjt.ask_inventory_on_launch is True
+ assert wfjt.ask_labels_on_launch is True
+ assert wfjt.ask_limit_on_launch is True
+ assert wfjt.ask_scm_branch_on_launch is True
+ assert wfjt.ask_skip_tags_on_launch is True
+ assert wfjt.ask_tags_on_launch is True
+ assert wfjt.ask_variables_on_launch is True
+
@pytest.mark.django_db
def test_workflow_ancestors(organization):
diff --git a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
index 526f06c4c9..0cbf6b7af0 100644
--- a/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
+++ b/awx/main/tests/unit/api/serializers/test_workflow_serializers.py
@@ -11,6 +11,7 @@ from awx.api.serializers import (
from awx.main.models import Job, WorkflowJobTemplateNode, WorkflowJob, WorkflowJobNode, WorkflowJobTemplate, Project, Inventory, JobTemplate
+@pytest.mark.django_db
@mock.patch('awx.api.serializers.UnifiedJobTemplateSerializer.get_related', lambda x, y: {})
class TestWorkflowJobTemplateSerializerGetRelated:
@pytest.fixture
@@ -58,6 +59,7 @@ class TestWorkflowNodeBaseSerializerGetRelated:
assert 'unified_job_template' not in related
+@pytest.mark.django_db
@mock.patch('awx.api.serializers.BaseSerializer.get_related', lambda x, y: {})
class TestWorkflowJobTemplateNodeSerializerGetRelated:
@pytest.fixture
@@ -146,6 +148,7 @@ class TestWorkflowJobTemplateNodeSerializerCharPrompts:
assert WFJT_serializer.instance.limit == 'webservers'
+@pytest.mark.django_db
@mock.patch('awx.api.serializers.BaseSerializer.validate', lambda self, attrs: attrs)
class TestWorkflowJobTemplateNodeSerializerSurveyPasswords:
@pytest.fixture
@@ -162,7 +165,7 @@ class TestWorkflowJobTemplateNodeSerializerSurveyPasswords:
def test_set_survey_passwords_create(self, jt):
serializer = WorkflowJobTemplateNodeSerializer()
- wfjt = WorkflowJobTemplate(name='fake-wfjt')
+ wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt')
attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': 'secret_answer'}})
assert 'survey_passwords' in attrs
assert 'var1' in attrs['survey_passwords']
@@ -171,7 +174,7 @@ class TestWorkflowJobTemplateNodeSerializerSurveyPasswords:
def test_set_survey_passwords_modify(self, jt):
serializer = WorkflowJobTemplateNodeSerializer()
- wfjt = WorkflowJobTemplate(name='fake-wfjt')
+ wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt')
serializer.instance = WorkflowJobTemplateNode(workflow_job_template=wfjt, unified_job_template=jt)
attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': 'secret_answer'}})
assert 'survey_passwords' in attrs
@@ -181,7 +184,7 @@ class TestWorkflowJobTemplateNodeSerializerSurveyPasswords:
def test_use_db_answer(self, jt, mocker):
serializer = WorkflowJobTemplateNodeSerializer()
- wfjt = WorkflowJobTemplate(name='fake-wfjt')
+ wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt')
serializer.instance = WorkflowJobTemplateNode(workflow_job_template=wfjt, unified_job_template=jt, extra_data={'var1': '$encrypted$foooooo'})
with mocker.patch('awx.main.models.mixins.decrypt_value', return_value='foo'):
attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}})
@@ -196,7 +199,7 @@ class TestWorkflowJobTemplateNodeSerializerSurveyPasswords:
with that particular var omitted so on launch time the default takes effect
"""
serializer = WorkflowJobTemplateNodeSerializer()
- wfjt = WorkflowJobTemplate(name='fake-wfjt')
+ wfjt = WorkflowJobTemplate.objects.create(name='fake-wfjt')
jt.survey_spec['spec'][0]['default'] = '$encrypted$bar'
attrs = serializer.validate({'unified_job_template': jt, 'workflow_job_template': wfjt, 'extra_data': {'var1': '$encrypted$'}})
assert 'survey_passwords' in attrs
diff --git a/awx/main/tests/unit/models/test_survey_models.py b/awx/main/tests/unit/models/test_survey_models.py
index 9ec5673cd8..57058930ea 100644
--- a/awx/main/tests/unit/models/test_survey_models.py
+++ b/awx/main/tests/unit/models/test_survey_models.py
@@ -259,13 +259,14 @@ def test_survey_encryption_defaults(survey_spec_factory, question_type, default,
@pytest.mark.survey
+@pytest.mark.django_db
class TestWorkflowSurveys:
def test_update_kwargs_survey_defaults(self, survey_spec_factory):
"Assure that the survey default over-rides a JT variable"
spec = survey_spec_factory('var1')
spec['spec'][0]['default'] = 3
spec['spec'][0]['required'] = False
- wfjt = WorkflowJobTemplate(name="test-wfjt", survey_spec=spec, survey_enabled=True, extra_vars="var1: 5")
+ wfjt = WorkflowJobTemplate.objects.create(name="test-wfjt", survey_spec=spec, survey_enabled=True, extra_vars="var1: 5")
updated_extra_vars = wfjt._update_unified_job_kwargs({}, {})
assert 'extra_vars' in updated_extra_vars
assert json.loads(updated_extra_vars['extra_vars'])['var1'] == 3
@@ -277,7 +278,7 @@ class TestWorkflowSurveys:
spec['spec'][0]['required'] = False
spec['spec'][1]['required'] = True
spec['spec'][2]['required'] = False
- wfjt = WorkflowJobTemplate(name="test-wfjt", survey_spec=spec, survey_enabled=True, extra_vars="question2: hiworld")
+ wfjt = WorkflowJobTemplate.objects.create(name="test-wfjt", survey_spec=spec, survey_enabled=True, extra_vars="question2: hiworld")
assert wfjt.variables_needed_to_start == ['question2']
assert not wfjt.can_start_without_user_input()
@@ -311,6 +312,6 @@ class TestExtraVarsNoPrompt:
self.process_vars_and_assert(jt, provided_vars, valid)
def test_wfjt_extra_vars_counting(self, provided_vars, valid):
- wfjt = WorkflowJobTemplate(name='foo', extra_vars={'tmpl_var': 'bar'})
+ wfjt = WorkflowJobTemplate.objects.create(name='foo', extra_vars={'tmpl_var': 'bar'})
prompted_fields, ignored_fields, errors = wfjt._accept_or_ignore_job_kwargs(extra_vars=provided_vars)
self.process_vars_and_assert(wfjt, provided_vars, valid)
diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py
index f8bb1e9c84..65190f92a3 100644
--- a/awx/main/tests/unit/models/test_workflow_unit.py
+++ b/awx/main/tests/unit/models/test_workflow_unit.py
@@ -94,7 +94,7 @@ def workflow_job_unit():
@pytest.fixture
def workflow_job_template_unit():
- return WorkflowJobTemplate(name='workflow')
+ return WorkflowJobTemplate.objects.create(name='workflow')
@pytest.fixture
@@ -151,6 +151,7 @@ def test_node_getter_and_setters():
assert node.job_type == 'check'
+@pytest.mark.django_db
class TestWorkflowJobCreate:
def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker):
mock_create = mocker.MagicMock()
@@ -183,6 +184,7 @@ class TestWorkflowJobCreate:
)
+@pytest.mark.django_db
@mock.patch('awx.main.models.workflow.WorkflowNodeBase.get_parent_nodes', lambda self: [])
class TestWorkflowJobNodeJobKWARGS:
"""
@@ -231,4 +233,12 @@ class TestWorkflowJobNodeJobKWARGS:
def test_get_ask_mapping_integrity():
- assert list(WorkflowJobTemplate.get_ask_mapping().keys()) == ['extra_vars', 'inventory', 'limit', 'scm_branch']
+ assert list(WorkflowJobTemplate.get_ask_mapping().keys()) == [
+ 'inventory',
+ 'limit',
+ 'scm_branch',
+ 'labels',
+ 'job_tags',
+ 'skip_tags',
+ 'extra_vars',
+ ]
diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py
index 547af7b42c..0059cb4984 100644
--- a/awx/main/tests/unit/test_access.py
+++ b/awx/main/tests/unit/test_access.py
@@ -196,6 +196,7 @@ def test_jt_can_add_bad_data(user_unit):
assert not access.can_add({'asdf': 'asdf'})
+@pytest.mark.django_db
class TestWorkflowAccessMethods:
@pytest.fixture
def workflow(self, workflow_job_template_factory):
diff --git a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js
index 27263d479c..d4e3cee1ef 100644
--- a/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js
+++ b/awx/ui/src/components/LaunchPrompt/LaunchPrompt.test.js
@@ -16,8 +16,12 @@ import CredentialsStep from './steps/CredentialsStep';
import CredentialPasswordsStep from './steps/CredentialPasswordsStep';
import OtherPromptsStep from './steps/OtherPromptsStep';
import PreviewStep from './steps/PreviewStep';
+import executionEnvironmentHelpTextStrings from 'screens/ExecutionEnvironment/shared/ExecutionEnvironment.helptext';
+import { ExecutionEnvironment } from 'types';
+import ExecutionEnvironmentStep from './steps/ExecutionEnvironmentStep';
jest.mock('../../api/models/Inventories');
+jest.mock('../../api/models/ExecutionEnvironments');
jest.mock('../../api/models/CredentialTypes');
jest.mock('../../api/models/Credentials');
jest.mock('../../api/models/JobTemplates');
@@ -150,13 +154,14 @@ describe('LaunchPrompt', () => {
const wizard = await waitForElement(wrapper, 'Wizard');
const steps = wizard.prop('steps');
- expect(steps).toHaveLength(6);
+ expect(steps).toHaveLength(7);
expect(steps[0].name.props.children).toEqual('Inventory');
expect(steps[1].name.props.children).toEqual('Credentials');
expect(steps[2].name.props.children).toEqual('Credential passwords');
- expect(steps[3].name.props.children).toEqual('Other prompts');
- expect(steps[4].name.props.children).toEqual('Survey');
- expect(steps[5].name.props.children).toEqual('Preview');
+ expect(steps[3].name.props.children).toEqual('Execution Environment');
+ expect(steps[4].name.props.children).toEqual('Other prompts');
+ expect(steps[5].name.props.children).toEqual('Survey');
+ expect(steps[6].name.props.children).toEqual('Preview');
expect(wizard.find('WizardHeader').prop('title')).toBe('Launch | Foobar');
expect(wizard.find('WizardHeader').prop('description')).toBe(
'Foo Description'
diff --git a/awx/ui/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.js b/awx/ui/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.js
index 7c119a522d..9fffccbb33 100644
--- a/awx/ui/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.js
+++ b/awx/ui/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.js
@@ -22,12 +22,18 @@ const jobTemplateData = {
allow_simultaneous: false,
ask_credential_on_launch: false,
ask_diff_mode_on_launch: false,
+ ask_execution_environment_on_launch: false,
+ ask_forks_on_launch: false,
+ ask_instance_groups_on_launch: false,
ask_inventory_on_launch: false,
+ ask_job_slice_count_on_launch: false,
ask_job_type_on_launch: false,
+ ask_labels_on_launch: false,
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
ask_skip_tags_on_launch: false,
ask_tags_on_launch: false,
+ ask_timeout_on_launch: false,
ask_variables_on_launch: false,
ask_verbosity_on_launch: false,
ask_execution_environment_on_launch: false,
diff --git a/awx/ui/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.js b/awx/ui/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.js
index 500143973f..2ada0105d5 100644
--- a/awx/ui/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.js
+++ b/awx/ui/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.js
@@ -35,13 +35,18 @@ const mockJobTemplate = {
allow_simultaneous: false,
ask_scm_branch_on_launch: false,
ask_diff_mode_on_launch: false,
+ ask_execution_environment_on_launch: false,
+ ask_forks_on_launch: false,
+ ask_instance_groups_on_launch: false,
ask_variables_on_launch: false,
ask_limit_on_launch: false,
ask_tags_on_launch: false,
ask_skip_tags_on_launch: false,
ask_job_type_on_launch: false,
+ ask_labels_on_launch: false,
ask_verbosity_on_launch: false,
ask_inventory_on_launch: false,
+ ask_job_slice_count_on_launch: false,
ask_credential_on_launch: false,
ask_execution_environment_on_launch: false,
ask_forks_on_launch: false,
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.js b/awx/ui/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.js
index a6cb0e1969..8a0c55cd06 100644
--- a/awx/ui/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.js
+++ b/awx/ui/src/screens/Template/WorkflowJobTemplateAdd/WorkflowJobTemplateAdd.test.js
@@ -82,7 +82,7 @@ describe('<WorkflowJobTemplateAdd/>', () => {
test('calls workflowJobTemplatesAPI with correct information on submit', async () => {
await act(async () => {
wrapper.find('input#wfjt-name').simulate('change', {
- target: { value: 'Alex', name: 'name' },
+ target: { value: 'Alex Singh', name: 'name' },
});
wrapper.find('LabelSelect').find('SelectToggle').simulate('click');
@@ -104,18 +104,23 @@ describe('<WorkflowJobTemplateAdd/>', () => {
wrapper.find('form').simulate('submit');
});
await expect(WorkflowJobTemplatesAPI.create).toHaveBeenCalledWith({
- name: 'Alex',
+ name: 'Alex Singh',
allow_simultaneous: false,
ask_inventory_on_launch: false,
+ ask_labels_on_launch: false,
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
+ ask_skip_tags_on_launch: false,
+ ask_tags_on_launch: false,
ask_variables_on_launch: false,
description: '',
extra_vars: '---',
inventory: undefined,
+ job_tags: '',
limit: null,
organization: undefined,
scm_branch: '',
+ skip_tags: '',
webhook_credential: undefined,
webhook_service: '',
webhook_url: '',
diff --git a/awx/ui/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.js b/awx/ui/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.js
index cb56e82ef0..56c99782c1 100644
--- a/awx/ui/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.js
+++ b/awx/ui/src/screens/Template/WorkflowJobTemplateEdit/WorkflowJobTemplateEdit.test.js
@@ -161,6 +161,7 @@ describe('<WorkflowJobTemplateEdit/>', () => {
expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalledWith(6, {
name: 'Alex',
description: 'Apollo and Athena',
+ skip_tags: '',
inventory: 1,
organization: 1,
scm_branch: 'main',
@@ -174,6 +175,11 @@ describe('<WorkflowJobTemplateEdit/>', () => {
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
ask_variables_on_launch: false,
+ ask_labels_on_launch: false,
+ ask_skip_tags_on_launch: false,
+ ask_tags_on_launch: false,
+ job_tags: '',
+ skip_tags: '',
});
wrapper.update();
await expect(WorkflowJobTemplatesAPI.disassociateLabel).toBeCalledWith(6, {
@@ -273,16 +279,21 @@ describe('<WorkflowJobTemplateEdit/>', () => {
expect(WorkflowJobTemplatesAPI.update).toBeCalledWith(6, {
allow_simultaneous: false,
ask_inventory_on_launch: false,
+ ask_labels_on_launch: false,
ask_limit_on_launch: false,
ask_scm_branch_on_launch: false,
+ ask_skip_tags_on_launch: false,
+ ask_tags_on_launch: false,
ask_variables_on_launch: false,
description: 'bar',
extra_vars: '---',
inventory: 1,
+ job_tags: '',
limit: '5000',
name: 'Foo',
organization: 1,
scm_branch: 'devel',
+ skip_tags: '',
webhook_credential: null,
webhook_service: '',
webhook_url: '',
diff --git a/awx/ui/src/screens/Template/shared/WorkflowJobTemplate.helptext.js b/awx/ui/src/screens/Template/shared/WorkflowJobTemplate.helptext.js
index eba476a214..a8f29f7bc6 100644
--- a/awx/ui/src/screens/Template/shared/WorkflowJobTemplate.helptext.js
+++ b/awx/ui/src/screens/Template/shared/WorkflowJobTemplate.helptext.js
@@ -18,6 +18,7 @@ const wfHelpTextStrings = () => ({
webhookKey: t`Webhook services can use this as a shared secret.`,
webhookCredential: t`Optionally select the credential to use to send status updates back to the webhook service.`,
webhookService: t`Select a webhook service.`,
+ skipTags: t`Skip tags are useful when you have a large playbook, and you want to skip specific parts of a play or task. Use commas to separate multiple tags. Refer to the documentation for details on the usage of tags.`,
enabledOptions: (
<>
<p>{t`Concurrent jobs: If enabled, simultaneous runs of this workflow job template will be allowed.`}</p>
diff --git a/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.js b/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.js
index 1b9f1f9511..30e9ac8668 100644
--- a/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.js
+++ b/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.js
@@ -27,6 +27,7 @@ import CheckboxField from 'components/FormField/CheckboxField';
import Popover from 'components/Popover';
import { WorkFlowJobTemplate } from 'types';
import LabelSelect from 'components/LabelSelect';
+import { TagMultiSelect } from 'components/MultiSelect';
import WebhookSubForm from './WebhookSubForm';
import getHelpText from './WorkflowJobTemplate.helptext';
@@ -59,6 +60,8 @@ function WorkflowJobTemplateForm({
const [, webhookKeyMeta, webhookKeyHelpers] = useField('webhook_key');
const [, webhookCredentialMeta, webhookCredentialHelpers] =
useField('webhook_credential');
+ const [skipTagsField, , skipTagsHelpers] = useField('skip_tags');
+ const [jobTagsField, , jobTagsHelpers] = useField('job_tags');
useEffect(() => {
if (enableWebhooks) {
@@ -167,7 +170,6 @@ function WorkflowJobTemplateForm({
}}
/>
</FieldWithPrompt>
-
<FieldWithPrompt
fieldId="wfjt-scm-branch"
label={t`Source control branch`}
@@ -184,14 +186,11 @@ function WorkflowJobTemplateForm({
aria-label={t`source control branch`}
/>
</FieldWithPrompt>
- </FormColumnLayout>
- <FormFullWidthLayout>
<FieldWithPrompt
- fieldId="template-labels"
label={t`Labels`}
+ fieldId="template-labels"
promptId="template-ask-labels-on-launch"
promptName="ask_labels_on_launch"
- tooltip={helpText.labels}
>
<LabelSelect
value={labelsField.value}
@@ -200,16 +199,42 @@ function WorkflowJobTemplateForm({
createText={t`Create`}
/>
</FieldWithPrompt>
- </FormFullWidthLayout>
- <FormFullWidthLayout>
- <VariablesField
- id="wfjt-variables"
- name="extra_vars"
- label={t`Variables`}
- promptId="template-ask-variables-on-launch"
- tooltip={helpText.variables}
- />
- </FormFullWidthLayout>
+ <FormFullWidthLayout>
+ <VariablesField
+ id="wfjt-variables"
+ name="extra_vars"
+ label={t`Variables`}
+ promptId="template-ask-variables-on-launch"
+ tooltip={helpText.variables}
+ />
+ </FormFullWidthLayout>
+ <FormColumnLayout>
+ <FieldWithPrompt
+ fieldId="template-tags"
+ label={t`Job Tags`}
+ promptId="template-ask-tags-on-launch"
+ promptName="ask_tags_on_launch"
+ tooltip={helpText.jobTags}
+ >
+ <TagMultiSelect
+ value={jobTagsField.value}
+ onChange={(value) => jobTagsHelpers.setValue(value)}
+ />
+ </FieldWithPrompt>
+ </FormColumnLayout>
+ <FieldWithPrompt
+ fieldId="template-skip-tags"
+ label={t`Skip Tags`}
+ promptId="template-ask-skip-tags-on-launch"
+ promptName="ask_skip_tags_on_launch"
+ tooltip={helpText.skipTags}
+ >
+ <TagMultiSelect
+ value={skipTagsField.value}
+ onChange={(value) => skipTagsHelpers.setValue(value)}
+ />
+ </FieldWithPrompt>
+ </FormColumnLayout>
<FormGroup fieldId="options" label={t`Options`}>
<FormCheckboxLayout isInline>
<Checkbox
@@ -282,6 +307,8 @@ const FormikApp = withFormik({
extra_vars: template.extra_vars || '---',
limit: template.limit || '',
scm_branch: template.scm_branch || '',
+ skip_tags: template.skip_tags || '',
+ job_tags: template.job_tags || '',
allow_simultaneous: template.allow_simultaneous || false,
webhook_credential: template?.summary_fields?.webhook_credential || null,
webhook_service: template.webhook_service || '',
@@ -290,6 +317,8 @@ const FormikApp = withFormik({
ask_inventory_on_launch: template.ask_inventory_on_launch || false,
ask_variables_on_launch: template.ask_variables_on_launch || false,
ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false,
+ ask_skip_tags_on_launch: template.ask_skip_tags_on_launch || false,
+ ask_tags_on_launch: template.ask_tags_on_launch || false,
webhook_url: template?.related?.webhook_receiver
? `${urlOrigin}${template.related.webhook_receiver}`
: '',
diff --git a/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.test.js b/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.test.js
index 17234df88c..65b5f59522 100644
--- a/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.test.js
+++ b/awx/ui/src/screens/Template/shared/WorkflowJobTemplateForm.test.js
@@ -189,7 +189,9 @@ describe('<WorkflowJobTemplateForm/>', () => {
'FieldWithPrompt[label="Inventory"]',
'FieldWithPrompt[label="Limit"]',
'FieldWithPrompt[label="Source control branch"]',
- 'FormGroup[label="Labels"]',
+ 'FieldWithPrompt[label="Labels"]',
+ 'FieldWithPrompt[label="Skip Tags"]',
+ 'FieldWithPrompt[label="Job Tags"]',
'VariablesField',
];
diff --git a/awx_collection/plugins/modules/workflow_job_template.py b/awx_collection/plugins/modules/workflow_job_template.py
index afc792e1f1..93eb451503 100644
--- a/awx_collection/plugins/modules/workflow_job_template.py
+++ b/awx_collection/plugins/modules/workflow_job_template.py
@@ -47,6 +47,16 @@ options:
description:
- Variables which will be made available to jobs ran inside the workflow.
type: dict
+ job_tags:
+ description:
+ - Comma separated list of the tags to use for the job template.
+ type: str
+ ask_tags_on_launch:
+ description:
+ - Prompt user for job tags on launch.
+ type: bool
+ aliases:
+ - ask_tags
organization:
description:
- Organization the workflow job template exists in.
@@ -85,6 +95,22 @@ options:
description:
- Prompt user for limit on launch of this workflow job template
type: bool
+ ask_labels_on_launch:
+ description:
+ - Prompt user for labels on launch.
+ type: bool
+ aliases:
+ - ask_labels
+ ask_skip_tags_on_launch:
+ description:
+ - Prompt user for job tags to skip on launch.
+ type: bool
+ aliases:
+ - ask_skip_tags
+ skip_tags:
+ description:
+ - Comma separated list of the tags to skip for the job template.
+ type: str
webhook_service:
description:
- Service that webhook requests will be accepted from
@@ -665,11 +691,15 @@ def main():
copy_from=dict(),
description=dict(),
extra_vars=dict(type='dict'),
+ job_tags=dict(),
+ skip_tags=dict(),
organization=dict(),
survey_spec=dict(type='dict', aliases=['survey']),
survey_enabled=dict(type='bool'),
allow_simultaneous=dict(type='bool'),
ask_variables_on_launch=dict(type='bool'),
+ ask_labels_on_launch=dict(type='bool', aliases=['ask_labels']),
+ ask_skip_tags_on_launch=dict(type='bool', aliases=['ask_skip_tags']),
inventory=dict(),
limit=dict(),
scm_branch=dict(),
@@ -752,7 +782,11 @@ def main():
'ask_scm_branch_on_launch',
'ask_limit_on_launch',
'ask_variables_on_launch',
+ 'ask_labels_on_launch',
+ 'ask_skip_tags_on_launch',
'webhook_service',
+ 'job_tags',
+ 'skip_tags',
):
field_val = module.params.get(field_name)
if field_val is not None:
diff --git a/awx_collection/test/awx/test_workflow_job_template.py b/awx_collection/test/awx/test_workflow_job_template.py
index c5448b23aa..60a4fff7cf 100644
--- a/awx_collection/test/awx/test_workflow_job_template.py
+++ b/awx_collection/test/awx/test_workflow_job_template.py
@@ -18,6 +18,8 @@ def test_create_workflow_job_template(run_module, admin_user, organization, surv
'survey_spec': survey_spec,
'survey_enabled': True,
'state': 'present',
+ 'job_tags': '',
+ 'skip_tags': '',
},
admin_user,
)
@@ -35,7 +37,16 @@ def test_create_workflow_job_template(run_module, admin_user, organization, surv
@pytest.mark.django_db
def test_create_modify_no_survey(run_module, admin_user, organization, survey_spec):
- result = run_module('workflow_job_template', {'name': 'foo-workflow', 'organization': organization.name}, admin_user)
+ result = run_module(
+ 'workflow_job_template',
+ {
+ 'name': 'foo-workflow',
+ 'organization': organization.name,
+ 'job_tags': '',
+ 'skip_tags': '',
+ },
+ admin_user,
+ )
assert not result.get('failed', False), result.get('msg', result)
assert result.get('changed', False), result