Skip to content

Commit 419a4a9

Browse files
authored
Merge pull request #195 from StackStorm/fix-with-items
Fix schema of with items task to not allow additional properties
2 parents 359a33f + 0d672b0 commit 419a4a9

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Fixed
3636
must be called separately. (bug fix)
3737
* When inspecting custom YAQL/Jinja function to see if there is a context arg, use getargspec
3838
for py2 and getfullargspec for py3. (bug fix)
39+
* Check syntax on with items task to ensure action is indented correctly. Fixes #184 (bug fix)
3940

4041
1.0.0
4142
-----

orquesta/specs/native/v1/models.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ class ItemizedSpec(native_v1_specs.Spec):
111111
'pattern': _items_regex
112112
},
113113
'concurrency': spec_types.STRING_OR_POSITIVE_INTEGER
114-
}
114+
},
115+
'additionalProperties': False
115116
}
116117

117118
_context_evaluation_sequence = [
@@ -377,6 +378,20 @@ def has_cycles(self):
377378

378379
return False
379380

381+
def detect_actionless_with_items(self, parent=None):
382+
result = []
383+
384+
# Identify with items task with no action defined.
385+
for task_name, task_spec in six.iteritems(self):
386+
if task_spec.has_items() and not task_spec.action:
387+
message = 'The action property is required for with items task.'
388+
spec_path = parent.get('spec_path') + '.' + task_name
389+
schema_path = parent.get('schema_path') + '.patternProperties.^\\w+$'
390+
entry = {'message': message, 'spec_path': spec_path, 'schema_path': schema_path}
391+
result.append(entry)
392+
393+
return result
394+
380395
def detect_reserved_names(self, parent=None):
381396
result = []
382397

@@ -514,6 +529,7 @@ def inspect_semantics(self, parent=None):
514529
result = self.detect_reserved_names(parent=parent)
515530
result.extend(self.detect_undefined_tasks(parent=parent))
516531
result.extend(self.detect_unreachable_tasks(parent=parent))
532+
result.extend(self.detect_actionless_with_items(parent=parent))
517533

518534
return result
519535

orquesta/tests/unit/base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,15 @@ class WorkflowConductorWithItemsTest(WorkflowConductorTest):
364364

365365
def assert_task_items(self, conductor, task_id, task_route, task_ctx, items, action_specs,
366366
mock_ac_ex_statuses, expected_task_statuses, expected_workflow_statuses,
367-
concurrency=None):
367+
concurrency=None, mock_ac_ex_results=None):
368368

369369
# Set up test cases.
370370
tests = list(zip(mock_ac_ex_statuses, expected_task_statuses, expected_workflow_statuses))
371371
tk_ex_result = [None] * len(items)
372372

373+
if mock_ac_ex_results is None:
374+
mock_ac_ex_results = items
375+
373376
# Verify the first set of action executions.
374377
expected_task = self.format_task_item(
375378
task_id,
@@ -431,7 +434,7 @@ def assert_task_items(self, conductor, task_id, task_route, task_ctx, items, act
431434

432435
# Mock the action execution for each item.
433436
for item_id in range(0, len(tests)):
434-
ac_ex_result = items[item_id]
437+
ac_ex_result = mock_ac_ex_results[item_id]
435438
tk_ex_result[item_id] = ac_ex_result
436439
ac_ex_status = tests[item_id][0]
437440

orquesta/tests/unit/conducting/test_workflow_conductor_with_items.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,89 @@ def test_empty_items_list(self):
7878
expected_output = {'items': []}
7979
self.assertDictEqual(conductor.get_workflow_output(), expected_output)
8080

81+
def test_bad_with_items_syntax(self):
82+
wf_def = """
83+
version: 1.0
84+
85+
vars:
86+
- xs:
87+
- fee
88+
- fi
89+
- fo
90+
- fum
91+
92+
tasks:
93+
task1:
94+
with:
95+
items: <% ctx(xs) %>
96+
action: core.echo message=<% item() %>
97+
next:
98+
- publish:
99+
- items: <% result() %>
100+
101+
output:
102+
- items: <% ctx(items) %>
103+
"""
104+
105+
expected_errors = {
106+
'semantics': [
107+
{
108+
'message': 'The action property is required for with items task.',
109+
'schema_path': 'properties.tasks.patternProperties.^\\w+$',
110+
'spec_path': 'tasks.task1'
111+
}
112+
],
113+
'syntax': [
114+
{
115+
'message': 'Additional properties are not allowed (\'action\' was unexpected)',
116+
'schema_path': (
117+
'properties.tasks.patternProperties.^\\w+$.'
118+
'properties.with.additionalProperties'
119+
),
120+
'spec_path': 'tasks.task1.with'
121+
}
122+
]
123+
}
124+
125+
spec = native_specs.WorkflowSpec(wf_def)
126+
self.assertDictEqual(spec.inspect(), expected_errors)
127+
128+
def test_with_items_that_is_action_less(self):
129+
wf_def = """
130+
version: 1.0
131+
132+
vars:
133+
- xs:
134+
- fee
135+
- fi
136+
- fo
137+
- fum
138+
139+
tasks:
140+
task1:
141+
with:
142+
items: <% ctx(xs) %>
143+
next:
144+
- publish:
145+
- items: <% result() %>
146+
147+
output:
148+
- items: <% ctx(items) %>
149+
"""
150+
151+
expected_errors = {
152+
'semantics': [
153+
{
154+
'message': 'The action property is required for with items task.',
155+
'schema_path': 'properties.tasks.patternProperties.^\\w+$',
156+
'spec_path': 'tasks.task1'
157+
}
158+
]
159+
}
160+
161+
spec = native_specs.WorkflowSpec(wf_def)
162+
self.assertDictEqual(spec.inspect(), expected_errors)
163+
81164
def test_basic_items_list(self):
82165
wf_def = """
83166
version: 1.0

0 commit comments

Comments
 (0)