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