mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 01:17:37 -02:30
Adding EE/IG/labels/forks/timeout/job_slice_count to schedules
Modifying schedules to work with related fields Updating awx.awx.workflow_job_template_node
This commit is contained in:
committed by
Alan Rominger
parent
2e217ed466
commit
809df74050
@@ -3640,6 +3640,10 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
|||||||
skip_tags = 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)
|
||||||
diff_mode = serializers.BooleanField(required=False, allow_null=True, default=None)
|
diff_mode = serializers.BooleanField(required=False, allow_null=True, default=None)
|
||||||
verbosity = serializers.ChoiceField(allow_null=True, required=False, default=None, choices=VERBOSITY_CHOICES)
|
verbosity = serializers.ChoiceField(allow_null=True, required=False, default=None, choices=VERBOSITY_CHOICES)
|
||||||
|
execution_environment = serializers.PrimaryKeyRelatedField(queryset=ExecutionEnvironment.objects.all(), required=False, allow_null=True, default=None)
|
||||||
|
forks = serializers.IntegerField(required=False, allow_null=True, default=None)
|
||||||
|
job_slice_count = serializers.IntegerField(required=False, allow_null=True, default=None)
|
||||||
|
timeout = serializers.IntegerField(required=False, allow_null=True, default=None)
|
||||||
exclude_errors = ()
|
exclude_errors = ()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -3655,6 +3659,10 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
|||||||
'skip_tags',
|
'skip_tags',
|
||||||
'diff_mode',
|
'diff_mode',
|
||||||
'verbosity',
|
'verbosity',
|
||||||
|
'execution_environment',
|
||||||
|
'forks',
|
||||||
|
'job_slice_count',
|
||||||
|
'timeout',
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_related(self, obj):
|
def get_related(self, obj):
|
||||||
@@ -3662,6 +3670,10 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
|||||||
if obj.inventory_id:
|
if obj.inventory_id:
|
||||||
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory_id})
|
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory_id})
|
||||||
res['credentials'] = self.reverse('api:{}_credentials_list'.format(get_type_for_model(self.Meta.model)), kwargs={'pk': obj.pk})
|
res['credentials'] = self.reverse('api:{}_credentials_list'.format(get_type_for_model(self.Meta.model)), kwargs={'pk': obj.pk})
|
||||||
|
res['labels'] = self.reverse('api:{}_labels_list'.format(get_type_for_model(self.Meta.model)), kwargs={'pk': obj.pk})
|
||||||
|
res['instance_groups'] = self.reverse('api:{}_instance_groups_list'.format(get_type_for_model(self.Meta.model)), kwargs={'pk': obj.pk})
|
||||||
|
if obj.execution_environment_id:
|
||||||
|
res['execution_environment'] = self.reverse('api:execution_environment_detail', kwargs={'pk': obj.execution_environment_id})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _build_mock_obj(self, attrs):
|
def _build_mock_obj(self, attrs):
|
||||||
@@ -3671,7 +3683,11 @@ class LaunchConfigurationBaseSerializer(BaseSerializer):
|
|||||||
setattr(mock_obj, field.name, getattr(self.instance, field.name))
|
setattr(mock_obj, field.name, getattr(self.instance, field.name))
|
||||||
field_names = set(field.name for field in self.Meta.model._meta.fields)
|
field_names = set(field.name for field in self.Meta.model._meta.fields)
|
||||||
for field_name, value in list(attrs.items()):
|
for field_name, value in list(attrs.items()):
|
||||||
setattr(mock_obj, field_name, value)
|
if field_name == 'execution_environment':
|
||||||
|
if value:
|
||||||
|
setattr(mock_obj, field_name, value)
|
||||||
|
else:
|
||||||
|
setattr(mock_obj, field_name, value)
|
||||||
if field_name not in field_names:
|
if field_name not in field_names:
|
||||||
attrs.pop(field_name)
|
attrs.pop(field_name)
|
||||||
return mock_obj
|
return mock_obj
|
||||||
@@ -4135,12 +4151,12 @@ class JobLaunchSerializer(BaseSerializer):
|
|||||||
skip_tags = serializers.CharField(required=False, write_only=True, allow_blank=True)
|
skip_tags = serializers.CharField(required=False, write_only=True, allow_blank=True)
|
||||||
limit = serializers.CharField(required=False, write_only=True, allow_blank=True)
|
limit = serializers.CharField(required=False, write_only=True, allow_blank=True)
|
||||||
verbosity = serializers.ChoiceField(required=False, choices=VERBOSITY_CHOICES, write_only=True)
|
verbosity = serializers.ChoiceField(required=False, choices=VERBOSITY_CHOICES, write_only=True)
|
||||||
execution_environment = serializers.PrimaryKeyRelatedField(queryset=ExecutionEnvironment.objects.all(), required=False, write_only=True)
|
execution_environment = serializers.PrimaryKeyRelatedField(queryset=ExecutionEnvironment.objects.all(), required=False)
|
||||||
labels = serializers.PrimaryKeyRelatedField(many=True, queryset=Label.objects.all(), required=False, write_only=True)
|
labels = serializers.PrimaryKeyRelatedField(many=True, queryset=Label.objects.all(), required=False)
|
||||||
forks = serializers.IntegerField(required=False, write_only=True, default=1)
|
forks = serializers.IntegerField(required=False, write_only=True, default=1)
|
||||||
job_slice_count = serializers.IntegerField(required=False, write_only=True, default=0)
|
job_slice_count = serializers.IntegerField(required=False, write_only=True, default=0)
|
||||||
timeout = serializers.IntegerField(required=False, write_only=True, default=0)
|
timeout = serializers.IntegerField(required=False, write_only=True, default=0)
|
||||||
instance_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=InstanceGroup.objects.all(), required=False, write_only=True)
|
instance_groups = serializers.PrimaryKeyRelatedField(many=True, queryset=InstanceGroup.objects.all(), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = JobTemplate
|
model = JobTemplate
|
||||||
@@ -4778,7 +4794,7 @@ class SchedulePreviewSerializer(BaseSerializer):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSerializer):
|
class ScheduleSerializer(LabelsListMixin, LaunchConfigurationBaseSerializer, SchedulePreviewSerializer):
|
||||||
show_capabilities = ['edit', 'delete']
|
show_capabilities = ['edit', 'delete']
|
||||||
|
|
||||||
timezone = serializers.SerializerMethodField(
|
timezone = serializers.SerializerMethodField(
|
||||||
@@ -4822,6 +4838,8 @@ class ScheduleSerializer(LaunchConfigurationBaseSerializer, SchedulePreviewSeria
|
|||||||
if isinstance(obj.unified_job_template, SystemJobTemplate):
|
if isinstance(obj.unified_job_template, SystemJobTemplate):
|
||||||
summary_fields['unified_job_template']['job_type'] = obj.unified_job_template.job_type
|
summary_fields['unified_job_template']['job_type'] = obj.unified_job_template.job_type
|
||||||
|
|
||||||
|
# We are not showing instance groups on summary fields because JTs don't either
|
||||||
|
|
||||||
if 'inventory' in summary_fields:
|
if 'inventory' in summary_fields:
|
||||||
return summary_fields
|
return summary_fields
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
|
|
||||||
from awx.api.views import ScheduleList, ScheduleDetail, ScheduleUnifiedJobsList, ScheduleCredentialsList
|
from awx.api.views import ScheduleList, ScheduleDetail, ScheduleUnifiedJobsList, ScheduleCredentialsList, ScheduleLabelsList, ScheduleInstanceGroupList
|
||||||
|
|
||||||
|
|
||||||
urls = [
|
urls = [
|
||||||
@@ -11,6 +11,8 @@ urls = [
|
|||||||
re_path(r'^(?P<pk>[0-9]+)/$', ScheduleDetail.as_view(), name='schedule_detail'),
|
re_path(r'^(?P<pk>[0-9]+)/$', ScheduleDetail.as_view(), name='schedule_detail'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/jobs/$', ScheduleUnifiedJobsList.as_view(), name='schedule_unified_jobs_list'),
|
re_path(r'^(?P<pk>[0-9]+)/jobs/$', ScheduleUnifiedJobsList.as_view(), name='schedule_unified_jobs_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/credentials/$', ScheduleCredentialsList.as_view(), name='schedule_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', ScheduleCredentialsList.as_view(), name='schedule_credentials_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', ScheduleLabelsList.as_view(), name='schedule_labels_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', ScheduleInstanceGroupList.as_view(), name='schedule_instance_groups_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from awx.api.views import (
|
|||||||
WorkflowJobNodeFailureNodesList,
|
WorkflowJobNodeFailureNodesList,
|
||||||
WorkflowJobNodeAlwaysNodesList,
|
WorkflowJobNodeAlwaysNodesList,
|
||||||
WorkflowJobNodeCredentialsList,
|
WorkflowJobNodeCredentialsList,
|
||||||
|
WorkflowJobNodeLabelsList,
|
||||||
|
WorkflowJobNodeInstanceGroupsList,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ urls = [
|
|||||||
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobNodeFailureNodesList.as_view(), name='workflow_job_node_failure_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobNodeFailureNodesList.as_view(), name='workflow_job_node_failure_nodes_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobNodeAlwaysNodesList.as_view(), name='workflow_job_node_always_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobNodeAlwaysNodesList.as_view(), name='workflow_job_node_always_nodes_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobNodeCredentialsList.as_view(), name='workflow_job_node_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobNodeCredentialsList.as_view(), name='workflow_job_node_credentials_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobNodeLabelsList.as_view(), name='workflow_job_node_labels_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', WorkflowJobNodeInstanceGroupsList.as_view(), name='workflow_job_node_instance_group_list'),
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ['urls']
|
__all__ = ['urls']
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from awx.api.views import (
|
|||||||
WorkflowJobTemplateNodeAlwaysNodesList,
|
WorkflowJobTemplateNodeAlwaysNodesList,
|
||||||
WorkflowJobTemplateNodeCredentialsList,
|
WorkflowJobTemplateNodeCredentialsList,
|
||||||
WorkflowJobTemplateNodeCreateApproval,
|
WorkflowJobTemplateNodeCreateApproval,
|
||||||
|
WorkflowJobTemplateNodeLabelsList,
|
||||||
|
WorkflowJobTemplateNodeInstanceGroupsList,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -21,6 +23,8 @@ urls = [
|
|||||||
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'),
|
re_path(r'^(?P<pk>[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'),
|
re_path(r'^(?P<pk>[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/labels/$', WorkflowJobTemplateNodeLabelsList.as_view(), name='workflow_job_template_node_labels_list'),
|
||||||
|
re_path(r'^(?P<pk>[0-9]+)/instance_groups/$', WorkflowJobTemplateNodeInstanceGroupsList.as_view(), name='workflow_job_template_node_instance_groups_list'),
|
||||||
re_path(r'^(?P<pk>[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'),
|
re_path(r'^(?P<pk>[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -618,6 +618,38 @@ class ScheduleCredentialsList(LaunchConfigCredentialsBase):
|
|||||||
parent_model = models.Schedule
|
parent_model = models.Schedule
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleLabelsList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.Label
|
||||||
|
serializer_class = serializers.LabelSerializer
|
||||||
|
parent_model = models.Schedule
|
||||||
|
relationship = 'labels'
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If a label already exists in the database, attach it instead of erroring out
|
||||||
|
# that it already exists
|
||||||
|
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
||||||
|
existing = models.Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
||||||
|
if existing.exists():
|
||||||
|
existing = existing[0]
|
||||||
|
request.data['id'] = existing.id
|
||||||
|
del request.data['name']
|
||||||
|
del request.data['organization']
|
||||||
|
if models.Label.objects.filter(schedule_labels=self.kwargs['pk']).count() > 100:
|
||||||
|
return Response(
|
||||||
|
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
return super(ScheduleLabelsList, self).post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleInstanceGroupList(SubListAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.InstanceGroup
|
||||||
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
|
parent_model = models.Schedule
|
||||||
|
relationship = 'instance_groups'
|
||||||
|
|
||||||
|
|
||||||
class ScheduleUnifiedJobsList(SubListAPIView):
|
class ScheduleUnifiedJobsList(SubListAPIView):
|
||||||
|
|
||||||
model = models.UnifiedJob
|
model = models.UnifiedJob
|
||||||
@@ -2967,6 +2999,38 @@ class WorkflowJobNodeCredentialsList(SubListAPIView):
|
|||||||
relationship = 'credentials'
|
relationship = 'credentials'
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobNodeLabelsList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.Label
|
||||||
|
serializer_class = serializers.LabelSerializer
|
||||||
|
parent_model = models.WorkflowJobNode
|
||||||
|
relationship = 'labels'
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If a label already exists in the database, attach it instead of erroring out
|
||||||
|
# that it already exists
|
||||||
|
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
||||||
|
existing = models.Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
||||||
|
if existing.exists():
|
||||||
|
existing = existing[0]
|
||||||
|
request.data['id'] = existing.id
|
||||||
|
del request.data['name']
|
||||||
|
del request.data['organization']
|
||||||
|
if models.Label.objects.filter(workflowjobnode_labels=self.kwargs['pk']).count() > 100:
|
||||||
|
return Response(
|
||||||
|
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
return super(WorkflowJobNodeLabelsList, self).post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.InstanceGroup
|
||||||
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
|
parent_model = models.WorkflowJobNode
|
||||||
|
relationship = 'instance_groups'
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeList(ListCreateAPIView):
|
class WorkflowJobTemplateNodeList(ListCreateAPIView):
|
||||||
|
|
||||||
model = models.WorkflowJobTemplateNode
|
model = models.WorkflowJobTemplateNode
|
||||||
@@ -2985,6 +3049,38 @@ class WorkflowJobTemplateNodeCredentialsList(LaunchConfigCredentialsBase):
|
|||||||
parent_model = models.WorkflowJobTemplateNode
|
parent_model = models.WorkflowJobTemplateNode
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobTemplateNodeLabelsList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.Label
|
||||||
|
serializer_class = serializers.LabelSerializer
|
||||||
|
parent_model = models.WorkflowJobTemplateNode
|
||||||
|
relationship = 'labels'
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If a label already exists in the database, attach it instead of erroring out
|
||||||
|
# that it already exists
|
||||||
|
if 'id' not in request.data and 'name' in request.data and 'organization' in request.data:
|
||||||
|
existing = models.Label.objects.filter(name=request.data['name'], organization_id=request.data['organization'])
|
||||||
|
if existing.exists():
|
||||||
|
existing = existing[0]
|
||||||
|
request.data['id'] = existing.id
|
||||||
|
del request.data['name']
|
||||||
|
del request.data['organization']
|
||||||
|
if models.Label.objects.filter(workflowjobtemplatenode_labels=self.kwargs['pk']).count() > 100:
|
||||||
|
return Response(
|
||||||
|
dict(msg=_('Maximum number of labels for {} reached.'.format(self.parent_model._meta.verbose_name_raw))), status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
return super(WorkflowJobTemplateNodeLabelsList, self).post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobTemplateNodeInstanceGroupsList(SubListAttachDetachAPIView):
|
||||||
|
|
||||||
|
model = models.InstanceGroup
|
||||||
|
serializer_class = serializers.InstanceGroupSerializer
|
||||||
|
parent_model = models.WorkflowJobTemplateNode
|
||||||
|
relationship = 'instance_groups'
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
|
class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView):
|
||||||
|
|
||||||
model = models.WorkflowJobTemplateNode
|
model = models.WorkflowJobTemplateNode
|
||||||
|
|||||||
@@ -1924,18 +1924,80 @@ class JobLaunchConfigAccess(BaseAccess):
|
|||||||
|
|
||||||
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
||||||
if isinstance(sub_obj, Credential) and relationship == 'credentials':
|
if isinstance(sub_obj, Credential) and relationship == 'credentials':
|
||||||
return self.user in sub_obj.use_role
|
if not self.user in sub_obj.use_role:
|
||||||
else:
|
logger.debug(
|
||||||
raise NotImplementedError('Only credentials can be attached to launch configurations.')
|
"User {} not allowed access to credential {} for {} {} ({})".format(self.user.username, sub_obj.name, obj.__class__, obj.name, obj.id)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(sub_obj, Label) and relationship == 'labels':
|
||||||
|
if not self.user.can_access(Label, 'read', sub_obj):
|
||||||
|
logger.debug("User {} not allowed access to label {} for {} {} ({})".format(self.user.username, sub_obj.name, obj.__class__, obj.name, obj.id))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(sub_obj, InstanceGroup) and relationship == 'instance_groups':
|
||||||
|
if not sub_obj in self.user.get_queryset(InstanceGroup):
|
||||||
|
logger.debug(
|
||||||
|
"User {} not allowed access to instance_group {} for {} {} ({})".format(self.user.username, sub_obj.name, obj.__class__, obj.name, obj.id)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
raise NotImplementedError('Only credentials, labels and instance groups can be attached to launch configurations.')
|
||||||
|
|
||||||
def can_unattach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
def can_unattach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
||||||
if isinstance(sub_obj, Credential) and relationship == 'credentials':
|
if isinstance(sub_obj, Credential) and relationship == 'credentials':
|
||||||
if skip_sub_obj_read_check:
|
if not skip_sub_obj_read_check:
|
||||||
|
logger.debug(
|
||||||
|
"Skipping check if user {} can access credential {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
else:
|
if not self.user in sub_obj.read_role:
|
||||||
return self.user in sub_obj.read_role
|
logger.debug(
|
||||||
else:
|
"User {} can not read credential {} ({}) for removal from {} {} ({})".format(
|
||||||
raise NotImplementedError('Only credentials can be attached to launch configurations.')
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
if isinstance(sub_obj, Label) and relationship == 'labels':
|
||||||
|
if skip_sub_obj_read_check:
|
||||||
|
logger.debug(
|
||||||
|
"Skipping check if user {} can access label {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
if self.user.can_access(Label, 'read', sub_obj):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read label {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
if isinstance(sub_obj, InstanceGroup) and relationship == 'instance_groups':
|
||||||
|
if skip_sub_obj_read_check:
|
||||||
|
logger.debug(
|
||||||
|
"Skipping check if user {} can access instance_group {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
if sub_obj in self.user.get_queryset(InstanceGroup):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read instance_group {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
raise NotImplementedError('Only credentials, labels and instance groups can be attached to launch configurations.')
|
||||||
|
|
||||||
|
|
||||||
class WorkflowJobTemplateNodeAccess(BaseAccess):
|
class WorkflowJobTemplateNodeAccess(BaseAccess):
|
||||||
@@ -2014,6 +2076,24 @@ class WorkflowJobTemplateNodeAccess(BaseAccess):
|
|||||||
return JobLaunchConfigAccess(self.user).can_attach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
|
return JobLaunchConfigAccess(self.user).can_attach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
|
||||||
elif relationship in ('success_nodes', 'failure_nodes', 'always_nodes'):
|
elif relationship in ('success_nodes', 'failure_nodes', 'always_nodes'):
|
||||||
return self.check_same_WFJT(obj, sub_obj)
|
return self.check_same_WFJT(obj, sub_obj)
|
||||||
|
elif relationship == 'labels':
|
||||||
|
if self.user.can_access(Label, 'read', sub_obj):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read label {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
elif relationship == 'instance_groups':
|
||||||
|
if sub_obj in self.user.get_queryset(InstanceGroup):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read instance_group {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Relationship {} not understood for WFJT nodes.'.format(relationship))
|
raise NotImplementedError('Relationship {} not understood for WFJT nodes.'.format(relationship))
|
||||||
|
|
||||||
@@ -2026,6 +2106,24 @@ class WorkflowJobTemplateNodeAccess(BaseAccess):
|
|||||||
return JobLaunchConfigAccess(self.user).can_unattach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
|
return JobLaunchConfigAccess(self.user).can_unattach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
|
||||||
elif relationship in ('success_nodes', 'failure_nodes', 'always_nodes'):
|
elif relationship in ('success_nodes', 'failure_nodes', 'always_nodes'):
|
||||||
return self.check_same_WFJT(obj, sub_obj)
|
return self.check_same_WFJT(obj, sub_obj)
|
||||||
|
elif relationship == 'labels':
|
||||||
|
if self.user.can_access(Label, 'read', sub_obj):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read label {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
elif relationship == 'instance_groups':
|
||||||
|
if sub_obj in self.user.get_queryset(InstanceGroup):
|
||||||
|
return True
|
||||||
|
logger.debug(
|
||||||
|
"User {} can not read instance_group {} ({}) for removal from {} {} ({})".format(
|
||||||
|
self.user.username, sub_obj.name, sub_obj.id, obj.__class__, obj.name, obj.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Relationship {} not understood for WFJT nodes.'.format(relationship))
|
raise NotImplementedError('Relationship {} not understood for WFJT nodes.'.format(relationship))
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 3.2.13 on 2022-08-16 11:40
|
# Generated by Django 3.2.13 on 2022-08-31 19:15
|
||||||
|
|
||||||
import awx.main.fields
|
import awx.main.fields
|
||||||
import awx.main.utils.polymorphic
|
import awx.main.utils.polymorphic
|
||||||
@@ -21,7 +21,7 @@ class Migration(migrations.Migration):
|
|||||||
default=None,
|
default=None,
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||||
related_name='execution_environment',
|
related_name='joblaunchconfig_as_prompt',
|
||||||
to='main.executionenvironment',
|
to='main.executionenvironment',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -60,21 +60,84 @@ class Migration(migrations.Migration):
|
|||||||
name='ask_timeout_on_launch',
|
name='ask_timeout_on_launch',
|
||||||
field=awx.main.fields.AskForField(blank=True, default=False),
|
field=awx.main.fields.AskForField(blank=True, default=False),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='schedule',
|
||||||
|
name='execution_environment',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||||
|
related_name='schedule_as_prompt',
|
||||||
|
to='main.executionenvironment',
|
||||||
|
),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='schedule',
|
model_name='schedule',
|
||||||
name='labels',
|
name='labels',
|
||||||
field=models.ManyToManyField(related_name='schedule_labels', to='main.Label'),
|
field=models.ManyToManyField(related_name='schedule_labels', to='main.Label'),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowjobnode',
|
||||||
|
name='execution_environment',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||||
|
related_name='workflowjobnode_as_prompt',
|
||||||
|
to='main.executionenvironment',
|
||||||
|
),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='workflowjobnode',
|
model_name='workflowjobnode',
|
||||||
name='labels',
|
name='labels',
|
||||||
field=models.ManyToManyField(related_name='workflowjobnode_labels', to='main.Label'),
|
field=models.ManyToManyField(related_name='workflowjobnode_labels', to='main.Label'),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowjobtemplatenode',
|
||||||
|
name='execution_environment',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||||
|
related_name='workflowjobtemplatenode_as_prompt',
|
||||||
|
to='main.executionenvironment',
|
||||||
|
),
|
||||||
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='workflowjobtemplatenode',
|
model_name='workflowjobtemplatenode',
|
||||||
name='labels',
|
name='labels',
|
||||||
field=models.ManyToManyField(related_name='workflowjobtemplatenode_labels', to='main.Label'),
|
field=models.ManyToManyField(related_name='workflowjobtemplatenode_labels', to='main.Label'),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WorkflowJobTemplateNodeBaseInstanceGroupMembership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('position', models.PositiveIntegerField(db_index=True, default=None, null=True)),
|
||||||
|
('instancegroup', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.instancegroup')),
|
||||||
|
('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.workflowjobtemplatenode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WorkflowJobNodeBaseInstanceGroupMembership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('position', models.PositiveIntegerField(db_index=True, default=None, null=True)),
|
||||||
|
('instancegroup', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.instancegroup')),
|
||||||
|
('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.workflowjobnode')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ScheduleInstanceGroupMembership',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('position', models.PositiveIntegerField(db_index=True, default=None, null=True)),
|
||||||
|
('instancegroup', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.instancegroup')),
|
||||||
|
('schedule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.schedule')),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='JobLaunchConfigInstanceGroupMembership',
|
name='JobLaunchConfigInstanceGroupMembership',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -107,20 +170,33 @@ class Migration(migrations.Migration):
|
|||||||
blank=True, editable=False, related_name='joblaunchconfigs', through='main.JobLaunchConfigInstanceGroupMembership', to='main.InstanceGroup'
|
blank=True, editable=False, related_name='joblaunchconfigs', through='main.JobLaunchConfigInstanceGroupMembership', to='main.InstanceGroup'
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
# added WFJT prompts
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='workflowjobtemplate',
|
model_name='schedule',
|
||||||
name='ask_labels_on_launch',
|
name='instance_groups',
|
||||||
field=awx.main.fields.AskForField(blank=True, default=False),
|
field=awx.main.fields.OrderedManyToManyField(
|
||||||
|
blank=True, editable=False, related_name='schedule_instance_groups', through='main.ScheduleInstanceGroupMembership', to='main.InstanceGroup'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='workflowjobtemplate',
|
model_name='workflowjobnode',
|
||||||
name='ask_skip_tags_on_launch',
|
name='instance_groups',
|
||||||
field=awx.main.fields.AskForField(blank=True, default=False),
|
field=awx.main.fields.OrderedManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
related_name='workflow_job_node_instance_groups',
|
||||||
|
through='main.WorkflowJobNodeBaseInstanceGroupMembership',
|
||||||
|
to='main.InstanceGroup',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='workflowjobtemplate',
|
model_name='workflowjobtemplatenode',
|
||||||
name='ask_tags_on_launch',
|
name='instance_groups',
|
||||||
field=awx.main.fields.AskForField(blank=True, default=False),
|
field=awx.main.fields.OrderedManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
related_name='workflow_job_template_node_instance_groups',
|
||||||
|
through='main.WorkflowJobTemplateNodeBaseInstanceGroupMembership',
|
||||||
|
to='main.InstanceGroup',
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -456,3 +456,36 @@ class JobLaunchConfigInstanceGroupMembership(models.Model):
|
|||||||
default=None,
|
default=None,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ScheduleInstanceGroupMembership(models.Model):
|
||||||
|
|
||||||
|
schedule = models.ForeignKey('Schedule', on_delete=models.CASCADE)
|
||||||
|
instancegroup = models.ForeignKey('InstanceGroup', on_delete=models.CASCADE)
|
||||||
|
position = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobTemplateNodeBaseInstanceGroupMembership(models.Model):
|
||||||
|
|
||||||
|
schedule = models.ForeignKey('WorkflowJobTemplateNode', on_delete=models.CASCADE)
|
||||||
|
instancegroup = models.ForeignKey('InstanceGroup', on_delete=models.CASCADE)
|
||||||
|
position = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowJobNodeBaseInstanceGroupMembership(models.Model):
|
||||||
|
|
||||||
|
schedule = models.ForeignKey('WorkflowJobNode', on_delete=models.CASCADE)
|
||||||
|
instancegroup = models.ForeignKey('InstanceGroup', on_delete=models.CASCADE)
|
||||||
|
position = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1007,6 +1007,10 @@ class LaunchTimeConfig(LaunchTimeConfigBase):
|
|||||||
# Labels needed for non-unified job / unified JT models
|
# Labels needed for non-unified job / unified JT models
|
||||||
labels = models.ManyToManyField('Label', related_name='%(class)s_labels')
|
labels = models.ManyToManyField('Label', related_name='%(class)s_labels')
|
||||||
|
|
||||||
|
execution_environment = models.ForeignKey(
|
||||||
|
'ExecutionEnvironment', null=True, blank=True, default=None, on_delete=polymorphic.SET_NULL, related_name='%(class)s_as_prompt'
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_vars(self):
|
def extra_vars(self):
|
||||||
return self.extra_data
|
return self.extra_data
|
||||||
@@ -1054,10 +1058,6 @@ class JobLaunchConfig(LaunchTimeConfig):
|
|||||||
'InstanceGroup', related_name='%(class)ss', blank=True, editable=False, through='JobLaunchConfigInstanceGroupMembership'
|
'InstanceGroup', related_name='%(class)ss', blank=True, editable=False, through='JobLaunchConfigInstanceGroupMembership'
|
||||||
)
|
)
|
||||||
|
|
||||||
execution_environment = models.ForeignKey(
|
|
||||||
'ExecutionEnvironment', null=True, blank=True, default=None, on_delete=polymorphic.SET_NULL, related_name='execution_environment'
|
|
||||||
)
|
|
||||||
|
|
||||||
def has_user_prompts(self, template):
|
def has_user_prompts(self, template):
|
||||||
"""
|
"""
|
||||||
Returns True if any fields exist in the launch config that are
|
Returns True if any fields exist in the launch config that are
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
|
from awx.main.fields import OrderedManyToManyField
|
||||||
from awx.main.models.base import PrimordialModel
|
from awx.main.models.base import PrimordialModel
|
||||||
from awx.main.models.jobs import LaunchTimeConfig
|
from awx.main.models.jobs import LaunchTimeConfig
|
||||||
from awx.main.utils import ignore_inventory_computed_fields
|
from awx.main.utils import ignore_inventory_computed_fields
|
||||||
@@ -83,6 +84,13 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
|
|||||||
)
|
)
|
||||||
rrule = models.TextField(help_text=_("A value representing the schedules iCal recurrence rule."))
|
rrule = models.TextField(help_text=_("A value representing the schedules iCal recurrence rule."))
|
||||||
next_run = models.DateTimeField(null=True, default=None, editable=False, help_text=_("The next time that the scheduled action will run."))
|
next_run = models.DateTimeField(null=True, default=None, editable=False, help_text=_("The next time that the scheduled action will run."))
|
||||||
|
instance_groups = OrderedManyToManyField(
|
||||||
|
'InstanceGroup',
|
||||||
|
related_name='schedule_instance_groups',
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
through='ScheduleInstanceGroupMembership',
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_zoneinfo(cls):
|
def get_zoneinfo(cls):
|
||||||
|
|||||||
@@ -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.notifications import NotificationTemplate, JobNotificationMixin
|
||||||
from awx.main.models.base import CreatedModifiedModel, VarsDictProperty
|
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.models.rbac import ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR
|
||||||
from awx.main.fields import ImplicitRoleField, JSONBlob
|
from awx.main.fields import ImplicitRoleField, AskForField, JSONBlob, OrderedManyToManyField
|
||||||
from awx.main.models.mixins import (
|
from awx.main.models.mixins import (
|
||||||
ResourceMixin,
|
ResourceMixin,
|
||||||
SurveyJobTemplateMixin,
|
SurveyJobTemplateMixin,
|
||||||
@@ -167,6 +167,13 @@ class WorkflowJobTemplateNode(WorkflowNodeBase):
|
|||||||
blank=False,
|
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.'),
|
help_text=_('An identifier for this node that is unique within its workflow. ' 'It is copied to workflow job nodes corresponding to this node.'),
|
||||||
)
|
)
|
||||||
|
instance_groups = OrderedManyToManyField(
|
||||||
|
'InstanceGroup',
|
||||||
|
related_name='workflow_job_template_node_instance_groups',
|
||||||
|
blank=True,
|
||||||
|
editable=False,
|
||||||
|
through='WorkflowJobTemplateNodeBaseInstanceGroupMembership',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -250,6 +257,9 @@ class WorkflowJobNode(WorkflowNodeBase):
|
|||||||
blank=True, # blank denotes pre-migration job nodes
|
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.'),
|
help_text=_('An identifier coresponding to the workflow job template node that this node was created from.'),
|
||||||
)
|
)
|
||||||
|
instance_groups = OrderedManyToManyField(
|
||||||
|
'InstanceGroup', related_name='workflow_job_node_instance_groups', blank=True, editable=False, through='WorkflowJobNodeBaseInstanceGroupMembership'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
|
|||||||
@@ -42,17 +42,37 @@ options:
|
|||||||
- Optional description of this schedule.
|
- Optional description of this schedule.
|
||||||
required: False
|
required: False
|
||||||
type: str
|
type: str
|
||||||
|
execution_environment:
|
||||||
|
description:
|
||||||
|
- Execution Environment applied as a prompt, assuming jot template prompts for execution environment
|
||||||
|
type: str
|
||||||
extra_data:
|
extra_data:
|
||||||
description:
|
description:
|
||||||
- Specify C(extra_vars) for the template.
|
- Specify C(extra_vars) for the template.
|
||||||
required: False
|
required: False
|
||||||
type: dict
|
type: dict
|
||||||
default: {}
|
default: {}
|
||||||
|
forks:
|
||||||
|
description:
|
||||||
|
- Forks applied as a prompt, assuming job template prompts for forks
|
||||||
|
type: int
|
||||||
|
instance_groups:
|
||||||
|
description:
|
||||||
|
- List of Instance Groups applied as a prompt, assuming job template prompts for instance groups
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
inventory:
|
inventory:
|
||||||
description:
|
description:
|
||||||
- Inventory applied as a prompt, assuming job template prompts for inventory
|
- Inventory applied as a prompt, assuming job template prompts for inventory
|
||||||
required: False
|
required: False
|
||||||
type: str
|
type: str
|
||||||
|
job_slice_count:
|
||||||
|
description:
|
||||||
|
- Job Slice Count applied as a prompt, assuming job template prompts for job slice count
|
||||||
|
type: int
|
||||||
|
labels:
|
||||||
|
description:
|
||||||
|
- List of labels applied as a prompt, assuming job template prompts for labels
|
||||||
credentials:
|
credentials:
|
||||||
description:
|
description:
|
||||||
- List of credentials applied as a prompt, assuming job template prompts for credentials
|
- List of credentials applied as a prompt, assuming job template prompts for credentials
|
||||||
@@ -63,6 +83,10 @@ options:
|
|||||||
- Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true.
|
- Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true.
|
||||||
required: False
|
required: False
|
||||||
type: str
|
type: str
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Timeout applied as a prompt, assuming job template prompts for timeout
|
||||||
|
type: int
|
||||||
job_type:
|
job_type:
|
||||||
description:
|
description:
|
||||||
- The job type to use for the job template.
|
- The job type to use for the job template.
|
||||||
@@ -176,8 +200,14 @@ def main():
|
|||||||
name=dict(required=True),
|
name=dict(required=True),
|
||||||
new_name=dict(),
|
new_name=dict(),
|
||||||
description=dict(),
|
description=dict(),
|
||||||
|
execution_environment=dict(type='str'),
|
||||||
extra_data=dict(type='dict'),
|
extra_data=dict(type='dict'),
|
||||||
|
forks=dict(type='int'),
|
||||||
|
instance_groups=dict(type='list', elements='str'),
|
||||||
inventory=dict(),
|
inventory=dict(),
|
||||||
|
job_slice_count=dict(type='int'),
|
||||||
|
labels=dict(type='list', elements='str'),
|
||||||
|
timeout=dict(type='int'),
|
||||||
credentials=dict(type='list', elements='str'),
|
credentials=dict(type='list', elements='str'),
|
||||||
scm_branch=dict(),
|
scm_branch=dict(),
|
||||||
job_type=dict(choices=['run', 'check']),
|
job_type=dict(choices=['run', 'check']),
|
||||||
@@ -200,8 +230,14 @@ def main():
|
|||||||
name = module.params.get('name')
|
name = module.params.get('name')
|
||||||
new_name = module.params.get("new_name")
|
new_name = module.params.get("new_name")
|
||||||
description = module.params.get('description')
|
description = module.params.get('description')
|
||||||
|
execution_environment = module.params.get('execution_environment')
|
||||||
extra_data = module.params.get('extra_data')
|
extra_data = module.params.get('extra_data')
|
||||||
|
forks = module.params.get('forks')
|
||||||
|
instance_groups = module.params.get('instance_groups')
|
||||||
inventory = module.params.get('inventory')
|
inventory = module.params.get('inventory')
|
||||||
|
job_slice_count = module.params.get('job_slice_count')
|
||||||
|
labels = module.params.get('labels')
|
||||||
|
timeout = module.params.get('timeout')
|
||||||
credentials = module.params.get('credentials')
|
credentials = module.params.get('credentials')
|
||||||
scm_branch = module.params.get('scm_branch')
|
scm_branch = module.params.get('scm_branch')
|
||||||
job_type = module.params.get('job_type')
|
job_type = module.params.get('job_type')
|
||||||
@@ -238,6 +274,28 @@ def main():
|
|||||||
for item in credentials:
|
for item in credentials:
|
||||||
association_fields['credentials'].append(module.resolve_name_to_id('credentials', item))
|
association_fields['credentials'].append(module.resolve_name_to_id('credentials', item))
|
||||||
|
|
||||||
|
# We need to clear out the name from the search fields so we can use name_or_id in the following searches
|
||||||
|
if 'name' in search_fields:
|
||||||
|
del search_fields['name']
|
||||||
|
|
||||||
|
if labels is not None:
|
||||||
|
association_fields['labels'] = []
|
||||||
|
for item in labels:
|
||||||
|
label_id = module.get_one('labels', name_or_id=item, **{'data': search_fields})
|
||||||
|
if label_id is None:
|
||||||
|
module.fail_json(msg='Could not find label entry with name {0}'.format(item))
|
||||||
|
else:
|
||||||
|
association_fields['labels'].append(label_id['id'])
|
||||||
|
|
||||||
|
if instance_groups is not None:
|
||||||
|
association_fields['instance_groups'] = []
|
||||||
|
for item in instance_groups:
|
||||||
|
instance_group_id = module.get_one('instance_groups', name_or_id=item, **{'data': search_fields})
|
||||||
|
if instance_group_id is None:
|
||||||
|
module.fail_json(msg='Could not find instance_group entry with name {0}'.format(item))
|
||||||
|
else:
|
||||||
|
association_fields['instance_groups'].append(instance_group_id['id'])
|
||||||
|
|
||||||
# Create the data that gets sent for create and update
|
# Create the data that gets sent for create and update
|
||||||
new_fields = {}
|
new_fields = {}
|
||||||
if rrule is not None:
|
if rrule is not None:
|
||||||
@@ -267,6 +325,22 @@ def main():
|
|||||||
new_fields['unified_job_template'] = unified_job_template_id
|
new_fields['unified_job_template'] = unified_job_template_id
|
||||||
if enabled is not None:
|
if enabled is not None:
|
||||||
new_fields['enabled'] = enabled
|
new_fields['enabled'] = enabled
|
||||||
|
if forks is not None:
|
||||||
|
new_fields['forks'] = forks
|
||||||
|
if job_slice_count is not None:
|
||||||
|
new_fields['job_slice_count'] = job_slice_count
|
||||||
|
if timeout is not None:
|
||||||
|
new_fields['timeout'] = timeout
|
||||||
|
|
||||||
|
if execution_environment is not None:
|
||||||
|
if execution_environment == '':
|
||||||
|
new_fields['execution_environment'] = ''
|
||||||
|
else:
|
||||||
|
ee = module.get_one('execution_environments', name_or_id=execution_environment, **{'data': search_fields})
|
||||||
|
if ee is None:
|
||||||
|
module.fail_json(msg='could not find execution_environment entry with name {0}'.format(execution_environment))
|
||||||
|
else:
|
||||||
|
new_fields['execution_environment'] = ee['id']
|
||||||
|
|
||||||
if state == 'absent':
|
if state == 'absent':
|
||||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||||
|
|||||||
@@ -152,6 +152,30 @@ options:
|
|||||||
- Uniqueness is not handled rigorously.
|
- Uniqueness is not handled rigorously.
|
||||||
type: list
|
type: list
|
||||||
elements: str
|
elements: str
|
||||||
|
execution_environment:
|
||||||
|
description:
|
||||||
|
- Execution Environment applied as a prompt, assuming jot template prompts for execution environment
|
||||||
|
type: str
|
||||||
|
forks:
|
||||||
|
description:
|
||||||
|
- Forks applied as a prompt, assuming job template prompts for forks
|
||||||
|
type: int
|
||||||
|
instance_groups:
|
||||||
|
description:
|
||||||
|
- List of Instance Groups applied as a prompt, assuming job template prompts for instance groups
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
job_slice_count:
|
||||||
|
description:
|
||||||
|
- Job Slice Count applied as a prompt, assuming job template prompts for job slice count
|
||||||
|
type: int
|
||||||
|
labels:
|
||||||
|
description:
|
||||||
|
- List of labels applied as a prompt, assuming job template prompts for labels
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Timeout applied as a prompt, assuming job template prompts for timeout
|
||||||
|
type: int
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Desired state of the resource.
|
- Desired state of the resource.
|
||||||
@@ -255,6 +279,12 @@ def main():
|
|||||||
always_nodes=dict(type='list', elements='str'),
|
always_nodes=dict(type='list', elements='str'),
|
||||||
failure_nodes=dict(type='list', elements='str'),
|
failure_nodes=dict(type='list', elements='str'),
|
||||||
credentials=dict(type='list', elements='str'),
|
credentials=dict(type='list', elements='str'),
|
||||||
|
execution_environment=dict(type='str'),
|
||||||
|
forks=dict(type='int'),
|
||||||
|
instance_groups=dict(type='list', elements='str'),
|
||||||
|
job_slice_count=dict(type='int'),
|
||||||
|
labels=dict(type='list', elements='str'),
|
||||||
|
timeout=dict(type='int'),
|
||||||
state=dict(choices=['present', 'absent'], default='present'),
|
state=dict(choices=['present', 'absent'], default='present'),
|
||||||
)
|
)
|
||||||
mutually_exclusive = [("unified_job_template", "approval_node")]
|
mutually_exclusive = [("unified_job_template", "approval_node")]
|
||||||
@@ -327,32 +357,44 @@ def main():
|
|||||||
'diff_mode',
|
'diff_mode',
|
||||||
'verbosity',
|
'verbosity',
|
||||||
'all_parents_must_converge',
|
'all_parents_must_converge',
|
||||||
|
'forks',
|
||||||
|
'job_slice_count',
|
||||||
|
'timeout',
|
||||||
):
|
):
|
||||||
field_val = module.params.get(field_name)
|
field_val = module.params.get(field_name)
|
||||||
if field_val:
|
if field_val:
|
||||||
new_fields[field_name] = field_val
|
new_fields[field_name] = field_val
|
||||||
|
|
||||||
association_fields = {}
|
association_fields = {}
|
||||||
for association in ('always_nodes', 'success_nodes', 'failure_nodes', 'credentials'):
|
for association in ('always_nodes', 'success_nodes', 'failure_nodes', 'credentials', 'instance_groups', 'labels'):
|
||||||
name_list = module.params.get(association)
|
name_list = module.params.get(association)
|
||||||
if name_list is None:
|
if name_list is None:
|
||||||
continue
|
continue
|
||||||
id_list = []
|
id_list = []
|
||||||
for sub_name in name_list:
|
for sub_name in name_list:
|
||||||
if association == 'credentials':
|
if association in ['credentials', 'instance_groups', 'labels']:
|
||||||
endpoint = 'credentials'
|
sub_obj = module.get_one(association, name_or_id=sub_name)
|
||||||
lookup_data = {'name': sub_name}
|
|
||||||
else:
|
else:
|
||||||
endpoint = 'workflow_job_template_nodes'
|
endpoint = 'workflow_job_template_nodes'
|
||||||
lookup_data = {'identifier': sub_name}
|
lookup_data = {'identifier': sub_name}
|
||||||
if workflow_job_template_id:
|
if workflow_job_template_id:
|
||||||
lookup_data['workflow_job_template'] = workflow_job_template_id
|
lookup_data['workflow_job_template'] = workflow_job_template_id
|
||||||
sub_obj = module.get_one(endpoint, **{'data': lookup_data})
|
sub_obj = module.get_one(endpoint, **{'data': lookup_data})
|
||||||
if sub_obj is None:
|
if sub_obj is None:
|
||||||
module.fail_json(msg='Could not find {0} entry with name {1}'.format(association, sub_name))
|
module.fail_json(msg='Could not find {0} entry with name {1}'.format(association, sub_name))
|
||||||
id_list.append(sub_obj['id'])
|
id_list.append(sub_obj['id'])
|
||||||
if id_list:
|
association_fields[association] = id_list
|
||||||
association_fields[association] = id_list
|
|
||||||
|
execution_environment = module.params.get('execution_environment')
|
||||||
|
if execution_environment is not None:
|
||||||
|
if execution_environment == '':
|
||||||
|
new_fields['execution_environment'] = ''
|
||||||
|
else:
|
||||||
|
ee = module.get_one('execution_environments', name_or_id=execution_environment)
|
||||||
|
if ee is None:
|
||||||
|
module.fail_json(msg='could not find execution_environment entry with name {0}'.format(execution_environment))
|
||||||
|
else:
|
||||||
|
new_fields['execution_environment'] = ee['id']
|
||||||
|
|
||||||
# In the case of a new object, the utils need to know it is a node
|
# In the case of a new object, the utils need to know it is a node
|
||||||
new_fields['type'] = 'workflow_job_template_node'
|
new_fields['type'] = 'workflow_job_template_node'
|
||||||
|
|||||||
@@ -7,11 +7,17 @@
|
|||||||
set_fact:
|
set_fact:
|
||||||
org_name: "AWX-Collection-tests-organization-org-{{ test_id }}"
|
org_name: "AWX-Collection-tests-organization-org-{{ test_id }}"
|
||||||
sched1: "AWX-Collection-tests-schedule-sched1-{{ test_id }}"
|
sched1: "AWX-Collection-tests-schedule-sched1-{{ test_id }}"
|
||||||
|
sched2: "AWX-Collection-tests-schedule-sched2-{{ test_id }}"
|
||||||
cred1: "AWX-Collection-tests-schedule-cred1-{{ test_id }}"
|
cred1: "AWX-Collection-tests-schedule-cred1-{{ test_id }}"
|
||||||
proj1: "AWX-Collection-tests-schedule-proj1-{{ test_id }}"
|
proj1: "AWX-Collection-tests-schedule-proj1-{{ test_id }}"
|
||||||
proj2: "AWX-Collection-tests-schedule-proj2-{{ test_id }}"
|
proj2: "AWX-Collection-tests-schedule-proj2-{{ test_id }}"
|
||||||
jt1: "AWX-Collection-tests-schedule-jt1-{{ test_id }}"
|
jt1: "AWX-Collection-tests-schedule-jt1-{{ test_id }}"
|
||||||
jt2: "AWX-Collection-tests-schedule-jt1-{{ test_id }}"
|
jt2: "AWX-Collection-tests-schedule-jt1-{{ test_id }}"
|
||||||
|
ee1: "AWX-Collection-tests-schedule-ee1-{{ test_id }}"
|
||||||
|
label1: "AWX-Collection-tests-schedule-l1-{{ test_id }}"
|
||||||
|
label2: "AWX-Collection-tests-schedule-l2-{{ test_id }}"
|
||||||
|
ig1: "AWX-Collection-tests-schedule-ig1-{{ test_id }}"
|
||||||
|
ig2: "AWX-Collection-tests-schedule-ig2-{{ test_id }}"
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: Try to create without an rrule
|
- name: Try to create without an rrule
|
||||||
@@ -124,6 +130,12 @@
|
|||||||
ask_limit_on_launch: true
|
ask_limit_on_launch: true
|
||||||
ask_diff_mode_on_launch: true
|
ask_diff_mode_on_launch: true
|
||||||
ask_verbosity_on_launch: true
|
ask_verbosity_on_launch: true
|
||||||
|
ask_execution_environment_on_launch: true
|
||||||
|
ask_forks_on_launch: true
|
||||||
|
ask_instance_groups_on_launch: true
|
||||||
|
ask_job_slice_count_on_launch: true
|
||||||
|
ask_labels_on_launch: true
|
||||||
|
ask_timeout_on_launch: true
|
||||||
job_type: run
|
job_type: run
|
||||||
state: present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
@@ -132,14 +144,33 @@
|
|||||||
that:
|
that:
|
||||||
- "result is changed"
|
- "result is changed"
|
||||||
|
|
||||||
|
- name: Create labels
|
||||||
|
label:
|
||||||
|
name: "{{ item }}"
|
||||||
|
organization: "{{ org_name }}"
|
||||||
|
loop:
|
||||||
|
- "{{ label1 }}"
|
||||||
|
- "{{ label2 }}"
|
||||||
|
|
||||||
|
- name: Create an execution environment
|
||||||
|
execution_environment:
|
||||||
|
name: "{{ ee1 }}"
|
||||||
|
image: "junk"
|
||||||
|
|
||||||
|
- name: Create instance groups
|
||||||
|
instance_group:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
|
||||||
- name: Create with options that the JT does support
|
- name: Create with options that the JT does support
|
||||||
schedule:
|
schedule:
|
||||||
name: "{{ sched1 }}"
|
name: "{{ sched2 }}"
|
||||||
state: present
|
state: present
|
||||||
unified_job_template: "{{ jt1 }}"
|
unified_job_template: "{{ jt1 }}"
|
||||||
rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
|
rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
|
||||||
description: "This hopefully will not work"
|
description: "This hopefully will work"
|
||||||
extra_data:
|
extra_data:
|
||||||
some: var
|
some: var
|
||||||
inventory: Demo Inventory
|
inventory: Demo Inventory
|
||||||
@@ -153,6 +184,33 @@
|
|||||||
diff_mode: true
|
diff_mode: true
|
||||||
verbosity: 4
|
verbosity: 4
|
||||||
enabled: true
|
enabled: true
|
||||||
|
execution_environment: "{{ ee1 }}"
|
||||||
|
forks: 10
|
||||||
|
instance_groups:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
job_slice_count: 10
|
||||||
|
labels:
|
||||||
|
- "{{ label1 }}"
|
||||||
|
- "{{ label2 }}"
|
||||||
|
timeout: 10
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result is changed"
|
||||||
|
|
||||||
|
- name: Reset some options
|
||||||
|
schedule:
|
||||||
|
name: "{{ sched2 }}"
|
||||||
|
state: present
|
||||||
|
execution_environment: ""
|
||||||
|
forks: 1
|
||||||
|
instance_groups: []
|
||||||
|
job_slice_count: 1
|
||||||
|
labels: []
|
||||||
|
timeout: 60
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
@@ -163,7 +221,7 @@
|
|||||||
- name: Disable a schedule
|
- name: Disable a schedule
|
||||||
schedule:
|
schedule:
|
||||||
name: "{{ sched1 }}"
|
name: "{{ sched1 }}"
|
||||||
unified_job_template: "{{ jt1 }}"
|
unified_job_template: "Demo Job Template"
|
||||||
state: present
|
state: present
|
||||||
enabled: "false"
|
enabled: "false"
|
||||||
register: result
|
register: result
|
||||||
@@ -213,42 +271,48 @@
|
|||||||
- result is changed
|
- result is changed
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Delete the schedule
|
- name: Delete the schedules
|
||||||
schedule:
|
schedule:
|
||||||
name: "{{ sched1 }}"
|
name: "{{ item }}"
|
||||||
state: absent
|
state: absent
|
||||||
|
loop:
|
||||||
|
- "{{ sched1 }}"
|
||||||
|
- "{{ sched2 }}"
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
- name: Delete the jt
|
- name: Delete the jt1
|
||||||
job_template:
|
job_template:
|
||||||
name: "{{ jt1 }}"
|
name: "{{ jt1 }}"
|
||||||
project: "{{ proj1 }}"
|
project: "{{ proj1 }}"
|
||||||
playbook: hello_world.yml
|
playbook: hello_world.yml
|
||||||
state: absent
|
state: absent
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
- name: Delete the jt
|
- name: Delete the jt2
|
||||||
job_template:
|
job_template:
|
||||||
name: "{{ jt2 }}"
|
name: "{{ jt2 }}"
|
||||||
project: "{{ proj2 }}"
|
project: "{{ proj2 }}"
|
||||||
playbook: hello_world.yml
|
playbook: hello_world.yml
|
||||||
state: absent
|
state: absent
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
- name: Delete the Project
|
- name: Delete the Project2
|
||||||
project:
|
project:
|
||||||
name: "{{ proj2 }}"
|
name: "{{ proj2 }}"
|
||||||
organization: "{{ org_name }}"
|
organization: "{{ org_name }}"
|
||||||
state: absent
|
state: absent
|
||||||
scm_type: git
|
scm_type: git
|
||||||
scm_url: https://github.com/ansible/ansible-tower-samples.git
|
scm_url: https://github.com/ansible/ansible-tower-samples.git
|
||||||
register: result
|
ignore_errors: True
|
||||||
|
|
||||||
- name: Delete the Project
|
- name: Delete the Project1
|
||||||
project:
|
project:
|
||||||
name: "{{ proj1 }}"
|
name: "{{ proj1 }}"
|
||||||
organization: Default
|
organization: Default
|
||||||
state: absent
|
state: absent
|
||||||
scm_type: git
|
scm_type: git
|
||||||
scm_url: https://github.com/ansible/ansible-tower-samples.git
|
scm_url: https://github.com/ansible/ansible-tower-samples.git
|
||||||
register: result
|
ignore_errors: True
|
||||||
|
|
||||||
- name: Delete Credential1
|
- name: Delete Credential1
|
||||||
credential:
|
credential:
|
||||||
@@ -256,9 +320,28 @@
|
|||||||
organization: Default
|
organization: Default
|
||||||
credential_type: Red Hat Ansible Automation Platform
|
credential_type: Red Hat Ansible Automation Platform
|
||||||
state: absent
|
state: absent
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
# Labels can not be deleted
|
||||||
|
|
||||||
|
- name: Delete an execution environment
|
||||||
|
execution_environment:
|
||||||
|
name: "{{ ee1 }}"
|
||||||
|
image: "junk"
|
||||||
|
state: absent
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
- name: Delete instance groups
|
||||||
|
instance_group:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
- name: "Remove the organization"
|
- name: "Remove the organization"
|
||||||
organization:
|
organization:
|
||||||
name: "{{ org_name }}"
|
name: "{{ org_name }}"
|
||||||
state: absent
|
state: absent
|
||||||
register: result
|
ignore_errors: True
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
project_inv: "AWX-Collection-tests-inventory_source-inv-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
|
project_inv: "AWX-Collection-tests-inventory_source-inv-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
|
||||||
project_inv_source: "AWX-Collection-tests-inventory_source-inv-source-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
|
project_inv_source: "AWX-Collection-tests-inventory_source-inv-source-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}"
|
||||||
github_webhook_credential_name: "AWX-Collection-tests-credential-webhook-{{ test_id }}_github"
|
github_webhook_credential_name: "AWX-Collection-tests-credential-webhook-{{ test_id }}_github"
|
||||||
|
ee1: "AWX-Collection-tests-workflow_job_template-ee1-{{ test_id }}"
|
||||||
|
label1: "AWX-Collection-tests-workflow_job_template-l1-{{ test_id }}"
|
||||||
|
label2: "AWX-Collection-tests-workflow_job_template-l2-{{ test_id }}"
|
||||||
|
ig1: "AWX-Collection-tests-workflow_job_template-ig1-{{ test_id }}"
|
||||||
|
ig2: "AWX-Collection-tests-workflow_job_template-ig2-{{ test_id }}"
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: "Create a new organization"
|
- name: "Create a new organization"
|
||||||
@@ -181,6 +186,12 @@
|
|||||||
playbook: hello_world.yml
|
playbook: hello_world.yml
|
||||||
job_type: run
|
job_type: run
|
||||||
state: present
|
state: present
|
||||||
|
ask_execution_environment_on_launch: true
|
||||||
|
ask_forks_on_launch: true
|
||||||
|
ask_instance_groups_on_launch: true
|
||||||
|
ask_timeout_on_launch: true
|
||||||
|
ask_job_slice_count_on_launch: true
|
||||||
|
ask_labels_on_launch: true
|
||||||
register: jt2_name_result
|
register: jt2_name_result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -198,6 +209,12 @@
|
|||||||
state: present
|
state: present
|
||||||
survey_enabled: true
|
survey_enabled: true
|
||||||
survey_spec: '{"spec": [{"index": 0, "question_name": "my question?", "default": "mydef", "variable": "myvar", "type": "text", "required": false}], "description": "test", "name": "test"}'
|
survey_spec: '{"spec": [{"index": 0, "question_name": "my question?", "default": "mydef", "variable": "myvar", "type": "text", "required": false}], "description": "test", "name": "test"}'
|
||||||
|
ask_execution_environment_on_launch: true
|
||||||
|
ask_forks_on_launch: true
|
||||||
|
ask_instance_groups_on_launch: true
|
||||||
|
ask_timeout_on_launch: true
|
||||||
|
ask_job_slice_count_on_launch: true
|
||||||
|
ask_labels_on_launch: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
@@ -255,6 +272,26 @@
|
|||||||
that:
|
that:
|
||||||
- "result is changed"
|
- "result is changed"
|
||||||
|
|
||||||
|
- name: Create labels
|
||||||
|
label:
|
||||||
|
name: "{{ item }}"
|
||||||
|
organization: "{{ org_name }}"
|
||||||
|
loop:
|
||||||
|
- "{{ label1 }}"
|
||||||
|
- "{{ label2 }}"
|
||||||
|
|
||||||
|
- name: Create an execution environment
|
||||||
|
execution_environment:
|
||||||
|
name: "{{ ee1 }}"
|
||||||
|
image: "junk"
|
||||||
|
|
||||||
|
- name: Create instance groups
|
||||||
|
instance_group:
|
||||||
|
name: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
|
||||||
# Node actions do what the schema command used to do
|
# Node actions do what the schema command used to do
|
||||||
- name: Create leaf node
|
- name: Create leaf node
|
||||||
workflow_job_template_node:
|
workflow_job_template_node:
|
||||||
@@ -262,6 +299,39 @@
|
|||||||
unified_job_template: "{{ jt2_name }}"
|
unified_job_template: "{{ jt2_name }}"
|
||||||
lookup_organization: "{{ org_name }}"
|
lookup_organization: "{{ org_name }}"
|
||||||
workflow: "{{ wfjt_name }}"
|
workflow: "{{ wfjt_name }}"
|
||||||
|
execution_environment: "{{ ee1 }}"
|
||||||
|
forks: 12
|
||||||
|
instance_groups:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
job_slice_count: 2
|
||||||
|
labels:
|
||||||
|
- "{{ label1 }}"
|
||||||
|
- "{{ label2 }}"
|
||||||
|
timeout: 23
|
||||||
|
register: results
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "results is changed"
|
||||||
|
|
||||||
|
- name: Update prompts on leaf node
|
||||||
|
workflow_job_template_node:
|
||||||
|
identifier: leaf
|
||||||
|
unified_job_template: "{{ jt2_name }}"
|
||||||
|
lookup_organization: "{{ org_name }}"
|
||||||
|
workflow: "{{ wfjt_name }}"
|
||||||
|
execution_environment: ""
|
||||||
|
forks: 1
|
||||||
|
instance_groups: []
|
||||||
|
job_slice_count: 1
|
||||||
|
labels: []
|
||||||
|
timeout: 10
|
||||||
|
register: results
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "results is changed"
|
||||||
|
|
||||||
- name: Create root node
|
- name: Create root node
|
||||||
workflow_job_template_node:
|
workflow_job_template_node:
|
||||||
@@ -815,6 +885,24 @@
|
|||||||
state: absent
|
state: absent
|
||||||
ignore_errors: True
|
ignore_errors: True
|
||||||
|
|
||||||
|
# Labels can not be deleted
|
||||||
|
|
||||||
|
- name: Delete an execution environment
|
||||||
|
execution_environment:
|
||||||
|
name: "{{ ee1 }}"
|
||||||
|
image: "junk"
|
||||||
|
state: absent
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
|
- name: Delete instance groups
|
||||||
|
instance_group:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: absent
|
||||||
|
loop:
|
||||||
|
- "{{ ig1 }}"
|
||||||
|
- "{{ ig2 }}"
|
||||||
|
ignore_errors: True
|
||||||
|
|
||||||
- name: "Remove the organization"
|
- name: "Remove the organization"
|
||||||
organization:
|
organization:
|
||||||
name: "{{ org_name }}"
|
name: "{{ org_name }}"
|
||||||
|
|||||||
Reference in New Issue
Block a user