Skip to content

Commit

Permalink
Merge pull request #4369 from AlanCoding/workflow_limit
Browse files Browse the repository at this point in the history
Implementation of WFJT limit & SCM_branch prompting

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
  • Loading branch information
softwarefactory-project-zuul[bot] authored Sep 16, 2019
2 parents 3139bc9 + e3c1189 commit b5fa160
Show file tree
Hide file tree
Showing 22 changed files with 413 additions and 101 deletions.
40 changes: 35 additions & 5 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3314,11 +3314,14 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
'admin', 'execute',
{'copy': 'organization.workflow_admin'}
]
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)

class Meta:
model = WorkflowJobTemplate
fields = ('*', 'extra_vars', 'organization', 'survey_enabled', 'allow_simultaneous',
'ask_variables_on_launch', 'inventory', 'ask_inventory_on_launch',)
'ask_variables_on_launch', 'inventory', 'limit', 'scm_branch',
'ask_inventory_on_launch', 'ask_scm_branch_on_launch', 'ask_limit_on_launch',)

def get_related(self, obj):
res = super(WorkflowJobTemplateSerializer, self).get_related(obj)
Expand All @@ -3344,6 +3347,22 @@ def get_related(self, obj):
def validate_extra_vars(self, value):
return vars_validate_or_raise(value)

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'):
if field_name in attrs:
setattr(mock_obj, field_name, attrs[field_name])
attrs.pop(field_name)

# Model `.save` needs the container dict, not the pseudo fields
if mock_obj.char_prompts:
attrs['char_prompts'] = mock_obj.char_prompts

return attrs


class WorkflowJobTemplateWithSpecSerializer(WorkflowJobTemplateSerializer):
'''
Expand All @@ -3356,13 +3375,15 @@ class Meta:


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)

class Meta:
model = WorkflowJob
fields = ('*', 'workflow_job_template', 'extra_vars', 'allow_simultaneous',
'job_template', 'is_sliced_job',
'-execution_node', '-event_processing_finished', '-controller_node',
'inventory',)
'inventory', 'limit', 'scm_branch',)

def get_related(self, obj):
res = super(WorkflowJobSerializer, self).get_related(obj)
Expand Down Expand Up @@ -3596,7 +3617,7 @@ def validate(self, attrs):
if errors:
raise serializers.ValidationError(errors)

# Model `.save` needs the container dict, not the psuedo fields
# Model `.save` needs the container dict, not the pseudo fields
if mock_obj.char_prompts:
attrs['char_prompts'] = mock_obj.char_prompts

Expand Down Expand Up @@ -4180,12 +4201,16 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
queryset=Inventory.objects.all(),
required=False, write_only=True
)
limit = serializers.CharField(required=False, write_only=True, allow_blank=True)
scm_branch = serializers.CharField(required=False, write_only=True, allow_blank=True)
workflow_job_template_data = serializers.SerializerMethodField()

class Meta:
model = WorkflowJobTemplate
fields = ('ask_inventory_on_launch', 'can_start_without_user_input', 'defaults', 'extra_vars',
'inventory', 'survey_enabled', 'variables_needed_to_start',
fields = ('ask_inventory_on_launch', 'ask_limit_on_launch', 'ask_scm_branch_on_launch',
'can_start_without_user_input', 'defaults', 'extra_vars',
'inventory', 'limit', 'scm_branch',
'survey_enabled', 'variables_needed_to_start',
'node_templates_missing', 'node_prompts_rejected',
'workflow_job_template_data', 'survey_enabled', 'ask_variables_on_launch')
read_only_fields = ('ask_inventory_on_launch', 'ask_variables_on_launch')
Expand Down Expand Up @@ -4225,9 +4250,14 @@ def validate(self, attrs):

WFJT_extra_vars = template.extra_vars
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
template.limit = WFJT_limit
template.scm_branch = WFJT_scm_branch

return accepted


Expand Down
25 changes: 20 additions & 5 deletions awx/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,17 @@ def get(self, request, *args, **kwargs):
data.update(messages)
return Response(data)

def _build_create_dict(self, obj):
"""Special processing of fields managed by char_prompts
"""
r = super(WorkflowJobTemplateCopy, self)._build_create_dict(obj)
field_names = set(f.name for f in obj._meta.get_fields())
for field_name, ask_field_name in obj.get_ask_mapping().items():
if field_name in r and field_name not in field_names:
r.setdefault('char_prompts', {})
r['char_prompts'][field_name] = r.pop(field_name)
return r

@staticmethod
def deep_copy_permission_check_func(user, new_objs):
for obj in new_objs:
Expand Down Expand Up @@ -3139,7 +3150,6 @@ class WorkflowJobTemplateLabelList(JobTemplateLabelList):

class WorkflowJobTemplateLaunch(RetrieveAPIView):


model = models.WorkflowJobTemplate
obj_permission_type = 'start'
serializer_class = serializers.WorkflowJobLaunchSerializer
Expand All @@ -3156,10 +3166,15 @@ def update_raw_data(self, data):
extra_vars.setdefault(v, u'')
if extra_vars:
data['extra_vars'] = extra_vars
if obj.ask_inventory_on_launch:
data['inventory'] = obj.inventory_id
else:
data.pop('inventory', None)
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():
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)
else:
data[field_name] = getattr(obj, field_name)
return data

def post(self, request, *args, **kwargs):
Expand Down
59 changes: 59 additions & 0 deletions awx/main/migrations/0090_v360_WFJT_prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 2.2.2 on 2019-07-23 17:56

import awx.main.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('main', '0089_v360_new_job_event_types'),
]

operations = [
migrations.AddField(
model_name='workflowjobtemplate',
name='ask_limit_on_launch',
field=awx.main.fields.AskForField(blank=True, default=False),
),
migrations.AddField(
model_name='workflowjobtemplate',
name='ask_scm_branch_on_launch',
field=awx.main.fields.AskForField(blank=True, default=False),
),
migrations.AddField(
model_name='workflowjobtemplate',
name='char_prompts',
field=awx.main.fields.JSONField(blank=True, default=dict),
),
migrations.AlterField(
model_name='joblaunchconfig',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='joblaunchconfigs', to='main.Inventory'),
),
migrations.AlterField(
model_name='schedule',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='schedules', to='main.Inventory'),
),
migrations.AlterField(
model_name='workflowjob',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='workflowjobs', to='main.Inventory'),
),
migrations.AlterField(
model_name='workflowjobnode',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='workflowjobnodes', to='main.Inventory'),
),
migrations.AlterField(
model_name='workflowjobtemplate',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='workflowjobtemplates', to='main.Inventory'),
),
migrations.AlterField(
model_name='workflowjobtemplatenode',
name='inventory',
field=models.ForeignKey(blank=True, default=None, help_text='Inventory applied as a prompt, assuming job template prompts for inventory', null=True, on_delete=models.deletion.SET_NULL, related_name='workflowjobtemplatenodes', to='main.Inventory'),
),
]
2 changes: 1 addition & 1 deletion awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,7 +1501,7 @@ def _get_unified_job_class(cls):
@classmethod
def _get_unified_job_field_names(cls):
return set(f.name for f in InventorySourceOptions._meta.fields) | set(
['name', 'description', 'schedule', 'credentials', 'inventory']
['name', 'description', 'credentials', 'inventory']
)

def save(self, *args, **kwargs):
Expand Down
66 changes: 24 additions & 42 deletions awx/main/models/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.utils import parse_yaml_or_json, getattr_dne
from awx.main.utils import parse_yaml_or_json, getattr_dne, NullablePromptPseudoField
from awx.main.fields import ImplicitRoleField, JSONField, AskForField
from awx.main.models.mixins import (
ResourceMixin,
Expand Down Expand Up @@ -271,7 +271,7 @@ def _get_unified_job_class(cls):
@classmethod
def _get_unified_job_field_names(cls):
return set(f.name for f in JobOptions._meta.fields) | set(
['name', 'description', 'schedule', 'survey_passwords', 'labels', 'credentials',
['name', 'description', 'survey_passwords', 'labels', 'credentials',
'job_slice_number', 'job_slice_count']
)

Expand Down Expand Up @@ -839,25 +839,6 @@ def finish_job_fact_cache(self, destination, modification_times):
host.save()


# Add on aliases for the non-related-model fields
class NullablePromptPsuedoField(object):
"""
Interface for psuedo-property stored in `char_prompts` dict
Used in LaunchTimeConfig and submodels
"""
def __init__(self, field_name):
self.field_name = field_name

def __get__(self, instance, type=None):
return instance.char_prompts.get(self.field_name, None)

def __set__(self, instance, value):
if value in (None, {}):
instance.char_prompts.pop(self.field_name, None)
else:
instance.char_prompts[self.field_name] = value


class LaunchTimeConfigBase(BaseModel):
'''
Needed as separate class from LaunchTimeConfig because some models
Expand All @@ -878,6 +859,7 @@ class Meta:
null=True,
default=None,
on_delete=models.SET_NULL,
help_text=_('Inventory applied as a prompt, assuming job template prompts for inventory')
)
# All standard fields are stored in this dictionary field
# This is a solution to the nullable CharField problem, specific to prompting
Expand Down Expand Up @@ -914,21 +896,14 @@ def prompts_dict(self, display=False):
data[prompt_name] = prompt_val
return data

