1package atc_test
2
3import (
4	"encoding/json"
5	"strings"
6
7	"github.com/concourse/concourse/atc"
8	"github.com/stretchr/testify/require"
9	"github.com/stretchr/testify/suite"
10	"sigs.k8s.io/yaml"
11)
12
13type StepsSuite struct {
14	suite.Suite
15	*require.Assertions
16}
17
18type StepTest struct {
19	Title string
20
21	ConfigYAML string
22	StepConfig atc.StepConfig
23
24	UnknownFields map[string]*json.RawMessage
25	Err           string
26}
27
28var factoryTests = []StepTest{
29	{
30		Title: "get step",
31		ConfigYAML: `
32			get: some-name
33			resource: some-resource
34			params: {some: params}
35			version: {some: version}
36			tags: [tag-1, tag-2]
37		`,
38		StepConfig: &atc.GetStep{
39			Name:     "some-name",
40			Resource: "some-resource",
41			Params:   atc.Params{"some": "params"},
42			Version:  &atc.VersionConfig{Pinned: atc.Version{"some": "version"}},
43			Tags:     []string{"tag-1", "tag-2"},
44		},
45	},
46	{
47		Title: "put step",
48
49		ConfigYAML: `
50			put: some-name
51			resource: some-resource
52			params: {some: params}
53			tags: [tag-1, tag-2]
54			inputs: all
55			get_params: {some: get-params}
56		`,
57		StepConfig: &atc.PutStep{
58			Name:      "some-name",
59			Resource:  "some-resource",
60			Params:    atc.Params{"some": "params"},
61			Tags:      []string{"tag-1", "tag-2"},
62			Inputs:    &atc.InputsConfig{All: true},
63			GetParams: atc.Params{"some": "get-params"},
64		},
65	},
66	{
67		Title: "task step",
68
69		ConfigYAML: `
70			task: some-task
71			privileged: true
72			config:
73			  platform: linux
74			  run: {path: hello}
75			file: some-task-file
76			vars: {some: vars}
77			params: {SOME: PARAMS}
78			tags: [tag-1, tag-2]
79			input_mapping: {generic: specific}
80			output_mapping: {specific: generic}
81			image: some-image
82		`,
83
84		StepConfig: &atc.TaskStep{
85			Name:       "some-task",
86			Privileged: true,
87			Config: &atc.TaskConfig{
88				Platform: "linux",
89				Run:      atc.TaskRunConfig{Path: "hello"},
90			},
91			ConfigPath:        "some-task-file",
92			Vars:              atc.Params{"some": "vars"},
93			Params:            atc.TaskEnv{"SOME": "PARAMS"},
94			Tags:              []string{"tag-1", "tag-2"},
95			InputMapping:      map[string]string{"generic": "specific"},
96			OutputMapping:     map[string]string{"specific": "generic"},
97			ImageArtifactName: "some-image",
98		},
99	},
100	{
101		Title: "task step with non-string params",
102
103		ConfigYAML: `
104			task: some-task
105			file: some-task-file
106			params:
107			  NUMBER: 42
108			  FLOAT: 1.5
109			  BOOL: yes
110			  OBJECT: {foo: bar}
111		`,
112
113		StepConfig: &atc.TaskStep{
114			Name:       "some-task",
115			ConfigPath: "some-task-file",
116			Params: atc.TaskEnv{
117				"NUMBER": "42",
118				"FLOAT":  "1.5",
119				"BOOL":   "true",
120				"OBJECT": `{"foo":"bar"}`,
121			},
122		},
123	},
124	{
125		Title: "set_pipeline step",
126
127		ConfigYAML: `
128			set_pipeline: some-pipeline
129			file: some-pipeline-file
130			vars: {some: vars}
131			var_files: [file-1, file-2]
132		`,
133
134		StepConfig: &atc.SetPipelineStep{
135			Name:     "some-pipeline",
136			File:     "some-pipeline-file",
137			Vars:     atc.Params{"some": "vars"},
138			VarFiles: []string{"file-1", "file-2"},
139		},
140	},
141	{
142		Title: "load_var step",
143
144		ConfigYAML: `
145			load_var: some-var
146			file: some-var-file
147			format: raw
148			reveal: true
149		`,
150
151		StepConfig: &atc.LoadVarStep{
152			Name:   "some-var",
153			File:   "some-var-file",
154			Format: "raw",
155			Reveal: true,
156		},
157	},
158	{
159		Title: "try step",
160
161		ConfigYAML: `
162			try:
163			  load_var: some-var
164			  file: some-file
165		`,
166
167		StepConfig: &atc.TryStep{
168			Step: atc.Step{
169				Config: &atc.LoadVarStep{
170					Name: "some-var",
171					File: "some-file",
172				},
173			},
174		},
175	},
176	{
177		Title: "do step",
178
179		ConfigYAML: `
180			do:
181			- load_var: some-var
182			  file: some-file
183			- load_var: some-other-var
184			  file: some-other-file
185		`,
186
187		StepConfig: &atc.DoStep{
188			Steps: []atc.Step{
189				{
190					Config: &atc.LoadVarStep{
191						Name: "some-var",
192						File: "some-file",
193					},
194				},
195				{
196					Config: &atc.LoadVarStep{
197						Name: "some-other-var",
198						File: "some-other-file",
199					},
200				},
201			},
202		},
203	},
204	{
205		Title: "in_parallel step with simple list",
206
207		ConfigYAML: `
208			in_parallel:
209			- load_var: some-var
210			  file: some-file
211			- load_var: some-other-var
212			  file: some-other-file
213		`,
214
215		StepConfig: &atc.InParallelStep{
216			Config: atc.InParallelConfig{
217				Steps: []atc.Step{
218					{
219						Config: &atc.LoadVarStep{
220							Name: "some-var",
221							File: "some-file",
222						},
223					},
224					{
225						Config: &atc.LoadVarStep{
226							Name: "some-other-var",
227							File: "some-other-file",
228						},
229					},
230				},
231			},
232		},
233	},
234	{
235		Title: "in_parallel step with config",
236
237		ConfigYAML: `
238			in_parallel:
239			  steps:
240			  - load_var: some-var
241			    file: some-file
242			  - load_var: some-other-var
243			    file: some-other-file
244			  limit: 3
245			  fail_fast: true
246		`,
247
248		StepConfig: &atc.InParallelStep{
249			Config: atc.InParallelConfig{
250				Steps: []atc.Step{
251					{
252						Config: &atc.LoadVarStep{
253							Name: "some-var",
254							File: "some-file",
255						},
256					},
257					{
258						Config: &atc.LoadVarStep{
259							Name: "some-other-var",
260							File: "some-other-file",
261						},
262					},
263				},
264				Limit:    3,
265				FailFast: true,
266			},
267		},
268	},
269	{
270		Title: "aggregate step",
271
272		ConfigYAML: `
273			aggregate:
274			- load_var: some-var
275			  file: some-file
276			- load_var: some-other-var
277			  file: some-other-file
278		`,
279
280		StepConfig: &atc.AggregateStep{
281			Steps: []atc.Step{
282				{
283					Config: &atc.LoadVarStep{
284						Name: "some-var",
285						File: "some-file",
286					},
287				},
288				{
289					Config: &atc.LoadVarStep{
290						Name: "some-other-var",
291						File: "some-other-file",
292					},
293				},
294			},
295		},
296	},
297	{
298		Title: "across step",
299
300		ConfigYAML: `
301			load_var: some-var
302			file: some-file
303			across:
304			- var: var1
305			  values: [1, 2, 3]
306			  max_in_flight: 3
307			- var: var2
308			  values: ["a", "b"]
309			  max_in_flight: all
310			- var: var3
311			  values: [{a: "a", b: "b"}]
312			fail_fast: true
313		`,
314
315		StepConfig: &atc.AcrossStep{
316			Step: &atc.LoadVarStep{
317				Name: "some-var",
318				File: "some-file",
319			},
320			Vars: []atc.AcrossVarConfig{
321				{
322					Var:         "var1",
323					Values:      []interface{}{float64(1), float64(2), float64(3)},
324					MaxInFlight: &atc.MaxInFlightConfig{Limit: 3},
325				},
326				{
327					Var:         "var2",
328					Values:      []interface{}{"a", "b"},
329					MaxInFlight: &atc.MaxInFlightConfig{All: true},
330				},
331				{
332					Var:    "var3",
333					Values: []interface{}{map[string]interface{}{"a": "a", "b": "b"}},
334				},
335			},
336			FailFast: true,
337		},
338	},
339	{
340		Title: "across step with invalid field",
341
342		ConfigYAML: `
343			load_var: some-var
344			file: some-file
345			across:
346			- var: var1
347			  values: [1, 2, 3]
348			  bogus_field: lol what ru gonna do about it
349		`,
350
351		Err: `error unmarshaling JSON: while decoding JSON: malformed across step: json: unknown field "bogus_field"`,
352	},
353	{
354		Title: "across step with invalid max_in_flight",
355
356		ConfigYAML: `
357			load_var: some-var
358			file: some-file
359			across:
360			- var: var1
361			  values: [1, 2, 3]
362			  max_in_flight: some
363		`,
364
365		Err: `error unmarshaling JSON: while decoding JSON: malformed across step: invalid max_in_flight "some"`,
366	},
367	{
368		Title: "timeout modifier",
369
370		ConfigYAML: `
371			load_var: some-var
372			file: some-file
373			timeout: 1h
374		`,
375
376		StepConfig: &atc.TimeoutStep{
377			Step: &atc.LoadVarStep{
378				Name: "some-var",
379				File: "some-file",
380			},
381			Duration: "1h",
382		},
383	},
384	{
385		Title: "attempts modifier",
386
387		ConfigYAML: `
388			load_var: some-var
389			file: some-file
390			attempts: 3
391		`,
392
393		StepConfig: &atc.RetryStep{
394			Step: &atc.LoadVarStep{
395				Name: "some-var",
396				File: "some-file",
397			},
398			Attempts: 3,
399		},
400	},
401	{
402		Title: "precedence of all hooks and modifiers",
403
404		ConfigYAML: `
405			load_var: some-var
406			file: some-file
407			timeout: 1h
408			attempts: 3
409			across:
410			- var: version
411			  values: [v1, v2, v3]
412			on_success:
413			  load_var: success-var
414			  file: success-file
415			on_failure:
416			  load_var: failure-var
417			  file: failure-file
418			on_abort:
419			  load_var: abort-var
420			  file: abort-file
421			on_error:
422			  load_var: error-var
423			  file: error-file
424			ensure:
425			  load_var: ensure-var
426			  file: ensure-file
427		`,
428
429		StepConfig: &atc.EnsureStep{
430			Step: &atc.OnErrorStep{
431				Step: &atc.OnAbortStep{
432					Step: &atc.OnFailureStep{
433						Step: &atc.OnSuccessStep{
434							Step: &atc.AcrossStep{
435								Step: &atc.RetryStep{
436									Step: &atc.TimeoutStep{
437										Step: &atc.LoadVarStep{
438											Name: "some-var",
439											File: "some-file",
440										},
441										Duration: "1h",
442									},
443									Attempts: 3,
444								},
445								Vars: []atc.AcrossVarConfig{
446									{
447										Var:    "version",
448										Values: []interface{}{"v1", "v2", "v3"},
449									},
450								},
451							},
452							Hook: atc.Step{
453								Config: &atc.LoadVarStep{
454									Name: "success-var",
455									File: "success-file",
456								},
457							},
458						},
459						Hook: atc.Step{
460							Config: &atc.LoadVarStep{
461								Name: "failure-var",
462								File: "failure-file",
463							},
464						},
465					},
466					Hook: atc.Step{
467						Config: &atc.LoadVarStep{
468							Name: "abort-var",
469							File: "abort-file",
470						},
471					},
472				},
473				Hook: atc.Step{
474					Config: &atc.LoadVarStep{
475						Name: "error-var",
476						File: "error-file",
477					},
478				},
479			},
480			Hook: atc.Step{
481				Config: &atc.LoadVarStep{
482					Name: "ensure-var",
483					File: "ensure-file",
484				},
485			},
486		},
487	},
488	{
489		Title: "unknown field with get step",
490
491		ConfigYAML: `
492			get: some-name
493			bogus: foo
494		`,
495
496		StepConfig: &atc.GetStep{
497			Name: "some-name",
498		},
499
500		UnknownFields: map[string]*json.RawMessage{"bogus": rawMessage(`"foo"`)},
501	},
502	{
503		Title: "multiple steps defined",
504
505		ConfigYAML: `
506			put: some-name
507			get: some-other-name
508		`,
509
510		StepConfig: &atc.PutStep{
511			Name: "some-name",
512		},
513
514		UnknownFields: map[string]*json.RawMessage{"get": rawMessage(`"some-other-name"`)},
515	},
516	{
517		Title: "step cannot contain only modifiers",
518
519		ConfigYAML: `
520			attempts: 2
521		`,
522
523		StepConfig: &atc.RetryStep{
524			Attempts: 2,
525		},
526
527		Err: "no core step type declared (e.g. get, put, task, etc.)",
528	},
529}
530
531func (test StepTest) Run(s *StepsSuite) {
532	cleanIndents := strings.ReplaceAll(test.ConfigYAML, "\t", "")
533
534	var step atc.Step
535	actualErr := yaml.Unmarshal([]byte(cleanIndents), &step)
536	if test.Err != "" {
537		s.Contains(actualErr.Error(), test.Err)
538		return
539	} else {
540		s.NoError(actualErr)
541	}
542
543	s.Equal(test.StepConfig, step.Config)
544	s.Equal(test.UnknownFields, step.UnknownFields)
545
546	remarshalled, err := json.Marshal(step)
547	s.NoError(err)
548
549	var reStep atc.Step
550	err = yaml.Unmarshal(remarshalled, &reStep)
551	s.NoError(err)
552
553	s.Equal(test.StepConfig, reStep.Config)
554}
555
556func (s *StepsSuite) TestFactory() {
557	for _, test := range factoryTests {
558		s.Run(test.Title, func() {
559			test.Run(s)
560		})
561	}
562}
563
564func rawMessage(s string) *json.RawMessage {
565	raw := json.RawMessage(s)
566	return &raw
567}
568