github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/configvalidate/validate_test.go (about) 1 package configvalidate_test 2 3 import ( 4 "encoding/json" 5 "strings" 6 7 "github.com/pf-qiu/concourse/v6/atc" 8 "github.com/pf-qiu/concourse/v6/atc/configvalidate" 9 10 // load dummy credential manager 11 _ "github.com/pf-qiu/concourse/v6/atc/creds/dummy" 12 13 . "github.com/onsi/ginkgo" 14 . "github.com/onsi/gomega" 15 ) 16 17 var _ = Describe("ValidateConfig", func() { 18 var ( 19 config atc.Config 20 warnings []atc.ConfigWarning 21 errorMessages []string 22 ) 23 24 BeforeEach(func() { 25 config = atc.Config{ 26 Groups: atc.GroupConfigs{ 27 { 28 Name: "some-group", 29 Jobs: []string{"some-job"}, 30 Resources: []string{"some-resource"}, 31 }, 32 { 33 Name: "some-other-group", 34 Jobs: []string{"some-empty-*"}, 35 }, 36 }, 37 38 VarSources: atc.VarSourceConfigs{}, 39 40 Resources: atc.ResourceConfigs{ 41 { 42 Name: "some-resource", 43 Type: "some-type", 44 Source: atc.Source{ 45 "source-config": "some-value", 46 }, 47 }, 48 }, 49 50 ResourceTypes: atc.ResourceTypes{ 51 { 52 Name: "some-resource-type", 53 Type: "some-type", 54 Source: atc.Source{ 55 "source-config": "some-value", 56 }, 57 }, 58 }, 59 60 Jobs: atc.JobConfigs{ 61 { 62 Name: "some-job", 63 Public: true, 64 Serial: true, 65 PlanSequence: []atc.Step{ 66 { 67 Config: &atc.GetStep{ 68 Name: "some-input", 69 Resource: "some-resource", 70 Params: atc.Params{ 71 "some-param": "some-value", 72 }, 73 }, 74 }, 75 { 76 Config: &atc.LoadVarStep{ 77 Name: "some-var", 78 File: "some-input/some-file.json", 79 }, 80 }, 81 { 82 Config: &atc.TaskStep{ 83 Name: "some-task", 84 Privileged: true, 85 ConfigPath: "some/config/path.yml", 86 }, 87 }, 88 { 89 Config: &atc.PutStep{ 90 Name: "some-resource", 91 Params: atc.Params{ 92 "some-param": "some-value", 93 }, 94 }, 95 }, 96 { 97 Config: &atc.SetPipelineStep{ 98 Name: "some-pipeline", 99 File: "some-file", 100 }, 101 }, 102 }, 103 }, 104 { 105 Name: "some-empty-job", 106 }, 107 }, 108 } 109 110 atc.EnableAcrossStep = true 111 }) 112 113 JustBeforeEach(func() { 114 warnings, errorMessages = configvalidate.Validate(config) 115 }) 116 117 Context("when the config is valid", func() { 118 It("returns no error", func() { 119 Expect(errorMessages).To(HaveLen(0)) 120 }) 121 }) 122 123 Describe("invalid identifiers", func() { 124 125 Context("when a group has an invalid identifier", func() { 126 BeforeEach(func() { 127 config.Groups = append(config.Groups, atc.GroupConfig{ 128 Name: "_some-group", 129 Jobs: []string{"some-job"}, 130 }) 131 }) 132 133 It("returns a warning", func() { 134 Expect(warnings).To(HaveLen(1)) 135 Expect(warnings[0].Message).To(ContainSubstring("'_some-group' is not a valid identifier")) 136 }) 137 }) 138 139 Context("when a resource has an invalid identifier", func() { 140 BeforeEach(func() { 141 config.Resources = append(config.Resources, atc.ResourceConfig{ 142 Name: "some_resource", 143 Type: "some-type", 144 Source: atc.Source{ 145 "source-config": "some-value", 146 }, 147 }) 148 }) 149 150 It("returns a warning", func() { 151 Expect(warnings).To(HaveLen(1)) 152 Expect(warnings[0].Message).To(ContainSubstring("'some_resource' is not a valid identifier")) 153 }) 154 }) 155 156 Context("when a resource type has an invalid identifier", func() { 157 BeforeEach(func() { 158 config.ResourceTypes = append(config.ResourceTypes, atc.ResourceType{ 159 Name: "_some-resource-type", 160 Type: "some-type", 161 Source: atc.Source{ 162 "source-config": "some-value", 163 }, 164 }) 165 }) 166 167 It("returns a warning", func() { 168 Expect(warnings).To(HaveLen(1)) 169 Expect(warnings[0].Message).To(ContainSubstring("'_some-resource-type' is not a valid identifier")) 170 }) 171 }) 172 173 Context("when a var source has an invalid identifier", func() { 174 BeforeEach(func() { 175 config.VarSources = append(config.VarSources, atc.VarSourceConfig{ 176 Name: "_some-var-source", 177 Type: "dummy", 178 Config: "", 179 }) 180 }) 181 182 It("returns a warning", func() { 183 Expect(warnings).To(HaveLen(1)) 184 Expect(warnings[0].Message).To(ContainSubstring("'_some-var-source' is not a valid identifier")) 185 }) 186 }) 187 188 Context("when a job has an invalid identifier", func() { 189 BeforeEach(func() { 190 config.Jobs = append(config.Jobs, atc.JobConfig{ 191 Name: "_some-job", 192 }) 193 }) 194 195 It("returns a warning", func() { 196 Expect(warnings).To(HaveLen(1)) 197 Expect(warnings[0].Message).To(ContainSubstring("'_some-job' is not a valid identifier")) 198 }) 199 }) 200 201 Context("when a step has an invalid identifier", func() { 202 var job atc.JobConfig 203 204 BeforeEach(func() { 205 job.PlanSequence = append(job.PlanSequence, atc.Step{ 206 Config: &atc.GetStep{ 207 Name: "_get-step", 208 }, 209 }) 210 job.PlanSequence = append(job.PlanSequence, atc.Step{ 211 Config: &atc.TaskStep{ 212 Name: "_task-step", 213 }, 214 }) 215 job.PlanSequence = append(job.PlanSequence, atc.Step{ 216 Config: &atc.PutStep{ 217 Name: "_put-step", 218 }, 219 }) 220 job.PlanSequence = append(job.PlanSequence, atc.Step{ 221 Config: &atc.SetPipelineStep{ 222 Name: "_set-pipeline-step", 223 }, 224 }) 225 job.PlanSequence = append(job.PlanSequence, atc.Step{ 226 Config: &atc.LoadVarStep{ 227 Name: "_load-var-step", 228 }, 229 }) 230 231 config.Jobs = append(config.Jobs, job) 232 }) 233 234 It("returns a warning", func() { 235 Expect(warnings).To(HaveLen(5)) 236 Expect(warnings[0].Message).To(ContainSubstring("'_get-step' is not a valid identifier")) 237 Expect(warnings[1].Message).To(ContainSubstring("'_task-step' is not a valid identifier")) 238 Expect(warnings[2].Message).To(ContainSubstring("'_put-step' is not a valid identifier")) 239 Expect(warnings[3].Message).To(ContainSubstring("'_set-pipeline-step' is not a valid identifier")) 240 Expect(warnings[4].Message).To(ContainSubstring("'_load-var-step' is not a valid identifier")) 241 }) 242 }) 243 }) 244 245 Describe("invalid groups", func() { 246 Context("when the groups reference a bogus resource", func() { 247 BeforeEach(func() { 248 config.Groups = append(config.Groups, atc.GroupConfig{ 249 Name: "bogus", 250 Resources: []string{"bogus-resource"}, 251 }) 252 }) 253 254 It("returns an error", func() { 255 Expect(errorMessages).To(HaveLen(1)) 256 Expect(errorMessages[0]).To(ContainSubstring("invalid groups:")) 257 Expect(errorMessages[0]).To(ContainSubstring("unknown resource 'bogus-resource'")) 258 }) 259 }) 260 261 Context("when the groups reference a bogus job", func() { 262 BeforeEach(func() { 263 config.Groups = append(config.Groups, atc.GroupConfig{ 264 Name: "bogus", 265 Jobs: []string{"bogus-*"}, 266 }) 267 }) 268 269 It("returns an error", func() { 270 Expect(errorMessages).To(HaveLen(1)) 271 Expect(errorMessages[0]).To(ContainSubstring("invalid groups:")) 272 Expect(errorMessages[0]).To(ContainSubstring("no jobs match 'bogus-*' for group 'bogus'")) 273 }) 274 }) 275 276 Context("when there are jobs excluded from groups", func() { 277 BeforeEach(func() { 278 config.Jobs = append(config.Jobs, atc.JobConfig{ 279 Name: "stand-alone-job", 280 }) 281 config.Jobs = append(config.Jobs, atc.JobConfig{ 282 Name: "other-stand-alone-job", 283 }) 284 }) 285 286 It("returns an error", func() { 287 Expect(errorMessages).To(HaveLen(1)) 288 Expect(errorMessages[0]).To(ContainSubstring("invalid groups:")) 289 Expect(errorMessages[0]).To(ContainSubstring("job 'stand-alone-job' belongs to no group")) 290 Expect(errorMessages[0]).To(ContainSubstring("job 'other-stand-alone-job' belongs to no group")) 291 }) 292 293 }) 294 295 Context("when the groups have two duplicate names", func() { 296 BeforeEach(func() { 297 config.Groups = append(config.Groups, atc.GroupConfig{ 298 Name: "some-group", 299 }) 300 }) 301 302 It("returns an error", func() { 303 Expect(errorMessages).To(HaveLen(1)) 304 Expect(errorMessages[0]).To(ContainSubstring("invalid groups:")) 305 Expect(errorMessages[0]).To(ContainSubstring("'some-group' appears 2 times. Duplicate names are not allowed.")) 306 }) 307 }) 308 309 Context("when the groups have four duplicate names", func() { 310 BeforeEach(func() { 311 config.Groups = append(config.Groups, atc.GroupConfig{ 312 Name: "some-group", 313 }, atc.GroupConfig{ 314 Name: "some-group", 315 }, atc.GroupConfig{ 316 Name: "some-group", 317 }) 318 }) 319 320 It("returns an error", func() { 321 Expect(errorMessages).To(HaveLen(1)) 322 errorMessage := strings.Trim(errorMessages[0], "\n") 323 errorLines := strings.Split(errorMessage, "\n") 324 Expect(errorLines).To(HaveLen(2)) 325 Expect(errorLines[0]).To(ContainSubstring("invalid groups:")) 326 Expect(errorLines[1]).To(ContainSubstring("group 'some-group' appears 4 times. Duplicate names are not allowed.")) 327 }) 328 }) 329 330 Context("when a group has and invalid glob expression", func() { 331 BeforeEach(func() { 332 config.Groups = append(config.Groups, atc.GroupConfig{ 333 Name: "a-group", 334 Jobs: []string{"some-bad-glob-[0-9"}, 335 }) 336 }) 337 338 It("returns an error", func() { 339 Expect(errorMessages).To(HaveLen(1)) 340 Expect(errorMessages[0]).To(ContainSubstring("invalid groups:")) 341 Expect(errorMessages[0]).To(ContainSubstring("invalid glob expression 'some-bad-glob-[0-9' for group 'a-group'")) 342 }) 343 }) 344 }) 345 346 Describe("invalid var sources", func() { 347 Context("when a var source type is invalid", func() { 348 BeforeEach(func() { 349 config.VarSources = append(config.VarSources, atc.VarSourceConfig{ 350 Name: "some", 351 Type: "some", 352 Config: "", 353 }) 354 }) 355 356 It("returns an error", func() { 357 Expect(errorMessages).To(HaveLen(1)) 358 Expect(errorMessages[0]).To(ContainSubstring("unknown credential manager type: some")) 359 }) 360 }) 361 362 Context("when config is invalid", func() { 363 BeforeEach(func() { 364 config.VarSources = append(config.VarSources, atc.VarSourceConfig{ 365 Name: "some", 366 Type: "dummy", 367 Config: "", 368 }) 369 }) 370 371 It("returns an error", func() { 372 Expect(errorMessages).To(HaveLen(1)) 373 Expect(errorMessages[0]).To(ContainSubstring("failed to create credential manager some: invalid dummy credential manager config")) 374 }) 375 }) 376 377 Context("when duplicate var source names", func() { 378 BeforeEach(func() { 379 config.VarSources = append(config.VarSources, 380 atc.VarSourceConfig{ 381 Name: "some", 382 Type: "dummy", 383 Config: map[string]interface{}{ 384 "vars": map[string]interface{}{"k2": "v2"}, 385 }, 386 }, 387 atc.VarSourceConfig{ 388 Name: "some", 389 Type: "dummy", 390 Config: map[string]interface{}{ 391 "vars": map[string]interface{}{"k2": "v2"}, 392 }, 393 }, 394 ) 395 }) 396 397 It("returns an error", func() { 398 Expect(errorMessages).To(HaveLen(1)) 399 Expect(errorMessages[0]).To(ContainSubstring("duplicate var_source name: some")) 400 }) 401 }) 402 403 Context("when var source's dependency cannot be resolved", func() { 404 BeforeEach(func() { 405 config.VarSources = append(config.VarSources, 406 atc.VarSourceConfig{ 407 Name: "s1", 408 Type: "dummy", 409 Config: map[string]interface{}{ 410 "vars": map[string]interface{}{"k": "v"}, 411 }, 412 }, 413 atc.VarSourceConfig{ 414 Name: "s2", 415 Type: "dummy", 416 Config: map[string]interface{}{ 417 "vars": map[string]interface{}{"k": "((s1:k))"}, 418 }, 419 }, 420 atc.VarSourceConfig{ 421 Name: "s3", 422 Type: "dummy", 423 Config: map[string]interface{}{ 424 "vars": map[string]interface{}{"k": "((none:k))"}, 425 }, 426 }, 427 ) 428 }) 429 430 It("returns an error", func() { 431 Expect(errorMessages).To(HaveLen(1)) 432 Expect(errorMessages[0]).To(ContainSubstring("could not resolve inter-dependent var sources: s3")) 433 }) 434 }) 435 436 Context("when var source names are circular", func() { 437 BeforeEach(func() { 438 config.VarSources = append(config.VarSources, 439 atc.VarSourceConfig{ 440 Name: "s1", 441 Type: "dummy", 442 Config: map[string]interface{}{ 443 "vars": map[string]interface{}{"k": "((s3:v))"}, 444 }, 445 }, 446 atc.VarSourceConfig{ 447 Name: "s2", 448 Type: "dummy", 449 Config: map[string]interface{}{ 450 "vars": map[string]interface{}{"k": "((s1:k))"}, 451 }, 452 }, 453 atc.VarSourceConfig{ 454 Name: "s3", 455 Type: "dummy", 456 Config: map[string]interface{}{ 457 "vars": map[string]interface{}{"k": "((s2:k))"}, 458 }, 459 }, 460 ) 461 }) 462 463 It("returns an error", func() { 464 Expect(errorMessages).To(HaveLen(1)) 465 Expect(errorMessages[0]).To(ContainSubstring("could not resolve inter-dependent var sources: s1, s2, s3")) 466 }) 467 }) 468 }) 469 470 Describe("invalid resources", func() { 471 Context("when a resource has no name", func() { 472 BeforeEach(func() { 473 config.Resources = append(config.Resources, atc.ResourceConfig{ 474 Name: "", 475 }) 476 }) 477 478 It("returns an error", func() { 479 Expect(errorMessages).To(HaveLen(1)) 480 Expect(errorMessages[0]).To(ContainSubstring("invalid resources:")) 481 Expect(errorMessages[0]).To(ContainSubstring("resources[1] has no name")) 482 }) 483 }) 484 485 Context("when a resource has no type", func() { 486 BeforeEach(func() { 487 config.Resources = append(config.Resources, atc.ResourceConfig{ 488 Name: "bogus-resource", 489 Type: "", 490 }) 491 }) 492 493 It("returns an error", func() { 494 Expect(errorMessages).To(HaveLen(1)) 495 Expect(errorMessages[0]).To(ContainSubstring("invalid resources:")) 496 Expect(errorMessages[0]).To(ContainSubstring("resources.bogus-resource has no type")) 497 }) 498 }) 499 500 Context("when a resource has no name or type", func() { 501 BeforeEach(func() { 502 config.Resources = append(config.Resources, atc.ResourceConfig{ 503 Name: "", 504 Type: "", 505 }) 506 }) 507 508 It("returns an error describing both errors", func() { 509 Expect(errorMessages).To(HaveLen(1)) 510 Expect(errorMessages[0]).To(ContainSubstring("invalid resources:")) 511 Expect(errorMessages[0]).To(ContainSubstring("resources[1] has no name")) 512 Expect(errorMessages[0]).To(ContainSubstring("resources[1] has no type")) 513 }) 514 }) 515 516 Context("when two resources have the same name", func() { 517 BeforeEach(func() { 518 config.Resources = append(config.Resources, config.Resources...) 519 }) 520 521 It("returns an error", func() { 522 Expect(errorMessages).To(HaveLen(1)) 523 Expect(errorMessages[0]).To(ContainSubstring("invalid resources:")) 524 Expect(errorMessages[0]).To(ContainSubstring( 525 "resources[0] and resources[1] have the same name ('some-resource')", 526 )) 527 }) 528 }) 529 }) 530 531 Describe("unused resources", func() { 532 BeforeEach(func() { 533 config = atc.Config{ 534 Resources: atc.ResourceConfigs{ 535 { 536 Name: "unused-resource", 537 Type: "some-type", 538 }, 539 { 540 Name: "get", 541 Type: "some-type", 542 }, 543 { 544 Name: "get-alias", 545 Type: "some-type", 546 }, 547 { 548 Name: "resource", 549 Type: "some-type", 550 }, 551 { 552 Name: "put", 553 Type: "some-type", 554 }, 555 { 556 Name: "put-alias", 557 Type: "some-type", 558 }, 559 { 560 Name: "do", 561 Type: "some-type", 562 }, 563 { 564 Name: "aggregate", 565 Type: "some-type", 566 }, 567 { 568 Name: "parallel", 569 Type: "some-type", 570 }, 571 { 572 Name: "abort", 573 Type: "some-type", 574 }, 575 { 576 Name: "error", 577 Type: "some-type", 578 }, 579 { 580 Name: "failure", 581 Type: "some-type", 582 }, 583 { 584 Name: "ensure", 585 Type: "some-type", 586 }, 587 { 588 Name: "success", 589 Type: "some-type", 590 }, 591 { 592 Name: "try", 593 Type: "some-type", 594 }, 595 { 596 Name: "another-job", 597 Type: "some-type", 598 }, 599 }, 600 601 Jobs: atc.JobConfigs{ 602 { 603 Name: "some-job", 604 PlanSequence: []atc.Step{ 605 { 606 Config: &atc.GetStep{ 607 Name: "get", 608 }, 609 }, 610 { 611 Config: &atc.GetStep{ 612 Name: "get-alias", 613 Resource: "resource", 614 }, 615 }, 616 { 617 Config: &atc.PutStep{ 618 Name: "put", 619 }, 620 }, 621 { 622 Config: &atc.PutStep{ 623 Name: "put-alias", 624 Resource: "resource", 625 }, 626 }, 627 { 628 Config: &atc.DoStep{ 629 Steps: []atc.Step{ 630 { 631 Config: &atc.GetStep{ 632 Name: "do", 633 }, 634 }, 635 }, 636 }, 637 }, 638 { 639 Config: &atc.AggregateStep{ 640 Steps: []atc.Step{ 641 { 642 Config: &atc.GetStep{ 643 Name: "aggregate", 644 }, 645 }, 646 }, 647 }, 648 }, 649 { 650 Config: &atc.InParallelStep{ 651 Config: atc.InParallelConfig{ 652 Steps: []atc.Step{ 653 { 654 Config: &atc.GetStep{ 655 Name: "parallel", 656 }, 657 }, 658 }, 659 Limit: 1, 660 FailFast: true, 661 }, 662 }, 663 }, 664 { 665 Config: &atc.OnAbortStep{ 666 Step: &atc.TaskStep{ 667 Name: "some-task", 668 ConfigPath: "some/config/path.yml", 669 }, 670 Hook: atc.Step{ 671 Config: &atc.GetStep{ 672 Name: "abort", 673 }, 674 }, 675 }, 676 }, 677 { 678 Config: &atc.OnErrorStep{ 679 Step: &atc.TaskStep{ 680 Name: "some-task", 681 ConfigPath: "some/config/path.yml", 682 }, 683 Hook: atc.Step{ 684 Config: &atc.GetStep{ 685 Name: "error", 686 }, 687 }, 688 }, 689 }, 690 { 691 Config: &atc.OnFailureStep{ 692 Step: &atc.TaskStep{ 693 Name: "some-task", 694 ConfigPath: "some/config/path.yml", 695 }, 696 Hook: atc.Step{ 697 Config: &atc.GetStep{ 698 Name: "failure", 699 }, 700 }, 701 }, 702 }, 703 { 704 Config: &atc.OnSuccessStep{ 705 Step: &atc.TaskStep{ 706 Name: "some-task", 707 ConfigPath: "some/config/path.yml", 708 }, 709 Hook: atc.Step{ 710 Config: &atc.GetStep{ 711 Name: "success", 712 }, 713 }, 714 }, 715 }, 716 { 717 Config: &atc.EnsureStep{ 718 Step: &atc.TaskStep{ 719 Name: "some-task", 720 ConfigPath: "some/config/path.yml", 721 }, 722 Hook: atc.Step{ 723 Config: &atc.GetStep{ 724 Name: "ensure", 725 }, 726 }, 727 }, 728 }, 729 { 730 Config: &atc.TryStep{ 731 Step: atc.Step{ 732 Config: &atc.GetStep{ 733 Name: "try", 734 }, 735 }, 736 }, 737 }, 738 { 739 Config: &atc.TaskStep{ 740 Name: "some-task", 741 ConfigPath: "some/config/path.yml", 742 }, 743 }, 744 }, 745 }, 746 { 747 Name: "another-job", 748 PlanSequence: []atc.Step{ 749 { 750 Config: &atc.GetStep{ 751 Name: "another-job", 752 }, 753 }, 754 { 755 Config: &atc.TaskStep{ 756 Name: "some-task", 757 ConfigPath: "some/config/path.yml", 758 }, 759 }, 760 }, 761 }, 762 }, 763 } 764 }) 765 766 Context("when a resource is not used in any jobs", func() { 767 It("returns an error", func() { 768 Expect(errorMessages).To(HaveLen(1)) 769 Expect(errorMessages[0]).To(ContainSubstring("resource 'unused-resource' is not used")) 770 Expect(errorMessages[0]).To(ContainSubstring("resource 'get-alias' is not used")) 771 Expect(errorMessages[0]).To(ContainSubstring("resource 'put-alias' is not used")) 772 }) 773 }) 774 }) 775 776 Describe("invalid resource types", func() { 777 Context("when a resource type has no name", func() { 778 BeforeEach(func() { 779 config.ResourceTypes = append(config.ResourceTypes, atc.ResourceType{ 780 Name: "", 781 }) 782 }) 783 784 It("returns an error", func() { 785 Expect(errorMessages).To(HaveLen(1)) 786 Expect(errorMessages[0]).To(ContainSubstring("invalid resource types:")) 787 Expect(errorMessages[0]).To(ContainSubstring("resource_types[1] has no name")) 788 }) 789 }) 790 791 Context("when a resource has no type", func() { 792 BeforeEach(func() { 793 config.ResourceTypes = append(config.ResourceTypes, atc.ResourceType{ 794 Name: "bogus-resource-type", 795 Type: "", 796 }) 797 }) 798 799 It("returns an error", func() { 800 Expect(errorMessages).To(HaveLen(1)) 801 Expect(errorMessages[0]).To(ContainSubstring("invalid resource types:")) 802 Expect(errorMessages[0]).To(ContainSubstring("resource_types.bogus-resource-type has no type")) 803 }) 804 }) 805 806 Context("when a resource has no name or type", func() { 807 BeforeEach(func() { 808 config.ResourceTypes = append(config.ResourceTypes, atc.ResourceType{ 809 Name: "", 810 Type: "", 811 }) 812 }) 813 814 It("returns an error describing both errors", func() { 815 Expect(errorMessages).To(HaveLen(1)) 816 Expect(errorMessages[0]).To(ContainSubstring("invalid resource types:")) 817 Expect(errorMessages[0]).To(ContainSubstring("resource_types[1] has no name")) 818 Expect(errorMessages[0]).To(ContainSubstring("resource_types[1] has no type")) 819 }) 820 }) 821 822 Context("when two resource types have the same name", func() { 823 BeforeEach(func() { 824 config.ResourceTypes = append(config.ResourceTypes, config.ResourceTypes...) 825 }) 826 827 It("returns an error", func() { 828 Expect(errorMessages).To(HaveLen(1)) 829 Expect(errorMessages[0]).To(ContainSubstring("invalid resource types:")) 830 Expect(errorMessages[0]).To(ContainSubstring("resource_types[0] and resource_types[1] have the same name ('some-resource-type')")) 831 }) 832 }) 833 }) 834 835 Describe("validating a job", func() { 836 var job atc.JobConfig 837 838 BeforeEach(func() { 839 job = atc.JobConfig{ 840 Name: "some-other-job", 841 } 842 config.Groups = []atc.GroupConfig{} 843 }) 844 845 Context("when a job has no name", func() { 846 BeforeEach(func() { 847 job.Name = "" 848 config.Jobs = append(config.Jobs, job) 849 }) 850 851 It("returns an error", func() { 852 Expect(errorMessages).To(HaveLen(1)) 853 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 854 Expect(errorMessages[0]).To(ContainSubstring("jobs[2] has no name")) 855 }) 856 }) 857 858 Context("when a job has a negative build_logs_to_retain", func() { 859 BeforeEach(func() { 860 job.BuildLogsToRetain = -1 861 config.Jobs = append(config.Jobs, job) 862 }) 863 864 It("returns an error", func() { 865 Expect(errorMessages).To(HaveLen(1)) 866 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 867 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job has negative build_logs_to_retain: -1")) 868 }) 869 }) 870 871 Context("when a job has duplicate inputs", func() { 872 BeforeEach(func() { 873 job.PlanSequence = append(job.PlanSequence, atc.Step{ 874 Config: &atc.GetStep{ 875 Name: "some-resource", 876 }, 877 }) 878 job.PlanSequence = append(job.PlanSequence, atc.Step{ 879 Config: &atc.GetStep{ 880 Name: "some-resource", 881 }, 882 }) 883 job.PlanSequence = append(job.PlanSequence, atc.Step{ 884 Config: &atc.GetStep{ 885 Name: "some-resource", 886 }, 887 }) 888 889 config.Jobs = append(config.Jobs, job) 890 }) 891 892 It("returns an error for each step", func() { 893 Expect(errorMessages).To(HaveLen(1)) 894 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 895 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[1].get(some-resource): repeated name")) 896 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[2].get(some-resource): repeated name")) 897 }) 898 }) 899 900 Context("when a job has duplicate inputs with different resources", func() { 901 BeforeEach(func() { 902 job.PlanSequence = append(job.PlanSequence, atc.Step{ 903 Config: &atc.GetStep{ 904 Name: "some-resource", 905 Resource: "a", 906 }, 907 }) 908 job.PlanSequence = append(job.PlanSequence, atc.Step{ 909 Config: &atc.GetStep{ 910 Name: "some-resource", 911 Resource: "b", 912 }, 913 }) 914 job.PlanSequence = append(job.PlanSequence, atc.Step{ 915 Config: &atc.GetStep{ 916 Name: "some-resource", 917 Resource: "c", 918 }, 919 }) 920 921 config.Jobs = append(config.Jobs, job) 922 }) 923 924 It("returns an error for each step", func() { 925 Expect(errorMessages).To(HaveLen(1)) 926 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 927 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[1].get(some-resource): repeated name")) 928 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[2].get(some-resource): repeated name")) 929 }) 930 }) 931 932 Context("when a job gets the same resource multiple times but with different names", func() { 933 BeforeEach(func() { 934 job.PlanSequence = append(job.PlanSequence, atc.Step{ 935 Config: &atc.GetStep{ 936 Name: "a", 937 Resource: "some-resource", 938 }, 939 }) 940 job.PlanSequence = append(job.PlanSequence, atc.Step{ 941 Config: &atc.GetStep{ 942 Name: "b", 943 Resource: "some-resource", 944 }, 945 }) 946 947 config.Jobs = append(config.Jobs, job) 948 }) 949 950 It("returns no errors", func() { 951 Expect(errorMessages).To(HaveLen(0)) 952 }) 953 }) 954 955 Describe("plans", func() { 956 Context("when a task plan has neither a config or a path set", func() { 957 BeforeEach(func() { 958 job.PlanSequence = append(job.PlanSequence, atc.Step{ 959 Config: &atc.TaskStep{ 960 Name: "lol", 961 }, 962 }) 963 964 config.Jobs = append(config.Jobs, job) 965 }) 966 967 It("returns an error", func() { 968 Expect(errorMessages).To(HaveLen(1)) 969 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 970 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].task(lol): must specify either `file:` or `config:`")) 971 }) 972 }) 973 974 Context("when a task plan has config path and config specified", func() { 975 BeforeEach(func() { 976 job.PlanSequence = append(job.PlanSequence, atc.Step{ 977 Config: &atc.TaskStep{ 978 Name: "lol", 979 ConfigPath: "task.yml", 980 Config: &atc.TaskConfig{ 981 Params: atc.TaskEnv{ 982 "param1": "value1", 983 }, 984 }, 985 }, 986 }) 987 988 config.Jobs = append(config.Jobs, job) 989 }) 990 991 It("returns an error", func() { 992 Expect(errorMessages).To(HaveLen(1)) 993 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 994 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].task(lol): must specify one of `file:` or `config:`, not both")) 995 }) 996 }) 997 998 Context("when a task plan is invalid", func() { 999 BeforeEach(func() { 1000 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1001 Config: &atc.TaskStep{ 1002 Name: "some-resource", 1003 Config: &atc.TaskConfig{ 1004 Params: atc.TaskEnv{ 1005 "param1": "value1", 1006 }, 1007 }, 1008 }, 1009 }) 1010 1011 config.Jobs = append(config.Jobs, job) 1012 }) 1013 1014 It("returns an error", func() { 1015 Expect(errorMessages).To(HaveLen(1)) 1016 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1017 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].task(some-resource).config: missing 'platform'")) 1018 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].task(some-resource).config: missing path to executable to run")) 1019 }) 1020 }) 1021 1022 Context("when a put plan has refers to a resource that does exist", func() { 1023 BeforeEach(func() { 1024 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1025 Config: &atc.PutStep{ 1026 Name: "some-resource", 1027 }, 1028 }) 1029 1030 config.Jobs = append(config.Jobs, job) 1031 }) 1032 1033 It("does not return an error", func() { 1034 Expect(errorMessages).To(HaveLen(0)) 1035 }) 1036 }) 1037 1038 Context("when a get plan has refers to a resource that does not exist", func() { 1039 BeforeEach(func() { 1040 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1041 Config: &atc.GetStep{ 1042 Name: "some-nonexistent-resource", 1043 }, 1044 }) 1045 1046 config.Jobs = append(config.Jobs, job) 1047 }) 1048 1049 It("returns an error", func() { 1050 Expect(errorMessages).To(HaveLen(1)) 1051 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1052 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1053 }) 1054 }) 1055 1056 Context("when a put plan has refers to a resource that does not exist", func() { 1057 BeforeEach(func() { 1058 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1059 Config: &atc.PutStep{ 1060 Name: "some-nonexistent-resource", 1061 }, 1062 }) 1063 1064 config.Jobs = append(config.Jobs, job) 1065 }) 1066 1067 It("returns an error", func() { 1068 Expect(errorMessages).To(HaveLen(1)) 1069 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1070 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].put(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1071 }) 1072 }) 1073 1074 Context("when a get plan has a custom name but refers to a resource that does exist", func() { 1075 BeforeEach(func() { 1076 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1077 Config: &atc.GetStep{ 1078 Name: "custom-name", 1079 Resource: "some-resource", 1080 }, 1081 }) 1082 1083 config.Jobs = append(config.Jobs, job) 1084 }) 1085 1086 It("does not return an error", func() { 1087 Expect(errorMessages).To(HaveLen(0)) 1088 }) 1089 }) 1090 1091 Context("when a get plan has a custom name but refers to a resource that does not exist", func() { 1092 BeforeEach(func() { 1093 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1094 Config: &atc.GetStep{ 1095 Name: "custom-name", 1096 Resource: "some-missing-resource", 1097 }, 1098 }) 1099 1100 config.Jobs = append(config.Jobs, job) 1101 }) 1102 1103 It("returns an error", func() { 1104 Expect(errorMessages).To(HaveLen(1)) 1105 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1106 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].get(custom-name): unknown resource 'some-missing-resource'")) 1107 }) 1108 }) 1109 1110 Context("when a put plan has a custom name but refers to a resource that does exist", func() { 1111 BeforeEach(func() { 1112 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1113 Config: &atc.PutStep{ 1114 Name: "custom-name", 1115 Resource: "some-resource", 1116 }, 1117 }) 1118 1119 config.Jobs = append(config.Jobs, job) 1120 }) 1121 1122 It("does not return an error", func() { 1123 Expect(errorMessages).To(HaveLen(0)) 1124 }) 1125 }) 1126 1127 Context("when a put plan has a custom name but refers to a resource that does not exist", func() { 1128 BeforeEach(func() { 1129 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1130 Config: &atc.PutStep{ 1131 Name: "custom-name", 1132 Resource: "some-missing-resource", 1133 }, 1134 }) 1135 1136 config.Jobs = append(config.Jobs, job) 1137 }) 1138 1139 It("does return an error", func() { 1140 Expect(errorMessages).To(HaveLen(1)) 1141 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].put(custom-name): unknown resource 'some-missing-resource'")) 1142 }) 1143 }) 1144 1145 Context("when a job success hook refers to a resource that does exist", func() { 1146 BeforeEach(func() { 1147 job.OnSuccess = &atc.Step{ 1148 Config: &atc.GetStep{ 1149 Name: "some-resource", 1150 }, 1151 } 1152 1153 config.Jobs = append(config.Jobs, job) 1154 }) 1155 1156 It("does not return an error", func() { 1157 Expect(errorMessages).To(HaveLen(0)) 1158 }) 1159 }) 1160 1161 Context("when a job success hook refers to a resource that does not exist", func() { 1162 BeforeEach(func() { 1163 job.OnSuccess = &atc.Step{ 1164 Config: &atc.GetStep{ 1165 Name: "some-nonexistent-resource", 1166 }, 1167 } 1168 1169 config.Jobs = append(config.Jobs, job) 1170 }) 1171 1172 It("returns an error", func() { 1173 Expect(errorMessages).To(HaveLen(1)) 1174 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1175 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.on_success.get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1176 }) 1177 }) 1178 1179 Context("when a job failure hook refers to a resource that does exist", func() { 1180 BeforeEach(func() { 1181 job.OnFailure = &atc.Step{ 1182 Config: &atc.GetStep{ 1183 Name: "some-resource", 1184 }, 1185 } 1186 1187 config.Jobs = append(config.Jobs, job) 1188 }) 1189 1190 It("does not return an error", func() { 1191 Expect(errorMessages).To(HaveLen(0)) 1192 }) 1193 }) 1194 1195 Context("when a job failure hook refers to a resource that does not exist", func() { 1196 BeforeEach(func() { 1197 job.OnFailure = &atc.Step{ 1198 Config: &atc.GetStep{ 1199 Name: "some-nonexistent-resource", 1200 }, 1201 } 1202 1203 config.Jobs = append(config.Jobs, job) 1204 }) 1205 1206 It("returns an error", func() { 1207 Expect(errorMessages).To(HaveLen(1)) 1208 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1209 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.on_failure.get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1210 }) 1211 }) 1212 1213 Context("when a job error hook refers to a resource that does exist", func() { 1214 BeforeEach(func() { 1215 job.OnError = &atc.Step{ 1216 Config: &atc.GetStep{ 1217 Name: "some-resource", 1218 }, 1219 } 1220 1221 config.Jobs = append(config.Jobs, job) 1222 }) 1223 1224 It("does not return an error", func() { 1225 Expect(errorMessages).To(HaveLen(0)) 1226 }) 1227 }) 1228 1229 Context("when a job ensure hook refers to a resource that does not exist", func() { 1230 BeforeEach(func() { 1231 job.OnError = &atc.Step{ 1232 Config: &atc.GetStep{ 1233 Name: "some-nonexistent-resource", 1234 }, 1235 } 1236 1237 config.Jobs = append(config.Jobs, job) 1238 }) 1239 1240 It("returns an error", func() { 1241 Expect(errorMessages).To(HaveLen(1)) 1242 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1243 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.on_error.get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1244 }) 1245 }) 1246 1247 Context("when a job abort hook refers to a resource that does exist", func() { 1248 BeforeEach(func() { 1249 job.OnAbort = &atc.Step{ 1250 Config: &atc.GetStep{ 1251 Name: "some-resource", 1252 }, 1253 } 1254 1255 config.Jobs = append(config.Jobs, job) 1256 }) 1257 1258 It("does not return an error", func() { 1259 Expect(errorMessages).To(HaveLen(0)) 1260 }) 1261 }) 1262 1263 Context("when a job abort hook refers to a resource that does not exist", func() { 1264 BeforeEach(func() { 1265 job.OnAbort = &atc.Step{ 1266 Config: &atc.GetStep{ 1267 Name: "some-nonexistent-resource", 1268 }, 1269 } 1270 1271 config.Jobs = append(config.Jobs, job) 1272 }) 1273 1274 It("returns an error", func() { 1275 Expect(errorMessages).To(HaveLen(1)) 1276 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1277 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.on_abort.get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1278 }) 1279 }) 1280 1281 Context("when a job ensure hook refers to a resource that does exist", func() { 1282 BeforeEach(func() { 1283 job.Ensure = &atc.Step{ 1284 Config: &atc.GetStep{ 1285 Name: "some-resource", 1286 }, 1287 } 1288 1289 config.Jobs = append(config.Jobs, job) 1290 }) 1291 1292 It("does not return an error", func() { 1293 Expect(errorMessages).To(HaveLen(0)) 1294 }) 1295 }) 1296 1297 Context("when a job ensure hook refers to a resource that does not exist", func() { 1298 BeforeEach(func() { 1299 job.Ensure = &atc.Step{ 1300 Config: &atc.GetStep{ 1301 Name: "some-nonexistent-resource", 1302 }, 1303 } 1304 1305 config.Jobs = append(config.Jobs, job) 1306 }) 1307 1308 It("returns an error", func() { 1309 Expect(errorMessages).To(HaveLen(1)) 1310 Expect(errorMessages[0]).To(ContainSubstring("invalid jobs:")) 1311 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.ensure.get(some-nonexistent-resource): unknown resource 'some-nonexistent-resource'")) 1312 }) 1313 }) 1314 1315 Context("when a get plan refers to a 'put' resource that exists in another job's hook", func() { 1316 var ( 1317 job1 atc.JobConfig 1318 job2 atc.JobConfig 1319 ) 1320 BeforeEach(func() { 1321 job1 = atc.JobConfig{ 1322 Name: "job-one", 1323 } 1324 job2 = atc.JobConfig{ 1325 Name: "job-two", 1326 } 1327 1328 job1.PlanSequence = append(job1.PlanSequence, atc.Step{ 1329 Config: &atc.OnSuccessStep{ 1330 Step: &atc.TaskStep{ 1331 Name: "job-one", 1332 ConfigPath: "job-one-config-path", 1333 }, 1334 Hook: atc.Step{ 1335 Config: &atc.PutStep{ 1336 Name: "some-resource", 1337 }, 1338 }, 1339 }, 1340 }) 1341 1342 job2.PlanSequence = append(job2.PlanSequence, atc.Step{ 1343 Config: &atc.GetStep{ 1344 Name: "some-resource", 1345 Passed: []string{"job-one"}, 1346 }, 1347 }) 1348 config.Jobs = append(config.Jobs, job1, job2) 1349 }) 1350 1351 It("does not return an error", func() { 1352 Expect(errorMessages).To(HaveLen(0)) 1353 }) 1354 }) 1355 1356 Context("when a get plan refers to a 'get' resource that exists in another job's hook", func() { 1357 var ( 1358 job1 atc.JobConfig 1359 job2 atc.JobConfig 1360 ) 1361 BeforeEach(func() { 1362 job1 = atc.JobConfig{ 1363 Name: "job-one", 1364 } 1365 job2 = atc.JobConfig{ 1366 Name: "job-two", 1367 } 1368 1369 job1.PlanSequence = append(job1.PlanSequence, atc.Step{ 1370 Config: &atc.OnSuccessStep{ 1371 Step: &atc.TaskStep{ 1372 Name: "job-one", 1373 ConfigPath: "job-one-config-path", 1374 }, 1375 Hook: atc.Step{ 1376 Config: &atc.GetStep{ 1377 Name: "some-resource", 1378 }, 1379 }, 1380 }, 1381 }) 1382 1383 job2.PlanSequence = append(job2.PlanSequence, atc.Step{ 1384 Config: &atc.GetStep{ 1385 Name: "some-resource", 1386 Passed: []string{"job-one"}, 1387 }, 1388 }) 1389 config.Jobs = append(config.Jobs, job1, job2) 1390 }) 1391 1392 It("does not return an error", func() { 1393 Expect(errorMessages).To(HaveLen(0)) 1394 }) 1395 }) 1396 1397 Context("when a get plan refers to a 'put' resource that exists in another job's try-step", func() { 1398 var ( 1399 job1 atc.JobConfig 1400 job2 atc.JobConfig 1401 ) 1402 BeforeEach(func() { 1403 job1 = atc.JobConfig{ 1404 Name: "job-one", 1405 } 1406 job2 = atc.JobConfig{ 1407 Name: "job-two", 1408 } 1409 1410 job1.PlanSequence = append(job1.PlanSequence, atc.Step{ 1411 Config: &atc.TryStep{ 1412 Step: atc.Step{ 1413 Config: &atc.PutStep{ 1414 Name: "some-resource", 1415 }, 1416 }, 1417 }, 1418 }) 1419 1420 job2.PlanSequence = append(job2.PlanSequence, atc.Step{ 1421 Config: &atc.GetStep{ 1422 Name: "some-resource", 1423 Passed: []string{"job-one"}, 1424 }, 1425 }) 1426 config.Jobs = append(config.Jobs, job1, job2) 1427 1428 }) 1429 1430 It("does not return an error", func() { 1431 Expect(errorMessages).To(HaveLen(0)) 1432 }) 1433 }) 1434 1435 Context("when a get plan refers to a 'get' resource that exists in another job's try-step", func() { 1436 var ( 1437 job1 atc.JobConfig 1438 job2 atc.JobConfig 1439 ) 1440 BeforeEach(func() { 1441 job1 = atc.JobConfig{ 1442 Name: "job-one", 1443 } 1444 job2 = atc.JobConfig{ 1445 Name: "job-two", 1446 } 1447 1448 job1.PlanSequence = append(job1.PlanSequence, atc.Step{ 1449 Config: &atc.TryStep{ 1450 Step: atc.Step{ 1451 Config: &atc.GetStep{ 1452 Name: "some-resource", 1453 }, 1454 }, 1455 }, 1456 }) 1457 1458 job2.PlanSequence = append(job2.PlanSequence, atc.Step{ 1459 Config: &atc.GetStep{ 1460 Name: "some-resource", 1461 Passed: []string{"job-one"}, 1462 }, 1463 }) 1464 config.Jobs = append(config.Jobs, job1, job2) 1465 1466 }) 1467 1468 It("does not return an error", func() { 1469 Expect(errorMessages).To(HaveLen(0)) 1470 }) 1471 }) 1472 1473 Context("when a plan has an invalid step within an abort", func() { 1474 BeforeEach(func() { 1475 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1476 Config: &atc.OnAbortStep{ 1477 Step: &atc.GetStep{ 1478 Name: "some-resource", 1479 }, 1480 Hook: atc.Step{ 1481 Config: &atc.PutStep{ 1482 Name: "custom-name", 1483 Resource: "some-missing-resource", 1484 }, 1485 }, 1486 }, 1487 }) 1488 1489 config.Jobs = append(config.Jobs, job) 1490 }) 1491 1492 It("throws a validation error", func() { 1493 Expect(errorMessages).To(HaveLen(1)) 1494 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].on_abort.put(custom-name): unknown resource 'some-missing-resource'")) 1495 }) 1496 }) 1497 1498 Context("when a plan has an invalid step within an error", func() { 1499 BeforeEach(func() { 1500 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1501 Config: &atc.OnErrorStep{ 1502 Step: &atc.GetStep{ 1503 Name: "some-resource", 1504 }, 1505 Hook: atc.Step{ 1506 Config: &atc.PutStep{ 1507 Name: "custom-name", 1508 Resource: "some-missing-resource", 1509 }, 1510 }, 1511 }, 1512 }) 1513 1514 config.Jobs = append(config.Jobs, job) 1515 }) 1516 1517 It("throws a validation error", func() { 1518 Expect(errorMessages).To(HaveLen(1)) 1519 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].on_error.put(custom-name): unknown resource 'some-missing-resource'")) 1520 }) 1521 }) 1522 1523 Context("when a plan has an invalid step within an ensure", func() { 1524 BeforeEach(func() { 1525 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1526 Config: &atc.EnsureStep{ 1527 Step: &atc.GetStep{ 1528 Name: "some-resource", 1529 }, 1530 Hook: atc.Step{ 1531 Config: &atc.PutStep{ 1532 Name: "custom-name", 1533 Resource: "some-missing-resource", 1534 }, 1535 }, 1536 }, 1537 }) 1538 1539 config.Jobs = append(config.Jobs, job) 1540 }) 1541 1542 It("throws a validation error", func() { 1543 Expect(errorMessages).To(HaveLen(1)) 1544 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].ensure.put(custom-name): unknown resource 'some-missing-resource'")) 1545 }) 1546 }) 1547 1548 Context("when a plan has an invalid step within a success", func() { 1549 BeforeEach(func() { 1550 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1551 Config: &atc.OnSuccessStep{ 1552 Step: &atc.GetStep{ 1553 Name: "some-resource", 1554 }, 1555 Hook: atc.Step{ 1556 Config: &atc.PutStep{ 1557 Name: "custom-name", 1558 Resource: "some-missing-resource", 1559 }, 1560 }, 1561 }, 1562 }) 1563 1564 config.Jobs = append(config.Jobs, job) 1565 }) 1566 1567 It("throws a validation error", func() { 1568 Expect(errorMessages).To(HaveLen(1)) 1569 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].on_success.put(custom-name): unknown resource 'some-missing-resource'")) 1570 }) 1571 }) 1572 1573 Context("when a plan has an invalid step within a failure", func() { 1574 BeforeEach(func() { 1575 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1576 Config: &atc.OnFailureStep{ 1577 Step: &atc.GetStep{ 1578 Name: "some-resource", 1579 }, 1580 Hook: atc.Step{ 1581 Config: &atc.PutStep{ 1582 Name: "custom-name", 1583 Resource: "some-missing-resource", 1584 }, 1585 }, 1586 }, 1587 }) 1588 1589 config.Jobs = append(config.Jobs, job) 1590 }) 1591 1592 It("throws a validation error", func() { 1593 Expect(errorMessages).To(HaveLen(1)) 1594 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].on_failure.put(custom-name): unknown resource 'some-missing-resource'")) 1595 }) 1596 }) 1597 1598 Context("when a plan has an invalid step within a try", func() { 1599 BeforeEach(func() { 1600 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1601 Config: &atc.TryStep{ 1602 Step: atc.Step{ 1603 Config: &atc.PutStep{ 1604 Name: "custom-name", 1605 Resource: "some-missing-resource", 1606 }, 1607 }, 1608 }, 1609 }) 1610 1611 config.Jobs = append(config.Jobs, job) 1612 }) 1613 1614 It("throws a validation error", func() { 1615 Expect(errorMessages).To(HaveLen(1)) 1616 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].try.put(custom-name): unknown resource 'some-missing-resource'")) 1617 }) 1618 }) 1619 1620 Context("when a plan has an invalid timeout in a step", func() { 1621 BeforeEach(func() { 1622 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1623 Config: &atc.TimeoutStep{ 1624 Step: &atc.GetStep{ 1625 Name: "some-resource", 1626 }, 1627 Duration: "nope", 1628 }, 1629 }) 1630 1631 config.Jobs = append(config.Jobs, job) 1632 }) 1633 1634 It("throws a validation error", func() { 1635 Expect(errorMessages).To(HaveLen(1)) 1636 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].timeout: invalid duration 'nope'")) 1637 }) 1638 }) 1639 1640 Context("when a retry plan has a negative attempts number", func() { 1641 BeforeEach(func() { 1642 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1643 Config: &atc.RetryStep{ 1644 Step: &atc.PutStep{ 1645 Name: "some-resource", 1646 }, 1647 Attempts: 0, 1648 }, 1649 }) 1650 1651 config.Jobs = append(config.Jobs, job) 1652 }) 1653 1654 It("does return an error", func() { 1655 Expect(errorMessages).To(HaveLen(1)) 1656 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].attempts: must be greater than 0")) 1657 }) 1658 }) 1659 1660 Context("when a set_pipeline step has no file configured", func() { 1661 BeforeEach(func() { 1662 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1663 Config: &atc.SetPipelineStep{ 1664 Name: "other-pipeline", 1665 }, 1666 }) 1667 1668 config.Jobs = append(config.Jobs, job) 1669 }) 1670 1671 It("does return an error", func() { 1672 Expect(errorMessages).To(HaveLen(1)) 1673 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].set_pipeline(other-pipeline): no file specified")) 1674 }) 1675 }) 1676 1677 Context("when a job's input's passed constraints reference a bogus job", func() { 1678 BeforeEach(func() { 1679 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1680 Config: &atc.GetStep{ 1681 Name: "lol", 1682 Passed: []string{"bogus-job"}, 1683 }, 1684 }) 1685 1686 config.Jobs = append(config.Jobs, job) 1687 }) 1688 1689 It("returns an error", func() { 1690 Expect(errorMessages).To(HaveLen(1)) 1691 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].get(lol).passed: unknown job 'bogus-job'")) 1692 }) 1693 }) 1694 1695 Context("when a job's input's passed constraints references a valid job that has the resource as an output", func() { 1696 BeforeEach(func() { 1697 config.Jobs[0].PlanSequence = append(config.Jobs[0].PlanSequence, atc.Step{ 1698 Config: &atc.PutStep{ 1699 Name: "custom-name", 1700 Resource: "some-resource", 1701 }, 1702 }) 1703 1704 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1705 Config: &atc.GetStep{ 1706 Name: "some-resource", 1707 Passed: []string{"some-job"}, 1708 }, 1709 }) 1710 1711 config.Jobs = append(config.Jobs, job) 1712 }) 1713 1714 It("does not return an error", func() { 1715 Expect(errorMessages).To(HaveLen(0)) 1716 }) 1717 }) 1718 1719 Context("when a job's input's passed constraints references a valid job that has the resource as an input", func() { 1720 BeforeEach(func() { 1721 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1722 Config: &atc.GetStep{ 1723 Name: "some-resource", 1724 Passed: []string{"some-job"}, 1725 }, 1726 }) 1727 1728 config.Jobs = append(config.Jobs, job) 1729 }) 1730 1731 It("does not return an error", func() { 1732 Expect(errorMessages).To(HaveLen(0)) 1733 }) 1734 }) 1735 1736 Context("when a job's input's passed constraints references a valid job that has the resource (with a custom name) as an input", func() { 1737 BeforeEach(func() { 1738 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1739 Config: &atc.GetStep{ 1740 Name: "custom-name", 1741 Resource: "some-resource", 1742 Passed: []string{"some-job"}, 1743 }, 1744 }) 1745 1746 config.Jobs = append(config.Jobs, job) 1747 }) 1748 1749 It("does not return an error", func() { 1750 Expect(errorMessages).To(HaveLen(0)) 1751 }) 1752 }) 1753 1754 Context("when a job's input's passed constraints references a valid job that does not have the resource as an input or output", func() { 1755 BeforeEach(func() { 1756 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1757 Config: &atc.GetStep{ 1758 Name: "some-resource", 1759 Passed: []string{"some-empty-job"}, 1760 }, 1761 }) 1762 1763 config.Jobs = append(config.Jobs, job) 1764 }) 1765 1766 It("returns an error", func() { 1767 Expect(errorMessages).To(HaveLen(1)) 1768 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].get(some-resource).passed: job 'some-empty-job' does not interact with resource 'some-resource'")) 1769 }) 1770 }) 1771 1772 Context("when a load_var has not defined 'File'", func() { 1773 BeforeEach(func() { 1774 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1775 Config: &atc.LoadVarStep{ 1776 Name: "a-var", 1777 }, 1778 }) 1779 1780 config.Jobs = append(config.Jobs, job) 1781 }) 1782 1783 It("returns an error", func() { 1784 Expect(errorMessages).To(HaveLen(1)) 1785 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].load_var(a-var): no file specified")) 1786 }) 1787 }) 1788 1789 Context("when two load_var steps have same name", func() { 1790 BeforeEach(func() { 1791 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1792 Config: &atc.LoadVarStep{ 1793 Name: "a-var", 1794 File: "file1", 1795 }, 1796 }, atc.Step{ 1797 Config: &atc.LoadVarStep{ 1798 Name: "a-var", 1799 File: "file1", 1800 }, 1801 }) 1802 1803 config.Jobs = append(config.Jobs, job) 1804 }) 1805 1806 It("returns an error", func() { 1807 Expect(errorMessages).To(HaveLen(1)) 1808 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[1].load_var(a-var): repeated var name")) 1809 }) 1810 }) 1811 1812 Context("when a step has unknown fields", func() { 1813 BeforeEach(func() { 1814 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1815 Config: &atc.TaskStep{ 1816 Name: "task", 1817 ConfigPath: "some-file", 1818 }, 1819 UnknownFields: map[string]*json.RawMessage{"bogus": nil}, 1820 }) 1821 1822 config.Jobs = append(config.Jobs, job) 1823 }) 1824 1825 It("returns an error", func() { 1826 Expect(errorMessages).To(HaveLen(1)) 1827 Expect(errorMessages[0]).To(ContainSubstring(`jobs.some-other-job.plan.do[0]: unknown fields ["bogus"]`)) 1828 }) 1829 }) 1830 1831 Context("when an across step is valid", func() { 1832 BeforeEach(func() { 1833 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1834 Config: &atc.AcrossStep{ 1835 Step: &atc.PutStep{ 1836 Name: "some-resource", 1837 }, 1838 Vars: []atc.AcrossVarConfig{ 1839 { 1840 Var: "var1", 1841 Values: []interface{}{"v1", "v2"}, 1842 }, 1843 { 1844 Var: "var2", 1845 MaxInFlight: &atc.MaxInFlightConfig{Limit: 2}, 1846 Values: []interface{}{"v1", "v2"}, 1847 }, 1848 { 1849 Var: "var3", 1850 MaxInFlight: &atc.MaxInFlightConfig{All: true}, 1851 Values: []interface{}{"v1", "v2"}, 1852 }, 1853 }, 1854 }, 1855 }) 1856 1857 config.Jobs = append(config.Jobs, job) 1858 }) 1859 1860 It("succeeds", func() { 1861 Expect(errorMessages).To(HaveLen(0)) 1862 }) 1863 }) 1864 1865 Context("when an across step has no vars", func() { 1866 BeforeEach(func() { 1867 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1868 Config: &atc.AcrossStep{ 1869 Step: &atc.PutStep{ 1870 Name: "some-resource", 1871 }, 1872 Vars: []atc.AcrossVarConfig{}, 1873 }, 1874 }) 1875 1876 config.Jobs = append(config.Jobs, job) 1877 }) 1878 1879 It("returns an error", func() { 1880 Expect(errorMessages).To(HaveLen(1)) 1881 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].across: no vars specified")) 1882 }) 1883 }) 1884 1885 Context("when an across step repeats a var name", func() { 1886 BeforeEach(func() { 1887 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1888 Config: &atc.AcrossStep{ 1889 Step: &atc.PutStep{ 1890 Name: "some-resource", 1891 }, 1892 Vars: []atc.AcrossVarConfig{ 1893 { 1894 Var: "var1", 1895 }, 1896 { 1897 Var: "var1", 1898 }, 1899 }, 1900 }, 1901 }) 1902 1903 config.Jobs = append(config.Jobs, job) 1904 }) 1905 1906 It("returns an error", func() { 1907 Expect(errorMessages).To(HaveLen(1)) 1908 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].across[1]: repeated var name")) 1909 }) 1910 }) 1911 1912 Context("when an across step shadows a var name from a parent scope", func() { 1913 BeforeEach(func() { 1914 job.PlanSequence = append(job.PlanSequence, 1915 atc.Step{Config: &atc.LoadVarStep{ 1916 Name: "var1", 1917 File: "unused", 1918 }}, 1919 atc.Step{ 1920 Config: &atc.AcrossStep{ 1921 Step: &atc.PutStep{ 1922 Name: "some-resource", 1923 }, 1924 Vars: []atc.AcrossVarConfig{ 1925 { 1926 Var: "var1", 1927 }, 1928 }, 1929 }, 1930 }) 1931 1932 config.Jobs = append(config.Jobs, job) 1933 }) 1934 1935 It("returns a warning", func() { 1936 Expect(errorMessages).To(BeEmpty()) 1937 Expect(warnings).To(HaveLen(1)) 1938 Expect(warnings[0].Message).To(ContainSubstring("jobs.some-other-job.plan.do[1].across[0]: shadows local var 'var1'")) 1939 }) 1940 }) 1941 1942 Context("when a substep of the across step shadows a var name from a parent scope", func() { 1943 BeforeEach(func() { 1944 job.PlanSequence = append(job.PlanSequence, 1945 atc.Step{Config: &atc.LoadVarStep{ 1946 Name: "a", 1947 File: "unused", 1948 }}, 1949 atc.Step{ 1950 Config: &atc.AcrossStep{ 1951 Step: &atc.LoadVarStep{ 1952 Name: "a", 1953 File: "unused", 1954 }, 1955 Vars: []atc.AcrossVarConfig{ 1956 { 1957 Var: "b", 1958 }, 1959 }, 1960 }, 1961 }) 1962 1963 config.Jobs = append(config.Jobs, job) 1964 }) 1965 1966 It("returns a warning", func() { 1967 Expect(errorMessages).To(BeEmpty()) 1968 Expect(warnings).To(HaveLen(1)) 1969 Expect(warnings[0].Message).To(ContainSubstring("jobs.some-other-job.plan.do[1].across.load_var(a): shadows local var 'a'")) 1970 }) 1971 }) 1972 1973 Context("when an across step has a non-positive limit", func() { 1974 BeforeEach(func() { 1975 job.PlanSequence = append(job.PlanSequence, atc.Step{ 1976 Config: &atc.AcrossStep{ 1977 Step: &atc.PutStep{ 1978 Name: "some-resource", 1979 }, 1980 Vars: []atc.AcrossVarConfig{ 1981 { 1982 Var: "var", 1983 MaxInFlight: &atc.MaxInFlightConfig{Limit: 0}, 1984 }, 1985 }, 1986 }, 1987 }) 1988 1989 config.Jobs = append(config.Jobs, job) 1990 }) 1991 1992 It("returns an error", func() { 1993 Expect(errorMessages).To(HaveLen(1)) 1994 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].across[0].max_in_flight: must be greater than 0")) 1995 }) 1996 }) 1997 1998 Context("when the across step is not enabled", func() { 1999 BeforeEach(func() { 2000 atc.EnableAcrossStep = false 2001 2002 job.PlanSequence = append(job.PlanSequence, atc.Step{ 2003 Config: &atc.AcrossStep{ 2004 Step: &atc.PutStep{ 2005 Name: "some-resource", 2006 }, 2007 Vars: []atc.AcrossVarConfig{ 2008 { 2009 Var: "var", 2010 }, 2011 }, 2012 }, 2013 }) 2014 2015 config.Jobs = append(config.Jobs, job) 2016 }) 2017 2018 It("returns an error", func() { 2019 Expect(errorMessages).To(HaveLen(1)) 2020 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-other-job.plan.do[0].across: the across step must be explicitly opted-in to using the `--enable-across-step` flag")) 2021 }) 2022 }) 2023 }) 2024 2025 Context("when two jobs have the same name", func() { 2026 BeforeEach(func() { 2027 config.Jobs = append(config.Jobs, config.Jobs...) 2028 }) 2029 2030 It("returns an error", func() { 2031 Expect(errorMessages).To(HaveLen(1)) 2032 Expect(errorMessages[0]).To(ContainSubstring("jobs[0] and jobs[2] have the same name ('some-job')")) 2033 }) 2034 }) 2035 2036 Context("when a job has build_log_retention and deprecated build_logs_to_retain", func() { 2037 BeforeEach(func() { 2038 config.Jobs[0].BuildLogRetention = &atc.BuildLogRetention{ 2039 Builds: 1, 2040 Days: 1, 2041 } 2042 config.Jobs[0].BuildLogsToRetain = 1 2043 }) 2044 2045 It("returns an error", func() { 2046 Expect(errorMessages).To(HaveLen(1)) 2047 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-job can't use both build_log_retention and build_logs_to_retain")) 2048 }) 2049 }) 2050 2051 Context("when a job has negative build_logs_to_retain", func() { 2052 BeforeEach(func() { 2053 config.Jobs[0].BuildLogsToRetain = -1 2054 }) 2055 2056 It("returns an error", func() { 2057 Expect(errorMessages).To(HaveLen(1)) 2058 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-job has negative build_logs_to_retain: -1")) 2059 }) 2060 }) 2061 2062 Context("when a job has negative build_log_retention values", func() { 2063 BeforeEach(func() { 2064 config.Jobs[0].BuildLogRetention = &atc.BuildLogRetention{ 2065 Builds: -1, 2066 Days: -1, 2067 } 2068 }) 2069 2070 It("returns an error", func() { 2071 Expect(errorMessages).To(HaveLen(1)) 2072 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-job has negative build_log_retention.builds: -1")) 2073 Expect(errorMessages[0]).To(ContainSubstring("jobs.some-job has negative build_log_retention.days: -1")) 2074 }) 2075 }) 2076 }) 2077 2078 Describe("validating display config", func() { 2079 Context("when the background image is a valid http URL", func() { 2080 BeforeEach(func() { 2081 config.Display = &atc.DisplayConfig{ 2082 BackgroundImage: "http://example.com/image.jpg", 2083 } 2084 }) 2085 2086 It("does not return an error", func() { 2087 Expect(errorMessages).To(HaveLen(0)) 2088 }) 2089 }) 2090 2091 Context("when the background image is a valid relative URL", func() { 2092 BeforeEach(func() { 2093 config.Display = &atc.DisplayConfig{ 2094 BackgroundImage: "public/images/image.jpg", 2095 } 2096 }) 2097 2098 It("does not return an error", func() { 2099 Expect(errorMessages).To(HaveLen(0)) 2100 }) 2101 }) 2102 2103 Context("when the background image uses an unsupported scheme", func() { 2104 BeforeEach(func() { 2105 config.Display = &atc.DisplayConfig{ 2106 BackgroundImage: "data:image/png;base64, iVBORw0KGgoA", 2107 } 2108 }) 2109 2110 It("returns an error", func() { 2111 Expect(errorMessages).To(HaveLen(1)) 2112 Expect(errorMessages[0]).To(ContainSubstring("invalid display config:")) 2113 Expect(errorMessages[0]).To(ContainSubstring("background_image scheme must be either http, https or relative")) 2114 }) 2115 }) 2116 2117 Context("when the background image is an invalid URL", func() { 2118 BeforeEach(func() { 2119 config.Display = &atc.DisplayConfig{ 2120 BackgroundImage: "://example.com", 2121 } 2122 }) 2123 2124 It("returns an error", func() { 2125 Expect(errorMessages).To(HaveLen(1)) 2126 Expect(errorMessages[0]).To(ContainSubstring("invalid display config:")) 2127 Expect(errorMessages[0]).To(ContainSubstring("background_image is not a valid URL: ://example.com")) 2128 }) 2129 }) 2130 }) 2131 2132 Describe("invalid pipeline", func() { 2133 Context("contains zero jobs", func() { 2134 BeforeEach(func() { 2135 config = atc.Config{} 2136 }) 2137 It("is an invalid pipeline", func() { 2138 Expect(errorMessages).To(HaveLen(1)) 2139 Expect(errorMessages[0]).To(ContainSubstring("pipeline must contain at least one job")) 2140 }) 2141 2142 }) 2143 }) 2144 })