Skip to content

Commit 76eda6f

Browse files
authored
fix: handle escape chars in withParam/withItems. Fixes argoproj#13718 (argoproj#14864)
Signed-off-by: Tzu-Ting <tzingshih@gmail.com>
1 parent 70ad4ac commit 76eda6f

4 files changed

Lines changed: 214 additions & 12 deletions

File tree

workflow/controller/dag_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,53 @@ func TestArtifactResolutionWhenSkippedDAG(t *testing.T) {
209209
assert.Equal(t, wfv1.WorkflowSucceeded, woc.wf.Status.Phase)
210210
}
211211

212+
func TestExpandTaskWithParam(t *testing.T) {
213+
ctx := logging.TestContext(t.Context())
214+
task := wfv1.DAGTask{
215+
Name: "fanout-param",
216+
Template: "tmpl",
217+
Arguments: wfv1.Arguments{
218+
Parameters: []wfv1.Parameter{{
219+
Name: "msg",
220+
Value: wfv1.AnyStringPtr("{{item}}"),
221+
}},
222+
},
223+
WithParam: `[1234, "foo\tbar", true, []]`,
224+
}
225+
226+
expanded, err := expandTask(ctx, task, map[string]string{})
227+
require.NoError(t, err)
228+
require.Len(t, expanded, 4)
229+
230+
expectedExpandedTasks := []struct {
231+
Name string
232+
Parameter string
233+
}{
234+
{
235+
Name: "fanout-param(0:1234)",
236+
Parameter: "1234",
237+
},
238+
{
239+
Name: `fanout-param(1:foo\tbar)`,
240+
Parameter: "foo\tbar",
241+
},
242+
{
243+
Name: "fanout-param(2:true)",
244+
Parameter: "true",
245+
},
246+
{
247+
Name: "fanout-param(3:[])",
248+
Parameter: "[]",
249+
},
250+
}
251+
252+
for i, expected := range expectedExpandedTasks {
253+
assert.Equal(t, expected.Name, expanded[i].Name)
254+
assert.Equal(t, "tmpl", expanded[i].Template)
255+
assert.Equal(t, expected.Parameter, expanded[i].Arguments.Parameters[0].Value.String())
256+
}
257+
}
258+
212259
func TestEvaluateDependsLogic(t *testing.T) {
213260
testTasks := []wfv1.DAGTask{
214261
{

workflow/controller/operator.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3783,9 +3783,12 @@ func processItem(ctx context.Context, tmpl template.Template, name string, index
37833783
var newName string
37843784

37853785
switch item.GetType() {
3786-
case wfv1.String, wfv1.Number, wfv1.Bool:
3786+
case wfv1.Number, wfv1.Bool:
37873787
replaceMap["item"] = fmt.Sprintf("%v", item)
37883788
newName = generateNodeName(name, index, item)
3789+
case wfv1.String:
3790+
replaceMap["item"] = item.GetStrVal()
3791+
newName = generateNodeName(name, index, item)
37893792
case wfv1.Map:
37903793
// Handle the case when withItems is a list of maps.
37913794
// vals holds stringified versions of the map items which are incorporated as part of the step name.

workflow/controller/operator_test.go

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6948,19 +6948,82 @@ func TestFailSuspendedAndPendingNodesAfterShutdown(t *testing.T) {
69486948

69496949
func Test_processItem(t *testing.T) {
69506950
ctx := logging.TestContext(t.Context())
6951-
task := wfv1.DAGTask{
6952-
WithParam: `[{"number": 2, "string": "foo", "list": [0, "1"], "json": {"number": 2, "string": "foo", "list": [0, "1"]}}]`,
6951+
6952+
tests := []struct {
6953+
name string
6954+
withParam string
6955+
expectedName string
6956+
expectedParam string
6957+
}{
6958+
{
6959+
name: "Test string",
6960+
withParam: `["string"]`,
6961+
expectedName: `task-name(0:string)`,
6962+
expectedParam: `string`,
6963+
},
6964+
{
6965+
name: "Test multiline string",
6966+
withParam: `["alpha\nbeta"]`,
6967+
expectedName: `task-name(0:alpha\nbeta)`,
6968+
expectedParam: "alpha\nbeta",
6969+
},
6970+
{
6971+
name: "Test number",
6972+
withParam: `[42]`,
6973+
expectedName: `task-name(0:42)`,
6974+
expectedParam: `42`,
6975+
},
6976+
{
6977+
name: "Test boolean",
6978+
withParam: `[true]`,
6979+
expectedName: `task-name(0:true)`,
6980+
expectedParam: `true`,
6981+
},
6982+
{
6983+
name: "Test map",
6984+
withParam: `[{"number": 2, "string": "foo", "list": [0, "1"], "json": {"number": 2, "string": "foo", "list": [0, "1"]}}]`,
6985+
expectedName: `task-name(0:json:{"list":[0,"1"],"number":2,"string":"foo"},list:[0,"1"],number:2,string:foo)`,
6986+
expectedParam: `{"json":{"list":[0,"1"],"number":2,"string":"foo"},"list":[0,"1"],"number":2,"string":"foo"}`,
6987+
},
6988+
{
6989+
name: "Test list",
6990+
withParam: `[[1, "two", 3]]`,
6991+
expectedName: `task-name(0:[1 two 3])`,
6992+
expectedParam: `[1,"two",3]`,
6993+
},
69536994
}
6954-
taskBytes, err := json.Marshal(task)
6955-
require.NoError(t, err)
6956-
var items []wfv1.Item
6957-
wfv1.MustUnmarshal([]byte(task.WithParam), &items)
69586995

6959-
var newTask wfv1.DAGTask
6960-
tmpl, _ := template.NewTemplate(string(taskBytes))
6961-
newTaskName, err := processItem(ctx, tmpl, "task-name", 0, items[0], &newTask, "", map[string]string{})
6962-
require.NoError(t, err)
6963-
assert.Equal(t, `task-name(0:json:{"list":[0,"1"],"number":2,"string":"foo"},list:[0,"1"],number:2,string:foo)`, newTaskName)
6996+
for _, tt := range tests {
6997+
t.Run(tt.name, func(t *testing.T) {
6998+
task := wfv1.DAGTask{
6999+
WithParam: tt.withParam,
7000+
Arguments: wfv1.Arguments{
7001+
Parameters: []wfv1.Parameter{
7002+
{
7003+
Name: "item",
7004+
Value: wfv1.AnyStringPtr("{{item}}"),
7005+
},
7006+
},
7007+
},
7008+
}
7009+
7010+
taskBytes, err := json.Marshal(task)
7011+
require.NoError(t, err)
7012+
7013+
tmpl, err := template.NewTemplate(string(taskBytes))
7014+
require.NoError(t, err)
7015+
7016+
var items []wfv1.Item
7017+
wfv1.MustUnmarshal([]byte(tt.withParam), &items)
7018+
7019+
var newTask wfv1.DAGTask
7020+
newTaskName, err := processItem(ctx, tmpl, "task-name", 0, items[0], &newTask, "", map[string]string{})
7021+
7022+
require.NoError(t, err)
7023+
assert.Equal(t, tt.expectedName, newTaskName)
7024+
assert.Equal(t, tt.expectedParam, newTask.Arguments.Parameters[0].Value.String())
7025+
})
7026+
}
69647027
}
69657028

69667029
var stepTimeoutWf = `

workflow/controller/steps_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,95 @@ func TestStepsWithParamAndGlobalParam(t *testing.T) {
127127
assert.Equal(t, wfv1.WorkflowRunning, woc.wf.Status.Phase)
128128
}
129129

130+
var stepsWithParam = `
131+
apiVersion: argoproj.io/v1alpha1
132+
kind: Workflow
133+
metadata:
134+
generateName: steps-with-params-
135+
spec:
136+
entrypoint: main
137+
templates:
138+
- name: main
139+
steps:
140+
- - name: use-with-param
141+
template: whalesay
142+
arguments:
143+
parameters:
144+
- name: message
145+
value: "{{item}}"
146+
withParam: "[1234, \"foo\\tbar\", true, []]"
147+
`
148+
149+
func TestExpandStepGroupWithParam(t *testing.T) {
150+
ctx := logging.TestContext(t.Context())
151+
wf := wfv1.MustUnmarshalWorkflow(stepsWithParam)
152+
woc := newWoc(ctx, *wf)
153+
154+
expanded, err := woc.expandStepGroup(ctx, "[0]", wf.Spec.Templates[0].Steps[0].Steps, &stepsContext{scope: createScope(&wf.Spec.Templates[0])})
155+
require.NoError(t, err)
156+
require.Len(t, expanded, 4)
157+
158+
expectedExpandedTasks := []struct {
159+
Name string
160+
Parameter string
161+
}{
162+
{
163+
Name: "use-with-param(0:1234)",
164+
Parameter: "1234",
165+
},
166+
{
167+
Name: `use-with-param(1:foo\tbar)`,
168+
Parameter: "foo\tbar",
169+
},
170+
{
171+
Name: "use-with-param(2:true)",
172+
Parameter: "true",
173+
},
174+
{
175+
Name: "use-with-param(3:[])",
176+
Parameter: "[]",
177+
},
178+
}
179+
180+
for i, expected := range expectedExpandedTasks {
181+
assert.Equal(t, expected.Name, expanded[i].Name)
182+
require.Len(t, expanded[i].Arguments.Parameters, 1)
183+
assert.Equal(t, expected.Parameter, expanded[i].Arguments.Parameters[0].Value.String())
184+
}
185+
}
186+
187+
var stepsWithItems = `
188+
apiVersion: argoproj.io/v1alpha1
189+
kind: Workflow
190+
metadata:
191+
generateName: steps-with-items-
192+
spec:
193+
entrypoint: main
194+
templates:
195+
- name: main
196+
steps:
197+
- - name: use-with-items
198+
template: whalesay
199+
arguments:
200+
parameters:
201+
- name: message
202+
value: "{{item}}"
203+
withItems:
204+
- Hello"Argo
205+
`
206+
207+
func TestExpandStepGroupWithItems(t *testing.T) {
208+
ctx := logging.TestContext(t.Context())
209+
wf := wfv1.MustUnmarshalWorkflow(stepsWithItems)
210+
woc := newWoc(ctx, *wf)
211+
212+
expanded, err := woc.expandStepGroup(ctx, "[0]", wf.Spec.Templates[0].Steps[0].Steps, &stepsContext{scope: createScope(&wf.Spec.Templates[0])})
213+
require.NoError(t, err)
214+
require.Len(t, expanded, 1)
215+
216+
assert.Equal(t, `Hello"Argo`, expanded[0].Arguments.Parameters[0].Value.String())
217+
}
218+
130219
func TestResourceDurationMetric(t *testing.T) {
131220
nodeStatus := `
132221
boundaryID: many-items-z26lj

0 commit comments

Comments
 (0)