def display_extra_vars(self):
'''
Hides fields marked as passwords in survey.
'''
if self.survey_passwords:
extra_vars = parse_yaml_or_json(self.extra_vars).copy()
for key, value in self.survey_passwords.items():
if key in extra_vars:
extra_vars[key] = value
return extra_vars
else:
return self.extra_vars

def display_extra_data(self):
return self.display_extra_vars()
for field_name in JobTemplate.get_ask_mapping().keys():
if field_name == 'extra_vars':
continue
try:
LaunchTimeConfigBase._meta.get_field(field_name)
except FieldDoesNotExist:
setattr(LaunchTimeConfigBase, field_name, NullablePromptPseudoField(field_name))


class LaunchTimeConfig(LaunchTimeConfigBase):
Expand Down Expand Up @@ -963,14 +938,21 @@ def extra_vars(self):
def extra_vars(self, extra_vars):
self.extra_data = extra_vars

def display_extra_vars(self):
'''
Hides fields marked as passwords in survey.
'''
if hasattr(self, 'survey_passwords') and self.survey_passwords:
extra_vars = parse_yaml_or_json(self.extra_vars).copy()
for key, value in self.survey_passwords.items():
if key in extra_vars:
extra_vars[key] = value
return extra_vars
else:
return self.extra_vars

for field_name in JobTemplate.get_ask_mapping().keys():
if field_name == 'extra_vars':
continue
try:
LaunchTimeConfig._meta.get_field(field_name)
except FieldDoesNotExist:
setattr(LaunchTimeConfig, field_name, NullablePromptPsuedoField(field_name))
def display_extra_data(self):
return self.display_extra_vars()


class JobLaunchConfig(LaunchTimeConfig):
Expand Down
2 changes: 1 addition & 1 deletion awx/main/models/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def _get_unified_job_class(cls):
@classmethod
def _get_unified_job_field_names(cls):
return set(f.name for f in ProjectOptions._meta.fields) | set(
['name', 'description', 'schedule']
['name', 'description']
)

def save(self, *args, **kwargs):
Expand Down
Loading

0 comments on commit b5fa160

Please sign in to comment.