github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/integration/execute_test.go (about) 1 package integration_test 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "net/url" 11 "os" 12 "os/exec" 13 "path" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "syscall" 18 "time" 19 20 "github.com/pf-qiu/concourse/v6/atc" 21 "github.com/pf-qiu/concourse/v6/atc/event" 22 "github.com/pf-qiu/concourse/v6/atc/testhelpers" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 "github.com/onsi/gomega/gbytes" 26 "github.com/onsi/gomega/gexec" 27 "github.com/onsi/gomega/ghttp" 28 "github.com/vito/go-sse/sse" 29 ) 30 31 var _ = Describe("Fly CLI", func() { 32 var tmpdir string 33 var buildDir string 34 var taskConfigPath string 35 36 var streaming chan struct{} 37 var events chan atc.Event 38 var uploadedBits chan struct{} 39 40 var expectedPlan atc.Plan 41 var taskPlan atc.Plan 42 var workerArtifact = atc.WorkerArtifact{ 43 ID: 125, 44 Name: "some-dir", 45 } 46 var planFactory atc.PlanFactory 47 48 BeforeEach(func() { 49 var err error 50 tmpdir, err = ioutil.TempDir("", "fly-build-dir") 51 Expect(err).NotTo(HaveOccurred()) 52 53 buildDir = filepath.Join(tmpdir, "fixture") 54 55 err = os.Mkdir(buildDir, 0755) 56 Expect(err).NotTo(HaveOccurred()) 57 58 taskConfigPath = filepath.Join(buildDir, "task.yml") 59 60 err = ioutil.WriteFile( 61 taskConfigPath, 62 []byte(`--- 63 platform: some-platform 64 65 image_resource: 66 type: registry-image 67 source: 68 repository: ubuntu 69 70 inputs: 71 - name: fixture 72 73 params: 74 FOO: bar 75 BAZ: buzz 76 X: 1 77 EMPTY: 78 79 run: 80 path: find 81 args: [.] 82 `), 83 0644, 84 ) 85 Expect(err).NotTo(HaveOccurred()) 86 87 streaming = make(chan struct{}) 88 events = make(chan atc.Event) 89 90 planFactory = atc.NewPlanFactory(0) 91 92 taskPlan = planFactory.NewPlan(atc.TaskPlan{ 93 Name: "one-off", 94 Config: &atc.TaskConfig{ 95 Platform: "some-platform", 96 ImageResource: &atc.ImageResource{ 97 Type: "registry-image", 98 Source: atc.Source{ 99 "repository": "ubuntu", 100 }, 101 }, 102 Inputs: []atc.TaskInputConfig{ 103 {Name: "fixture"}, 104 }, 105 Params: map[string]string{ 106 "FOO": "bar", 107 "BAZ": "buzz", 108 "X": "1", 109 "EMPTY": "", 110 }, 111 Run: atc.TaskRunConfig{ 112 Path: "find", 113 Args: []string{"."}, 114 }, 115 }, 116 }) 117 118 expectedPlan = planFactory.NewPlan(atc.DoPlan{ 119 planFactory.NewPlan(atc.AggregatePlan{ 120 planFactory.NewPlan(atc.ArtifactInputPlan{ 121 ArtifactID: 125, 122 Name: filepath.Base(buildDir), 123 }), 124 }), 125 taskPlan, 126 }) 127 }) 128 129 AfterEach(func() { 130 os.RemoveAll(tmpdir) 131 close(uploadedBits) 132 }) 133 134 JustBeforeEach(func() { 135 uploadedBits = make(chan struct{}, 5) // at most there should only be 2 uploads 136 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 137 ghttp.CombineHandlers( 138 func(w http.ResponseWriter, req *http.Request) { 139 gr, err := gzip.NewReader(req.Body) 140 Expect(err).NotTo(HaveOccurred()) 141 142 tr := tar.NewReader(gr) 143 144 hdr, err := tr.Next() 145 Expect(err).NotTo(HaveOccurred()) 146 147 Expect(hdr.Name).To(Equal("./")) 148 149 hdr, err = tr.Next() 150 Expect(err).NotTo(HaveOccurred()) 151 152 Expect(hdr.Name).To(MatchRegexp("(./)?task.yml$")) 153 154 uploadedBits <- struct{}{} 155 }, 156 ghttp.RespondWith(201, `{"id":125}`), 157 ), 158 ) 159 atcServer.RouteToHandler("POST", "/api/v1/teams/main/builds", 160 ghttp.CombineHandlers( 161 ghttp.VerifyRequest("POST", "/api/v1/teams/main/builds"), 162 VerifyPlan(expectedPlan), 163 func(w http.ResponseWriter, r *http.Request) { 164 http.SetCookie(w, &http.Cookie{ 165 Name: "Some-Cookie", 166 Value: "some-cookie-data", 167 Path: "/", 168 Expires: time.Now().Add(1 * time.Minute), 169 }) 170 }, 171 ghttp.RespondWith(201, `{"id":128}`), 172 ), 173 ) 174 atcServer.RouteToHandler("GET", "/api/v1/builds/128/events", 175 ghttp.CombineHandlers( 176 ghttp.VerifyRequest("GET", "/api/v1/builds/128/events"), 177 func(w http.ResponseWriter, r *http.Request) { 178 flusher := w.(http.Flusher) 179 180 w.Header().Add("Content-Type", "text/event-stream; charset=utf-8") 181 w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate") 182 w.Header().Add("Connection", "keep-alive") 183 184 w.WriteHeader(http.StatusOK) 185 186 flusher.Flush() 187 188 close(streaming) 189 190 id := 0 191 192 for e := range events { 193 payload, err := json.Marshal(event.Message{Event: e}) 194 Expect(err).NotTo(HaveOccurred()) 195 196 event := sse.Event{ 197 ID: fmt.Sprintf("%d", id), 198 Name: "event", 199 Data: payload, 200 } 201 202 err = event.Write(w) 203 Expect(err).NotTo(HaveOccurred()) 204 205 flusher.Flush() 206 207 id++ 208 } 209 210 err := sse.Event{ 211 Name: "end", 212 }.Write(w) 213 Expect(err).NotTo(HaveOccurred()) 214 }, 215 ), 216 ) 217 atcServer.RouteToHandler("GET", "/api/v1/builds/128/artifacts", 218 ghttp.RespondWithJSONEncoded(200, []atc.WorkerArtifact{workerArtifact}), 219 ) 220 221 }) 222 223 It("creates a build, streams output, uploads the bits, and polls until completion", func() { 224 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 225 flyCmd.Dir = buildDir 226 227 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 228 Expect(err).NotTo(HaveOccurred()) 229 230 Eventually(streaming).Should(BeClosed()) 231 232 buildURL, _ := url.Parse(atcServer.URL()) 233 buildURL.Path = path.Join(buildURL.Path, "builds/128") 234 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 235 236 events <- event.Log{Payload: "sup"} 237 238 Eventually(sess.Out).Should(gbytes.Say("sup")) 239 240 close(events) 241 242 <-sess.Exited 243 Expect(sess.ExitCode()).To(Equal(0)) 244 245 Expect(uploadedBits).To(HaveLen(1)) 246 }) 247 248 Context("when there is a pipeline job with the same input", func() { 249 BeforeEach(func() { 250 taskPlan.Task.VersionedResourceTypes = atc.VersionedResourceTypes{ 251 atc.VersionedResourceType{ 252 ResourceType: atc.ResourceType{ 253 Name: "resource-type", 254 Type: "s3", 255 Source: atc.Source{}, 256 }, 257 }, 258 } 259 260 planFactory := atc.NewPlanFactory(0) 261 262 expectedPlan = planFactory.NewPlan(atc.DoPlan{ 263 planFactory.NewPlan(atc.AggregatePlan{ 264 planFactory.NewPlan(atc.GetPlan{ 265 Name: "fixture", 266 VersionedResourceTypes: atc.VersionedResourceTypes{ 267 atc.VersionedResourceType{ 268 ResourceType: atc.ResourceType{ 269 Name: "resource-type", 270 Type: "s3", 271 Source: atc.Source{}, 272 }, 273 }, 274 }, 275 }), 276 }), 277 taskPlan, 278 }) 279 280 expectedQueryParams := "instance_vars=%7B%22branch%22%3A%22master%22%7D" 281 atcServer.RouteToHandler("POST", "/api/v1/teams/main/pipelines/some-pipeline/builds", 282 ghttp.CombineHandlers( 283 ghttp.VerifyRequest("POST", "/api/v1/teams/main/pipelines/some-pipeline/builds", expectedQueryParams), 284 testhelpers.VerifyPlan(expectedPlan), 285 func(w http.ResponseWriter, r *http.Request) { 286 http.SetCookie(w, &http.Cookie{ 287 Name: "Some-Cookie", 288 Value: "some-cookie-data", 289 Path: "/", 290 Expires: time.Now().Add(1 * time.Minute), 291 }) 292 }, 293 ghttp.RespondWith(201, `{"id":128}`), 294 ), 295 ) 296 atcServer.RouteToHandler("GET", "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job/inputs", 297 ghttp.CombineHandlers( 298 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job/inputs", expectedQueryParams), 299 ghttp.RespondWithJSONEncoded(200, []atc.BuildInput{atc.BuildInput{Name: "fixture"}}), 300 ), 301 ) 302 atcServer.RouteToHandler("GET", "/api/v1/teams/main/pipelines/some-pipeline/resource-types", 303 ghttp.CombineHandlers( 304 ghttp.VerifyRequest("GET", "/api/v1/teams/main/pipelines/some-pipeline/resource-types", expectedQueryParams), 305 ghttp.RespondWithJSONEncoded(200, atc.VersionedResourceTypes{ 306 atc.VersionedResourceType{ 307 ResourceType: atc.ResourceType{ 308 Name: "resource-type", 309 Type: "s3", 310 Source: atc.Source{}, 311 }, 312 }, 313 }), 314 ), 315 ) 316 }) 317 318 It("creates a build, streams output, and polls until completion", func() { 319 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-j", "some-pipeline/branch:master/some-job") 320 flyCmd.Dir = buildDir 321 322 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 323 Expect(err).NotTo(HaveOccurred()) 324 325 Eventually(streaming).Should(BeClosed()) 326 327 buildURL, _ := url.Parse(atcServer.URL()) 328 buildURL.Path = path.Join(buildURL.Path, "builds/128") 329 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 330 331 events <- event.Log{Payload: "sup"} 332 333 Eventually(sess.Out).Should(gbytes.Say("sup")) 334 335 close(events) 336 337 <-sess.Exited 338 Expect(sess.ExitCode()).To(Equal(0)) 339 }) 340 341 }) 342 343 Context("when the build config is invalid", func() { 344 BeforeEach(func() { 345 // missing platform and run path 346 err := ioutil.WriteFile( 347 filepath.Join(buildDir, "task.yml"), 348 []byte(`--- 349 run: {} 350 `), 351 0644, 352 ) 353 Expect(err).NotTo(HaveOccurred()) 354 }) 355 356 It("prints the failure and exits 1", func() { 357 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 358 flyCmd.Dir = buildDir 359 360 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 361 Expect(err).NotTo(HaveOccurred()) 362 363 Eventually(sess.Err).Should(gbytes.Say("missing")) 364 365 <-sess.Exited 366 Expect(sess.ExitCode()).To(Equal(1)) 367 }) 368 }) 369 370 Context("when the build config is valid", func() { 371 JustBeforeEach(func() { 372 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 373 ghttp.CombineHandlers( 374 func(w http.ResponseWriter, req *http.Request) { 375 uploadedBits <- struct{}{} 376 }, 377 ghttp.RespondWith(201, `{"id":125}`), 378 ), 379 ) 380 }) 381 382 Context("when task defines one input but it was not passed in as a flag", func() { 383 It("uploads the current directory", func() { 384 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 385 flyCmd.Dir = buildDir 386 387 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 388 Expect(err).NotTo(HaveOccurred()) 389 390 buildURL, _ := url.Parse(atcServer.URL()) 391 buildURL.Path = path.Join(buildURL.Path, "builds/128") 392 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 393 394 close(events) 395 396 <-sess.Exited 397 Expect(sess.ExitCode()).To(Equal(0)) 398 399 Expect(uploadedBits).To(HaveLen(1)) 400 }) 401 }) 402 403 Context("when task defines 2 inputs but only 1 was passed as a flag", func() { 404 var bardir string 405 406 BeforeEach(func() { 407 err := ioutil.WriteFile( 408 filepath.Join(buildDir, "task.yml"), 409 []byte(`--- 410 platform: some-platform 411 412 image_resource: 413 type: registry-image 414 source: 415 repository: ubuntu 416 417 inputs: 418 - name: fixture 419 - name: bar 420 421 run: 422 path: find 423 args: [.] 424 `), 425 0644, 426 ) 427 bardir = filepath.Join(tmpdir, "bar") 428 err = os.Mkdir(bardir, 0755) 429 Expect(err).ToNot(HaveOccurred()) 430 431 taskPlan.Task.Config.Inputs = []atc.TaskInputConfig{ 432 {Name: "fixture"}, 433 {Name: "bar"}, 434 } 435 taskPlan.Task.Config.Params = nil 436 437 Expect(err).NotTo(HaveOccurred()) 438 expectedPlan = planFactory.NewPlan(atc.DoPlan{ 439 planFactory.NewPlan(atc.AggregatePlan{ 440 planFactory.NewPlan(atc.ArtifactInputPlan{ 441 ArtifactID: 125, 442 Name: filepath.Base(buildDir), 443 }), 444 planFactory.NewPlan(atc.ArtifactInputPlan{ 445 ArtifactID: 125, 446 Name: filepath.Base(bardir), 447 }), 448 }), 449 taskPlan, 450 }) 451 452 }) 453 454 AfterEach(func() { 455 os.RemoveAll(bardir) 456 }) 457 458 Context("when the current directory name is the same as the missing input", func() { 459 It("uploads the current directory", func() { 460 flyCmd := exec.Command(flyPath, "-t", targetName, "e", 461 "-c", taskConfigPath, 462 "-i", fmt.Sprintf("bar=%s", bardir), 463 ) 464 flyCmd.Dir = buildDir 465 466 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 467 Expect(err).NotTo(HaveOccurred()) 468 469 buildURL, _ := url.Parse(atcServer.URL()) 470 buildURL.Path = path.Join(buildURL.Path, "builds/128") 471 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 472 473 close(events) 474 475 <-sess.Exited 476 Expect(sess.ExitCode()).To(Equal(0)) 477 478 Expect(uploadedBits).To(HaveLen(2)) 479 }) 480 }) 481 482 Context("when the current directory name is not the same as the missing input", func() { 483 BeforeEach(func() { 484 err := ioutil.WriteFile( 485 filepath.Join(buildDir, "task.yml"), 486 []byte(`--- 487 platform: some-platform 488 489 image_resource: 490 type: registry-image 491 source: 492 repository: ubuntu 493 494 inputs: 495 - name: foo 496 - name: bar 497 498 params: 499 FOO: bar 500 BAZ: buzz 501 X: 1 502 EMPTY: 503 504 run: 505 path: find 506 args: [.] 507 `), 508 0644, 509 ) 510 Expect(err).NotTo(HaveOccurred()) 511 (*expectedPlan.Do)[1].Task.Config.Inputs = []atc.TaskInputConfig{ 512 {Name: "foo"}, 513 {Name: "bar"}, 514 } 515 }) 516 517 It("errors with the missing input", func() { 518 flyCmd := exec.Command(flyPath, "-t", targetName, "e", 519 "-c", taskConfigPath, 520 "-i", fmt.Sprintf("bar=%s", bardir), 521 ) 522 flyCmd.Dir = buildDir 523 524 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 525 Expect(err).NotTo(HaveOccurred()) 526 527 Eventually(sess.Err).Should(gbytes.Say("error: missing required input `foo`")) 528 529 close(events) 530 531 <-sess.Exited 532 Expect(sess.ExitCode()).To(Equal(1)) 533 534 Expect(uploadedBits).To(HaveLen(1)) 535 }) 536 }) 537 }) 538 }) 539 540 Context("when arguments include input that is not a git repo", func() { 541 542 Context("when arguments not include --include-ignored", func() { 543 It("uploading with everything", func() { 544 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture="+buildDir) 545 546 flyCmd.Dir = buildDir 547 548 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 549 Expect(err).NotTo(HaveOccurred()) 550 551 // sync with after create 552 Eventually(streaming).Should(BeClosed()) 553 554 close(events) 555 556 <-sess.Exited 557 Expect(sess.ExitCode()).To(Equal(0)) 558 559 Expect(uploadedBits).To(HaveLen(1)) 560 }) 561 }) 562 }) 563 564 Context("when arguments include input that is a git repo", func() { 565 566 BeforeEach(func() { 567 gitIgnorePath := filepath.Join(buildDir, ".gitignore") 568 569 err := ioutil.WriteFile(gitIgnorePath, []byte(`*.test`), 0644) 570 Expect(err).NotTo(HaveOccurred()) 571 572 fileToBeIgnoredPath := filepath.Join(buildDir, "dev.test") 573 err = ioutil.WriteFile(fileToBeIgnoredPath, []byte(`test file content`), 0644) 574 Expect(err).NotTo(HaveOccurred()) 575 576 err = os.Mkdir(filepath.Join(buildDir, ".git"), 0755) 577 Expect(err).NotTo(HaveOccurred()) 578 579 err = os.Mkdir(filepath.Join(buildDir, ".git/refs"), 0755) 580 Expect(err).NotTo(HaveOccurred()) 581 582 err = os.Mkdir(filepath.Join(buildDir, ".git/objects"), 0755) 583 Expect(err).NotTo(HaveOccurred()) 584 585 gitHEADPath := filepath.Join(buildDir, ".git/HEAD") 586 err = ioutil.WriteFile(gitHEADPath, []byte(`ref: refs/heads/master`), 0644) 587 Expect(err).NotTo(HaveOccurred()) 588 }) 589 590 Context("when arguments not include --include-ignored", func() { 591 It("by default apply .gitignore", func() { 592 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 593 ghttp.CombineHandlers( 594 func(w http.ResponseWriter, req *http.Request) { 595 gr, err := gzip.NewReader(req.Body) 596 Expect(err).NotTo(HaveOccurred()) 597 598 tr := tar.NewReader(gr) 599 600 var matchFound = false 601 for { 602 hdr, err := tr.Next() 603 if err != nil { 604 break 605 } 606 if strings.Contains(hdr.Name, "dev.test") { 607 matchFound = true 608 break 609 } 610 } 611 612 Expect(matchFound).To(Equal(false)) 613 614 uploadedBits <- struct{}{} 615 }, 616 ghttp.RespondWith(201, `{"id":125}`), 617 ), 618 ) 619 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 620 flyCmd.Dir = buildDir 621 622 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 623 Expect(err).NotTo(HaveOccurred()) 624 625 // sync with after create 626 Eventually(streaming).Should(BeClosed()) 627 628 close(events) 629 630 <-sess.Exited 631 Expect(sess.ExitCode()).To(Equal(0)) 632 633 Expect(uploadedBits).To(HaveLen(1)) 634 }) 635 }) 636 637 Context("when arguments include --include-ignored", func() { 638 It("uploading with everything", func() { 639 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 640 ghttp.CombineHandlers( 641 func(w http.ResponseWriter, req *http.Request) { 642 Expect(req.FormValue("platform")).To(Equal("some-platform")) 643 644 gr, err := gzip.NewReader(req.Body) 645 Expect(err).NotTo(HaveOccurred()) 646 647 tr := tar.NewReader(gr) 648 649 var matchFound = false 650 for { 651 hdr, err := tr.Next() 652 if err != nil { 653 break 654 } 655 if strings.Contains(hdr.Name, "dev.test") { 656 matchFound = true 657 break 658 } 659 } 660 661 Expect(matchFound).To(Equal(true)) 662 uploadedBits <- struct{}{} 663 }, 664 ghttp.RespondWith(201, `{"id":125}`), 665 ), 666 ) 667 668 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--include-ignored") 669 flyCmd.Dir = buildDir 670 671 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 672 Expect(err).NotTo(HaveOccurred()) 673 674 // sync with after create 675 Eventually(streaming).Should(BeClosed()) 676 677 close(events) 678 679 <-sess.Exited 680 Expect(sess.ExitCode()).To(Equal(0)) 681 682 Expect(uploadedBits).To(HaveLen(1)) 683 }) 684 }) 685 }) 686 687 Context("when arguments are passed through", func() { 688 BeforeEach(func() { 689 (*expectedPlan.Do)[1].Task.Config.Run.Args = []string{".", "-name", `foo "bar" baz`} 690 }) 691 692 It("inserts them into the config template", func() { 693 atcServer.AllowUnhandledRequests = true 694 695 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--", "-name", "foo \"bar\" baz") 696 flyCmd.Dir = buildDir 697 698 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 699 Expect(err).NotTo(HaveOccurred()) 700 701 // sync with after create 702 Eventually(streaming).Should(BeClosed()) 703 704 close(events) 705 706 <-sess.Exited 707 Expect(sess.ExitCode()).To(Equal(0)) 708 709 Expect(uploadedBits).To(HaveLen(1)) 710 }) 711 }) 712 713 Context("when tags are specified", func() { 714 BeforeEach(func() { 715 (*expectedPlan.Do)[1].Task.Tags = []string{"tag-1", "tag-2"} 716 }) 717 718 It("sprinkles them on the task", func() { 719 atcServer.AllowUnhandledRequests = true 720 721 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--tag", "tag-1", "--tag", "tag-2") 722 flyCmd.Dir = buildDir 723 724 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 725 Expect(err).NotTo(HaveOccurred()) 726 727 // sync with after create 728 Eventually(streaming).Should(BeClosed()) 729 730 close(events) 731 732 <-sess.Exited 733 Expect(sess.ExitCode()).To(Equal(0)) 734 735 Expect(uploadedBits).To(HaveLen(1)) 736 }) 737 }) 738 739 Context("when invalid inputs are passed", func() { 740 It("prints an error", func() { 741 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.", "-i", "evan=.") 742 flyCmd.Dir = buildDir 743 744 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 745 Expect(err).NotTo(HaveOccurred()) 746 747 Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`")) 748 749 <-sess.Exited 750 Expect(sess.ExitCode()).To(Equal(1)) 751 }) 752 753 Context("when input is not a folder", func() { 754 It("prints an error", func() { 755 testFile := filepath.Join(buildDir, "test-file.txt") 756 err := ioutil.WriteFile( 757 testFile, 758 []byte(`test file content`), 759 0644, 760 ) 761 Expect(err).NotTo(HaveOccurred()) 762 763 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=./test-file.txt") 764 flyCmd.Dir = buildDir 765 766 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 767 Expect(err).NotTo(HaveOccurred()) 768 769 Eventually(sess.Err).Should(gbytes.Say("./test-file.txt not a folder")) 770 771 <-sess.Exited 772 Expect(sess.ExitCode()).To(Equal(1)) 773 }) 774 }) 775 776 Context("when invalid inputs are passed and the single valid input is correctly omitted", func() { 777 It("prints an error about invalid inputs instead of missing inputs", func() { 778 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "evan=.") 779 flyCmd.Dir = buildDir 780 781 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 782 Expect(err).NotTo(HaveOccurred()) 783 784 Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`")) 785 786 <-sess.Exited 787 Expect(sess.ExitCode()).To(Equal(1)) 788 }) 789 }) 790 }) 791 792 Context("when the task specifies no input", func() { 793 BeforeEach(func() { 794 err := ioutil.WriteFile( 795 filepath.Join(buildDir, "task.yml"), 796 []byte(`--- 797 platform: some-platform 798 799 image_resource: 800 type: registry-image 801 source: 802 repository: ubuntu 803 804 inputs: 805 806 params: 807 FOO: bar 808 BAZ: buzz 809 X: 1 810 EMPTY: 811 812 813 run: 814 path: find 815 args: [.] 816 `), 817 0644, 818 ) 819 Expect(err).NotTo(HaveOccurred()) 820 (*expectedPlan.Do)[1].Task.Config.Inputs = nil 821 (*expectedPlan.Do)[0].Aggregate = &atc.AggregatePlan{} 822 }) 823 824 It("shouldn't upload the current directory", func() { 825 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 826 ghttp.CombineHandlers( 827 func(w http.ResponseWriter, req *http.Request) { 828 uploadedBits <- struct{}{} 829 }, 830 ghttp.RespondWith(201, `{"id":125}`), 831 ), 832 ) 833 834 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 835 flyCmd.Dir = buildDir 836 837 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 838 Expect(err).NotTo(HaveOccurred()) 839 840 close(events) 841 842 <-sess.Exited 843 Expect(sess.ExitCode()).To(Equal(0)) 844 Expect(uploadedBits).To(HaveLen(0)) 845 }) 846 }) 847 848 Context("when the task specifies an optional input", func() { 849 BeforeEach(func() { 850 err := ioutil.WriteFile( 851 filepath.Join(buildDir, "task.yml"), 852 []byte(`--- 853 platform: some-platform 854 855 image_resource: 856 type: registry-image 857 source: 858 repository: ubuntu 859 860 inputs: 861 - name: fixture 862 - name: some-optional-input 863 optional: true 864 865 params: 866 FOO: bar 867 BAZ: buzz 868 X: 1 869 EMPTY: 870 871 run: 872 path: find 873 args: [.] 874 `), 875 0644, 876 ) 877 Expect(err).NotTo(HaveOccurred()) 878 (*expectedPlan.Do)[1].Task.Config.Inputs = []atc.TaskInputConfig{ 879 {Name: "fixture"}, 880 {Name: "some-optional-input", Optional: true}, 881 } 882 }) 883 884 Context("when the required input is specified but the optional input is omitted", func() { 885 It("runs successfully", func() { 886 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.") 887 flyCmd.Dir = buildDir 888 889 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 890 Expect(err).NotTo(HaveOccurred()) 891 892 Eventually(streaming).Should(BeClosed()) 893 894 buildURL, _ := url.Parse(atcServer.URL()) 895 buildURL.Path = path.Join(buildURL.Path, "builds/128") 896 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 897 898 events <- event.Log{Payload: "sup"} 899 900 Eventually(sess.Out).Should(gbytes.Say("sup")) 901 902 close(events) 903 904 <-sess.Exited 905 Expect(sess.ExitCode()).To(Equal(0)) 906 907 Expect(uploadedBits).To(HaveLen(1)) 908 }) 909 }) 910 911 Context("when the required input is not specified on the command line", func() { 912 It("runs infers the required input successfully", func() { 913 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 914 flyCmd.Dir = buildDir 915 916 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 917 Expect(err).NotTo(HaveOccurred()) 918 919 Eventually(streaming).Should(BeClosed()) 920 921 buildURL, _ := url.Parse(atcServer.URL()) 922 buildURL.Path = path.Join(buildURL.Path, "builds/128") 923 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 924 925 events <- event.Log{Payload: "sup"} 926 927 Eventually(sess.Out).Should(gbytes.Say("sup")) 928 929 close(events) 930 931 <-sess.Exited 932 Expect(sess.ExitCode()).To(Equal(0)) 933 934 Expect(uploadedBits).To(HaveLen(1)) 935 }) 936 }) 937 }) 938 939 Context("when the task specifies more than one required input", func() { 940 BeforeEach(func() { 941 err := ioutil.WriteFile( 942 filepath.Join(buildDir, "task.yml"), 943 []byte(`--- 944 platform: some-platform 945 946 image_resource: 947 type: registry-image 948 source: 949 repository: ubuntu 950 951 inputs: 952 - name: fixture 953 - name: something 954 955 params: 956 FOO: bar 957 BAZ: buzz 958 X: 1 959 EMPTY: 960 961 run: 962 path: find 963 args: [.] 964 `), 965 0644, 966 ) 967 Expect(err).NotTo(HaveOccurred()) 968 }) 969 970 Context("When some required inputs are not passed", func() { 971 It("Prints an error", func() { 972 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.") 973 flyCmd.Dir = buildDir 974 975 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 976 Expect(err).NotTo(HaveOccurred()) 977 978 Eventually(sess.Err).Should(gbytes.Say("missing required input `something`")) 979 980 <-sess.Exited 981 Expect(sess.ExitCode()).To(Equal(1)) 982 }) 983 984 }) 985 986 Context("When no inputs are passed", func() { 987 It("Prints an error", func() { 988 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 989 flyCmd.Dir = buildDir 990 991 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 992 Expect(err).NotTo(HaveOccurred()) 993 994 Eventually(sess.Err).Should(gbytes.Say("missing required input")) 995 996 <-sess.Exited 997 Expect(sess.ExitCode()).To(Equal(1)) 998 }) 999 }) 1000 }) 1001 1002 Context("when running with --privileged", func() { 1003 BeforeEach(func() { 1004 (*expectedPlan.Do)[1].Task.Privileged = true 1005 }) 1006 1007 It("inserts them into the config template", func() { 1008 atcServer.AllowUnhandledRequests = true 1009 1010 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--privileged") 1011 flyCmd.Dir = buildDir 1012 1013 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1014 Expect(err).NotTo(HaveOccurred()) 1015 1016 // sync with after create 1017 Eventually(streaming).Should(BeClosed()) 1018 1019 close(events) 1020 1021 <-sess.Exited 1022 Expect(sess.ExitCode()).To(Equal(0)) 1023 1024 Expect(uploadedBits).To(HaveLen(1)) 1025 }) 1026 }) 1027 1028 Context("when running with bogus flags", func() { 1029 It("exits 1", func() { 1030 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--bogus-flag") 1031 flyCmd.Dir = buildDir 1032 1033 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1034 Expect(err).NotTo(HaveOccurred()) 1035 1036 Eventually(sess.Err).Should(gbytes.Say("unknown flag `bogus-flag'")) 1037 1038 <-sess.Exited 1039 Expect(sess.ExitCode()).To(Equal(1)) 1040 }) 1041 }) 1042 1043 Context("when running with invalid -j flag", func() { 1044 It("exits 1", func() { 1045 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-j", "some-pipeline/invalid/some-job") 1046 flyCmd.Dir = buildDir 1047 1048 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1049 Expect(err).NotTo(HaveOccurred()) 1050 1051 Eventually(sess.Err).Should(gbytes.Say("argument format should be <pipeline>/<key:value>/<job>")) 1052 1053 <-sess.Exited 1054 Expect(sess.ExitCode()).To(Equal(1)) 1055 }) 1056 }) 1057 1058 Context("when parameters are specified in the environment", func() { 1059 BeforeEach(func() { 1060 (*expectedPlan.Do)[1].Task.Config.Params = map[string]string{ 1061 "FOO": "newbar", 1062 "BAZ": "buzz", 1063 "X": "", 1064 "EMPTY": "", 1065 } 1066 }) 1067 1068 It("overrides the builds parameter values", func() { 1069 atcServer.AllowUnhandledRequests = true 1070 1071 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1072 flyCmd.Dir = buildDir 1073 flyCmd.Env = append(os.Environ(), "FOO=newbar", "X=") 1074 1075 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1076 Expect(err).NotTo(HaveOccurred()) 1077 1078 // sync with after create 1079 Eventually(streaming).Should(BeClosed()) 1080 1081 close(events) 1082 1083 <-sess.Exited 1084 Expect(sess.ExitCode()).To(Equal(0)) 1085 1086 Expect(uploadedBits).To(HaveLen(1)) 1087 }) 1088 }) 1089 1090 Context("when the build is interrupted", func() { 1091 var aborted chan struct{} 1092 1093 JustBeforeEach(func() { 1094 aborted = make(chan struct{}) 1095 1096 atcServer.AppendHandlers( 1097 ghttp.CombineHandlers( 1098 ghttp.VerifyRequest("PUT", "/api/v1/builds/128/abort"), 1099 func(w http.ResponseWriter, r *http.Request) { 1100 close(aborted) 1101 }, 1102 ), 1103 ) 1104 }) 1105 1106 if runtime.GOOS != "windows" { 1107 Describe("with SIGINT", func() { 1108 It("aborts the build and exits nonzero", func() { 1109 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1110 flyCmd.Dir = buildDir 1111 1112 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1113 Expect(err).ToNot(HaveOccurred()) 1114 1115 Eventually(streaming).Should(BeClosed()) 1116 1117 Expect(uploadedBits).To(HaveLen(1)) 1118 1119 sess.Signal(os.Interrupt) 1120 1121 Eventually(aborted).Should(BeClosed()) 1122 1123 events <- event.Status{Status: atc.StatusErrored} 1124 close(events) 1125 1126 <-sess.Exited 1127 Expect(sess.ExitCode()).To(Equal(2)) 1128 }) 1129 }) 1130 1131 Describe("with SIGTERM", func() { 1132 It("aborts the build and exits nonzero", func() { 1133 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1134 flyCmd.Dir = buildDir 1135 1136 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1137 Expect(err).ToNot(HaveOccurred()) 1138 1139 Eventually(streaming).Should(BeClosed()) 1140 1141 Expect(uploadedBits).To(HaveLen(1)) 1142 1143 sess.Signal(syscall.SIGTERM) 1144 1145 Eventually(aborted).Should(BeClosed()) 1146 1147 events <- event.Status{Status: atc.StatusErrored} 1148 close(events) 1149 1150 <-sess.Exited 1151 Expect(sess.ExitCode()).To(Equal(2)) 1152 }) 1153 }) 1154 } 1155 }) 1156 1157 Context("when the build succeeds", func() { 1158 It("exits 0", func() { 1159 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1160 flyCmd.Dir = buildDir 1161 1162 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1163 Expect(err).ToNot(HaveOccurred()) 1164 1165 Eventually(streaming).Should(BeClosed()) 1166 1167 events <- event.Status{Status: atc.StatusSucceeded} 1168 close(events) 1169 1170 <-sess.Exited 1171 Expect(sess.ExitCode()).To(Equal(0)) 1172 1173 Expect(uploadedBits).To(HaveLen(1)) 1174 }) 1175 }) 1176 1177 Context("when the build fails", func() { 1178 It("exits 1", func() { 1179 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1180 flyCmd.Dir = buildDir 1181 1182 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1183 Expect(err).ToNot(HaveOccurred()) 1184 1185 Eventually(streaming).Should(BeClosed()) 1186 1187 events <- event.Status{Status: atc.StatusFailed} 1188 close(events) 1189 1190 <-sess.Exited 1191 Expect(sess.ExitCode()).To(Equal(1)) 1192 1193 Expect(uploadedBits).To(HaveLen(1)) 1194 }) 1195 }) 1196 1197 Context("when the build errors", func() { 1198 It("exits 2", func() { 1199 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1200 flyCmd.Dir = buildDir 1201 1202 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1203 Expect(err).ToNot(HaveOccurred()) 1204 1205 Eventually(streaming).Should(BeClosed()) 1206 1207 events <- event.Status{Status: atc.StatusErrored} 1208 close(events) 1209 1210 <-sess.Exited 1211 Expect(sess.ExitCode()).To(Equal(2)) 1212 1213 Expect(uploadedBits).To(HaveLen(1)) 1214 }) 1215 }) 1216 })