1package integration_test 2 3import ( 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/concourse/concourse/atc" 21 "github.com/concourse/concourse/atc/event" 22 "github.com/concourse/concourse/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 31var _ = 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(`--- 63platform: some-platform 64 65image_resource: 66 type: registry-image 67 source: 68 repository: ubuntu 69 70inputs: 71- name: fixture 72 73params: 74 FOO: bar 75 BAZ: buzz 76 X: 1 77 EMPTY: 78 79run: 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 atcServer.RouteToHandler("POST", "/api/v1/teams/main/pipelines/some-pipeline/builds", 281 ghttp.CombineHandlers( 282 ghttp.VerifyRequest("POST", "/api/v1/teams/main/pipelines/some-pipeline/builds"), 283 testhelpers.VerifyPlan(expectedPlan), 284 func(w http.ResponseWriter, r *http.Request) { 285 http.SetCookie(w, &http.Cookie{ 286 Name: "Some-Cookie", 287 Value: "some-cookie-data", 288 Path: "/", 289 Expires: time.Now().Add(1 * time.Minute), 290 }) 291 }, 292 ghttp.RespondWith(201, `{"id":128}`), 293 ), 294 ) 295 atcServer.RouteToHandler("GET", "/api/v1/teams/main/pipelines/some-pipeline/jobs/some-job/inputs", 296 ghttp.RespondWithJSONEncoded(200, []atc.BuildInput{atc.BuildInput{Name: "fixture"}}), 297 ) 298 atcServer.RouteToHandler("GET", "/api/v1/teams/main/pipelines/some-pipeline/resource-types", 299 ghttp.RespondWithJSONEncoded(200, atc.VersionedResourceTypes{ 300 atc.VersionedResourceType{ 301 ResourceType: atc.ResourceType{ 302 Name: "resource-type", 303 Type: "s3", 304 Source: atc.Source{}, 305 }, 306 }, 307 }), 308 ) 309 }) 310 311 It("creates a build, streams output, and polls until completion", func() { 312 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-j", "some-pipeline/some-job") 313 flyCmd.Dir = buildDir 314 315 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 316 Expect(err).NotTo(HaveOccurred()) 317 318 Eventually(streaming).Should(BeClosed()) 319 320 buildURL, _ := url.Parse(atcServer.URL()) 321 buildURL.Path = path.Join(buildURL.Path, "builds/128") 322 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 323 324 events <- event.Log{Payload: "sup"} 325 326 Eventually(sess.Out).Should(gbytes.Say("sup")) 327 328 close(events) 329 330 <-sess.Exited 331 Expect(sess.ExitCode()).To(Equal(0)) 332 }) 333 334 }) 335 336 Context("when the build config is invalid", func() { 337 BeforeEach(func() { 338 // missing platform and run path 339 err := ioutil.WriteFile( 340 filepath.Join(buildDir, "task.yml"), 341 []byte(`--- 342run: {} 343`), 344 0644, 345 ) 346 Expect(err).NotTo(HaveOccurred()) 347 }) 348 349 It("prints the failure and exits 1", func() { 350 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 351 flyCmd.Dir = buildDir 352 353 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 354 Expect(err).NotTo(HaveOccurred()) 355 356 Eventually(sess.Err).Should(gbytes.Say("missing")) 357 358 <-sess.Exited 359 Expect(sess.ExitCode()).To(Equal(1)) 360 }) 361 }) 362 363 Context("when the build config is valid", func() { 364 JustBeforeEach(func() { 365 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 366 ghttp.CombineHandlers( 367 func(w http.ResponseWriter, req *http.Request) { 368 uploadedBits <- struct{}{} 369 }, 370 ghttp.RespondWith(201, `{"id":125}`), 371 ), 372 ) 373 }) 374 375 Context("when task defines one input but it was not passed in as a flag", func() { 376 It("uploads the current directory", func() { 377 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 378 flyCmd.Dir = buildDir 379 380 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 381 Expect(err).NotTo(HaveOccurred()) 382 383 buildURL, _ := url.Parse(atcServer.URL()) 384 buildURL.Path = path.Join(buildURL.Path, "builds/128") 385 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 386 387 close(events) 388 389 <-sess.Exited 390 Expect(sess.ExitCode()).To(Equal(0)) 391 392 Expect(uploadedBits).To(HaveLen(1)) 393 }) 394 }) 395 396 Context("when task defines 2 inputs but only 1 was passed as a flag", func() { 397 var bardir string 398 399 BeforeEach(func() { 400 err := ioutil.WriteFile( 401 filepath.Join(buildDir, "task.yml"), 402 []byte(`--- 403platform: some-platform 404 405image_resource: 406 type: registry-image 407 source: 408 repository: ubuntu 409 410inputs: 411- name: fixture 412- name: bar 413 414run: 415 path: find 416 args: [.] 417`), 418 0644, 419 ) 420 bardir = filepath.Join(tmpdir, "bar") 421 err = os.Mkdir(bardir, 0755) 422 Expect(err).ToNot(HaveOccurred()) 423 424 taskPlan.Task.Config.Inputs = []atc.TaskInputConfig{ 425 {Name: "fixture"}, 426 {Name: "bar"}, 427 } 428 taskPlan.Task.Config.Params = nil 429 430 Expect(err).NotTo(HaveOccurred()) 431 expectedPlan = planFactory.NewPlan(atc.DoPlan{ 432 planFactory.NewPlan(atc.AggregatePlan{ 433 planFactory.NewPlan(atc.ArtifactInputPlan{ 434 ArtifactID: 125, 435 Name: filepath.Base(buildDir), 436 }), 437 planFactory.NewPlan(atc.ArtifactInputPlan{ 438 ArtifactID: 125, 439 Name: filepath.Base(bardir), 440 }), 441 }), 442 taskPlan, 443 }) 444 445 }) 446 447 AfterEach(func() { 448 os.RemoveAll(bardir) 449 }) 450 451 Context("when the current directory name is the same as the missing input", func() { 452 It("uploads the current directory", func() { 453 flyCmd := exec.Command(flyPath, "-t", targetName, "e", 454 "-c", taskConfigPath, 455 "-i", fmt.Sprintf("bar=%s", bardir), 456 ) 457 flyCmd.Dir = buildDir 458 459 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 460 Expect(err).NotTo(HaveOccurred()) 461 462 buildURL, _ := url.Parse(atcServer.URL()) 463 buildURL.Path = path.Join(buildURL.Path, "builds/128") 464 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 465 466 close(events) 467 468 <-sess.Exited 469 Expect(sess.ExitCode()).To(Equal(0)) 470 471 Expect(uploadedBits).To(HaveLen(2)) 472 }) 473 }) 474 475 Context("when the current directory name is not the same as the missing input", func() { 476 BeforeEach(func() { 477 err := ioutil.WriteFile( 478 filepath.Join(buildDir, "task.yml"), 479 []byte(`--- 480platform: some-platform 481 482image_resource: 483 type: registry-image 484 source: 485 repository: ubuntu 486 487inputs: 488- name: foo 489- name: bar 490 491params: 492 FOO: bar 493 BAZ: buzz 494 X: 1 495 EMPTY: 496 497run: 498 path: find 499 args: [.] 500`), 501 0644, 502 ) 503 Expect(err).NotTo(HaveOccurred()) 504 (*expectedPlan.Do)[1].Task.Config.Inputs = []atc.TaskInputConfig{ 505 {Name: "foo"}, 506 {Name: "bar"}, 507 } 508 }) 509 510 It("errors with the missing input", func() { 511 flyCmd := exec.Command(flyPath, "-t", targetName, "e", 512 "-c", taskConfigPath, 513 "-i", fmt.Sprintf("bar=%s", bardir), 514 ) 515 flyCmd.Dir = buildDir 516 517 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 518 Expect(err).NotTo(HaveOccurred()) 519 520 Eventually(sess.Err).Should(gbytes.Say("error: missing required input `foo`")) 521 522 close(events) 523 524 <-sess.Exited 525 Expect(sess.ExitCode()).To(Equal(1)) 526 527 Expect(uploadedBits).To(HaveLen(1)) 528 }) 529 }) 530 }) 531 }) 532 533 Context("when arguments include input that is not a git repo", func() { 534 535 Context("when arguments not include --include-ignored", func() { 536 It("uploading with everything", func() { 537 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture="+buildDir) 538 539 flyCmd.Dir = buildDir 540 541 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 542 Expect(err).NotTo(HaveOccurred()) 543 544 // sync with after create 545 Eventually(streaming).Should(BeClosed()) 546 547 close(events) 548 549 <-sess.Exited 550 Expect(sess.ExitCode()).To(Equal(0)) 551 552 Expect(uploadedBits).To(HaveLen(1)) 553 }) 554 }) 555 }) 556 557 Context("when arguments include input that is a git repo", func() { 558 559 BeforeEach(func() { 560 gitIgnorePath := filepath.Join(buildDir, ".gitignore") 561 562 err := ioutil.WriteFile(gitIgnorePath, []byte(`*.test`), 0644) 563 Expect(err).NotTo(HaveOccurred()) 564 565 fileToBeIgnoredPath := filepath.Join(buildDir, "dev.test") 566 err = ioutil.WriteFile(fileToBeIgnoredPath, []byte(`test file content`), 0644) 567 Expect(err).NotTo(HaveOccurred()) 568 569 err = os.Mkdir(filepath.Join(buildDir, ".git"), 0755) 570 Expect(err).NotTo(HaveOccurred()) 571 572 err = os.Mkdir(filepath.Join(buildDir, ".git/refs"), 0755) 573 Expect(err).NotTo(HaveOccurred()) 574 575 err = os.Mkdir(filepath.Join(buildDir, ".git/objects"), 0755) 576 Expect(err).NotTo(HaveOccurred()) 577 578 gitHEADPath := filepath.Join(buildDir, ".git/HEAD") 579 err = ioutil.WriteFile(gitHEADPath, []byte(`ref: refs/heads/master`), 0644) 580 Expect(err).NotTo(HaveOccurred()) 581 }) 582 583 Context("when arguments not include --include-ignored", func() { 584 It("by default apply .gitignore", func() { 585 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 586 ghttp.CombineHandlers( 587 func(w http.ResponseWriter, req *http.Request) { 588 gr, err := gzip.NewReader(req.Body) 589 Expect(err).NotTo(HaveOccurred()) 590 591 tr := tar.NewReader(gr) 592 593 var matchFound = false 594 for { 595 hdr, err := tr.Next() 596 if err != nil { 597 break 598 } 599 if strings.Contains(hdr.Name, "dev.test") { 600 matchFound = true 601 break 602 } 603 } 604 605 Expect(matchFound).To(Equal(false)) 606 607 uploadedBits <- struct{}{} 608 }, 609 ghttp.RespondWith(201, `{"id":125}`), 610 ), 611 ) 612 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 613 flyCmd.Dir = buildDir 614 615 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 616 Expect(err).NotTo(HaveOccurred()) 617 618 // sync with after create 619 Eventually(streaming).Should(BeClosed()) 620 621 close(events) 622 623 <-sess.Exited 624 Expect(sess.ExitCode()).To(Equal(0)) 625 626 Expect(uploadedBits).To(HaveLen(1)) 627 }) 628 }) 629 630 Context("when arguments include --include-ignored", func() { 631 It("uploading with everything", func() { 632 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 633 ghttp.CombineHandlers( 634 func(w http.ResponseWriter, req *http.Request) { 635 Expect(req.FormValue("platform")).To(Equal("some-platform")) 636 637 gr, err := gzip.NewReader(req.Body) 638 Expect(err).NotTo(HaveOccurred()) 639 640 tr := tar.NewReader(gr) 641 642 var matchFound = false 643 for { 644 hdr, err := tr.Next() 645 if err != nil { 646 break 647 } 648 if strings.Contains(hdr.Name, "dev.test") { 649 matchFound = true 650 break 651 } 652 } 653 654 Expect(matchFound).To(Equal(true)) 655 uploadedBits <- struct{}{} 656 }, 657 ghttp.RespondWith(201, `{"id":125}`), 658 ), 659 ) 660 661 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--include-ignored") 662 flyCmd.Dir = buildDir 663 664 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 665 Expect(err).NotTo(HaveOccurred()) 666 667 // sync with after create 668 Eventually(streaming).Should(BeClosed()) 669 670 close(events) 671 672 <-sess.Exited 673 Expect(sess.ExitCode()).To(Equal(0)) 674 675 Expect(uploadedBits).To(HaveLen(1)) 676 }) 677 }) 678 }) 679 680 Context("when arguments are passed through", func() { 681 BeforeEach(func() { 682 (*expectedPlan.Do)[1].Task.Config.Run.Args = []string{".", "-name", `foo "bar" baz`} 683 }) 684 685 It("inserts them into the config template", func() { 686 atcServer.AllowUnhandledRequests = true 687 688 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--", "-name", "foo \"bar\" baz") 689 flyCmd.Dir = buildDir 690 691 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 692 Expect(err).NotTo(HaveOccurred()) 693 694 // sync with after create 695 Eventually(streaming).Should(BeClosed()) 696 697 close(events) 698 699 <-sess.Exited 700 Expect(sess.ExitCode()).To(Equal(0)) 701 702 Expect(uploadedBits).To(HaveLen(1)) 703 }) 704 }) 705 706 Context("when tags are specified", func() { 707 BeforeEach(func() { 708 (*expectedPlan.Do)[1].Task.Tags = []string{"tag-1", "tag-2"} 709 }) 710 711 It("sprinkles them on the task", func() { 712 atcServer.AllowUnhandledRequests = true 713 714 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--tag", "tag-1", "--tag", "tag-2") 715 flyCmd.Dir = buildDir 716 717 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 718 Expect(err).NotTo(HaveOccurred()) 719 720 // sync with after create 721 Eventually(streaming).Should(BeClosed()) 722 723 close(events) 724 725 <-sess.Exited 726 Expect(sess.ExitCode()).To(Equal(0)) 727 728 Expect(uploadedBits).To(HaveLen(1)) 729 }) 730 }) 731 732 Context("when invalid inputs are passed", func() { 733 It("prints an error", func() { 734 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.", "-i", "evan=.") 735 flyCmd.Dir = buildDir 736 737 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 738 Expect(err).NotTo(HaveOccurred()) 739 740 Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`")) 741 742 <-sess.Exited 743 Expect(sess.ExitCode()).To(Equal(1)) 744 }) 745 746 Context("when input is not a folder", func() { 747 It("prints an error", func() { 748 testFile := filepath.Join(buildDir, "test-file.txt") 749 err := ioutil.WriteFile( 750 testFile, 751 []byte(`test file content`), 752 0644, 753 ) 754 Expect(err).NotTo(HaveOccurred()) 755 756 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=./test-file.txt") 757 flyCmd.Dir = buildDir 758 759 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 760 Expect(err).NotTo(HaveOccurred()) 761 762 Eventually(sess.Err).Should(gbytes.Say("./test-file.txt not a folder")) 763 764 <-sess.Exited 765 Expect(sess.ExitCode()).To(Equal(1)) 766 }) 767 }) 768 769 Context("when invalid inputs are passed and the single valid input is correctly omitted", func() { 770 It("prints an error about invalid inputs instead of missing inputs", func() { 771 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "evan=.") 772 flyCmd.Dir = buildDir 773 774 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 775 Expect(err).NotTo(HaveOccurred()) 776 777 Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`")) 778 779 <-sess.Exited 780 Expect(sess.ExitCode()).To(Equal(1)) 781 }) 782 }) 783 }) 784 785 Context("when the task specifies no input", func() { 786 BeforeEach(func() { 787 err := ioutil.WriteFile( 788 filepath.Join(buildDir, "task.yml"), 789 []byte(`--- 790platform: some-platform 791 792image_resource: 793 type: registry-image 794 source: 795 repository: ubuntu 796 797inputs: 798 799params: 800 FOO: bar 801 BAZ: buzz 802 X: 1 803 EMPTY: 804 805 806run: 807 path: find 808 args: [.] 809`), 810 0644, 811 ) 812 Expect(err).NotTo(HaveOccurred()) 813 (*expectedPlan.Do)[1].Task.Config.Inputs = nil 814 (*expectedPlan.Do)[0].Aggregate = &atc.AggregatePlan{} 815 }) 816 817 It("shouldn't upload the current directory", func() { 818 atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts", 819 ghttp.CombineHandlers( 820 func(w http.ResponseWriter, req *http.Request) { 821 uploadedBits <- struct{}{} 822 }, 823 ghttp.RespondWith(201, `{"id":125}`), 824 ), 825 ) 826 827 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 828 flyCmd.Dir = buildDir 829 830 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 831 Expect(err).NotTo(HaveOccurred()) 832 833 close(events) 834 835 <-sess.Exited 836 Expect(sess.ExitCode()).To(Equal(0)) 837 Expect(uploadedBits).To(HaveLen(0)) 838 }) 839 }) 840 841 Context("when the task specifies an optional input", func() { 842 BeforeEach(func() { 843 err := ioutil.WriteFile( 844 filepath.Join(buildDir, "task.yml"), 845 []byte(`--- 846platform: some-platform 847 848image_resource: 849 type: registry-image 850 source: 851 repository: ubuntu 852 853inputs: 854- name: fixture 855- name: some-optional-input 856 optional: true 857 858params: 859 FOO: bar 860 BAZ: buzz 861 X: 1 862 EMPTY: 863 864run: 865 path: find 866 args: [.] 867`), 868 0644, 869 ) 870 Expect(err).NotTo(HaveOccurred()) 871 (*expectedPlan.Do)[1].Task.Config.Inputs = []atc.TaskInputConfig{ 872 {Name: "fixture"}, 873 {Name: "some-optional-input", Optional: true}, 874 } 875 }) 876 877 Context("when the required input is specified but the optional input is omitted", func() { 878 It("runs successfully", func() { 879 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.") 880 flyCmd.Dir = buildDir 881 882 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 883 Expect(err).NotTo(HaveOccurred()) 884 885 Eventually(streaming).Should(BeClosed()) 886 887 buildURL, _ := url.Parse(atcServer.URL()) 888 buildURL.Path = path.Join(buildURL.Path, "builds/128") 889 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 890 891 events <- event.Log{Payload: "sup"} 892 893 Eventually(sess.Out).Should(gbytes.Say("sup")) 894 895 close(events) 896 897 <-sess.Exited 898 Expect(sess.ExitCode()).To(Equal(0)) 899 900 Expect(uploadedBits).To(HaveLen(1)) 901 }) 902 }) 903 904 Context("when the required input is not specified on the command line", func() { 905 It("runs infers the required input successfully", func() { 906 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 907 flyCmd.Dir = buildDir 908 909 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 910 Expect(err).NotTo(HaveOccurred()) 911 912 Eventually(streaming).Should(BeClosed()) 913 914 buildURL, _ := url.Parse(atcServer.URL()) 915 buildURL.Path = path.Join(buildURL.Path, "builds/128") 916 Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String())) 917 918 events <- event.Log{Payload: "sup"} 919 920 Eventually(sess.Out).Should(gbytes.Say("sup")) 921 922 close(events) 923 924 <-sess.Exited 925 Expect(sess.ExitCode()).To(Equal(0)) 926 927 Expect(uploadedBits).To(HaveLen(1)) 928 }) 929 }) 930 }) 931 932 Context("when the task specifies more than one required input", func() { 933 BeforeEach(func() { 934 err := ioutil.WriteFile( 935 filepath.Join(buildDir, "task.yml"), 936 []byte(`--- 937platform: some-platform 938 939image_resource: 940 type: registry-image 941 source: 942 repository: ubuntu 943 944inputs: 945- name: fixture 946- name: something 947 948params: 949 FOO: bar 950 BAZ: buzz 951 X: 1 952 EMPTY: 953 954run: 955 path: find 956 args: [.] 957`), 958 0644, 959 ) 960 Expect(err).NotTo(HaveOccurred()) 961 }) 962 963 Context("When some required inputs are not passed", func() { 964 It("Prints an error", func() { 965 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.") 966 flyCmd.Dir = buildDir 967 968 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 969 Expect(err).NotTo(HaveOccurred()) 970 971 Eventually(sess.Err).Should(gbytes.Say("missing required input `something`")) 972 973 <-sess.Exited 974 Expect(sess.ExitCode()).To(Equal(1)) 975 }) 976 977 }) 978 979 Context("When no inputs are passed", func() { 980 It("Prints an error", func() { 981 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 982 flyCmd.Dir = buildDir 983 984 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 985 Expect(err).NotTo(HaveOccurred()) 986 987 Eventually(sess.Err).Should(gbytes.Say("missing required input")) 988 989 <-sess.Exited 990 Expect(sess.ExitCode()).To(Equal(1)) 991 }) 992 }) 993 }) 994 995 Context("when running with --privileged", func() { 996 BeforeEach(func() { 997 (*expectedPlan.Do)[1].Task.Privileged = true 998 }) 999 1000 It("inserts them into the config template", func() { 1001 atcServer.AllowUnhandledRequests = true 1002 1003 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--privileged") 1004 flyCmd.Dir = buildDir 1005 1006 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1007 Expect(err).NotTo(HaveOccurred()) 1008 1009 // sync with after create 1010 Eventually(streaming).Should(BeClosed()) 1011 1012 close(events) 1013 1014 <-sess.Exited 1015 Expect(sess.ExitCode()).To(Equal(0)) 1016 1017 Expect(uploadedBits).To(HaveLen(1)) 1018 }) 1019 }) 1020 1021 Context("when running with bogus flags", func() { 1022 It("exits 1", func() { 1023 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--bogus-flag") 1024 flyCmd.Dir = buildDir 1025 1026 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1027 Expect(err).NotTo(HaveOccurred()) 1028 1029 Eventually(sess.Err).Should(gbytes.Say("unknown flag `bogus-flag'")) 1030 1031 <-sess.Exited 1032 Expect(sess.ExitCode()).To(Equal(1)) 1033 }) 1034 }) 1035 1036 Context("when running with invalid -j flag", func() { 1037 It("exits 1", func() { 1038 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-j", "some-pipeline/invalid/some-job") 1039 flyCmd.Dir = buildDir 1040 1041 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1042 Expect(err).NotTo(HaveOccurred()) 1043 1044 Eventually(sess.Err).Should(gbytes.Say("argument format should be <pipeline>/<job>")) 1045 1046 <-sess.Exited 1047 Expect(sess.ExitCode()).To(Equal(1)) 1048 }) 1049 }) 1050 1051 Context("when parameters are specified in the environment", func() { 1052 BeforeEach(func() { 1053 (*expectedPlan.Do)[1].Task.Config.Params = map[string]string{ 1054 "FOO": "newbar", 1055 "BAZ": "buzz", 1056 "X": "", 1057 "EMPTY": "", 1058 } 1059 }) 1060 1061 It("overrides the builds parameter values", func() { 1062 atcServer.AllowUnhandledRequests = true 1063 1064 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1065 flyCmd.Dir = buildDir 1066 flyCmd.Env = append(os.Environ(), "FOO=newbar", "X=") 1067 1068 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1069 Expect(err).NotTo(HaveOccurred()) 1070 1071 // sync with after create 1072 Eventually(streaming).Should(BeClosed()) 1073 1074 close(events) 1075 1076 <-sess.Exited 1077 Expect(sess.ExitCode()).To(Equal(0)) 1078 1079 Expect(uploadedBits).To(HaveLen(1)) 1080 }) 1081 }) 1082 1083 Context("when the build is interrupted", func() { 1084 var aborted chan struct{} 1085 1086 JustBeforeEach(func() { 1087 aborted = make(chan struct{}) 1088 1089 atcServer.AppendHandlers( 1090 ghttp.CombineHandlers( 1091 ghttp.VerifyRequest("PUT", "/api/v1/builds/128/abort"), 1092 func(w http.ResponseWriter, r *http.Request) { 1093 close(aborted) 1094 }, 1095 ), 1096 ) 1097 }) 1098 1099 if runtime.GOOS != "windows" { 1100 Describe("with SIGINT", func() { 1101 It("aborts the build and exits nonzero", func() { 1102 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1103 flyCmd.Dir = buildDir 1104 1105 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1106 Expect(err).ToNot(HaveOccurred()) 1107 1108 Eventually(streaming).Should(BeClosed()) 1109 1110 Expect(uploadedBits).To(HaveLen(1)) 1111 1112 sess.Signal(os.Interrupt) 1113 1114 Eventually(aborted).Should(BeClosed()) 1115 1116 events <- event.Status{Status: atc.StatusErrored} 1117 close(events) 1118 1119 <-sess.Exited 1120 Expect(sess.ExitCode()).To(Equal(2)) 1121 }) 1122 }) 1123 1124 Describe("with SIGTERM", func() { 1125 It("aborts the build and exits nonzero", func() { 1126 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1127 flyCmd.Dir = buildDir 1128 1129 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1130 Expect(err).ToNot(HaveOccurred()) 1131 1132 Eventually(streaming).Should(BeClosed()) 1133 1134 Expect(uploadedBits).To(HaveLen(1)) 1135 1136 sess.Signal(syscall.SIGTERM) 1137 1138 Eventually(aborted).Should(BeClosed()) 1139 1140 events <- event.Status{Status: atc.StatusErrored} 1141 close(events) 1142 1143 <-sess.Exited 1144 Expect(sess.ExitCode()).To(Equal(2)) 1145 }) 1146 }) 1147 } 1148 }) 1149 1150 Context("when the build succeeds", func() { 1151 It("exits 0", func() { 1152 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1153 flyCmd.Dir = buildDir 1154 1155 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1156 Expect(err).ToNot(HaveOccurred()) 1157 1158 Eventually(streaming).Should(BeClosed()) 1159 1160 events <- event.Status{Status: atc.StatusSucceeded} 1161 close(events) 1162 1163 <-sess.Exited 1164 Expect(sess.ExitCode()).To(Equal(0)) 1165 1166 Expect(uploadedBits).To(HaveLen(1)) 1167 }) 1168 }) 1169 1170 Context("when the build fails", func() { 1171 It("exits 1", func() { 1172 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1173 flyCmd.Dir = buildDir 1174 1175 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1176 Expect(err).ToNot(HaveOccurred()) 1177 1178 Eventually(streaming).Should(BeClosed()) 1179 1180 events <- event.Status{Status: atc.StatusFailed} 1181 close(events) 1182 1183 <-sess.Exited 1184 Expect(sess.ExitCode()).To(Equal(1)) 1185 1186 Expect(uploadedBits).To(HaveLen(1)) 1187 }) 1188 }) 1189 1190 Context("when the build errors", func() { 1191 It("exits 2", func() { 1192 flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath) 1193 flyCmd.Dir = buildDir 1194 1195 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1196 Expect(err).ToNot(HaveOccurred()) 1197 1198 Eventually(streaming).Should(BeClosed()) 1199 1200 events <- event.Status{Status: atc.StatusErrored} 1201 close(events) 1202 1203 <-sess.Exited 1204 Expect(sess.ExitCode()).To(Equal(2)) 1205 1206 Expect(uploadedBits).To(HaveLen(1)) 1207 }) 1208 }) 1209}) 1210