github.com/chenbh/concourse/v6@v6.4.2/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/chenbh/concourse/v6/atc"
    21  	"github.com/chenbh/concourse/v6/atc/event"
    22  	"github.com/chenbh/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 uploadingBits <-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  
    47  	BeforeEach(func() {
    48  		var err error
    49  		tmpdir, err = ioutil.TempDir("", "fly-build-dir")
    50  		Expect(err).NotTo(HaveOccurred())
    51  
    52  		buildDir = filepath.Join(tmpdir, "fixture")
    53  
    54  		err = os.Mkdir(buildDir, 0755)
    55  		Expect(err).NotTo(HaveOccurred())
    56  
    57  		taskConfigPath = filepath.Join(buildDir, "task.yml")
    58  
    59  		err = ioutil.WriteFile(
    60  			taskConfigPath,
    61  			[]byte(`---
    62  platform: some-platform
    63  
    64  image_resource:
    65    type: registry-image
    66    source:
    67      repository: ubuntu
    68  
    69  inputs:
    70  - name: fixture
    71  
    72  params:
    73    FOO: bar
    74    BAZ: buzz
    75    X: 1
    76    EMPTY:
    77  
    78  run:
    79    path: find
    80    args: [.]
    81  `),
    82  			0644,
    83  		)
    84  		Expect(err).NotTo(HaveOccurred())
    85  
    86  		streaming = make(chan struct{})
    87  		events = make(chan atc.Event)
    88  
    89  		planFactory := atc.NewPlanFactory(0)
    90  
    91  		taskPlan = planFactory.NewPlan(atc.TaskPlan{
    92  			Name: "one-off",
    93  			Config: &atc.TaskConfig{
    94  				Platform: "some-platform",
    95  				ImageResource: &atc.ImageResource{
    96  					Type: "registry-image",
    97  					Source: atc.Source{
    98  						"repository": "ubuntu",
    99  					},
   100  				},
   101  				Inputs: []atc.TaskInputConfig{
   102  					{Name: "fixture"},
   103  				},
   104  				Params: map[string]string{
   105  					"FOO":   "bar",
   106  					"BAZ":   "buzz",
   107  					"X":     "1",
   108  					"EMPTY": "",
   109  				},
   110  				Run: atc.TaskRunConfig{
   111  					Path: "find",
   112  					Args: []string{"."},
   113  				},
   114  			},
   115  		})
   116  
   117  		expectedPlan = planFactory.NewPlan(atc.DoPlan{
   118  			planFactory.NewPlan(atc.AggregatePlan{
   119  				planFactory.NewPlan(atc.ArtifactInputPlan{
   120  					ArtifactID: 125,
   121  					Name:       filepath.Base(buildDir),
   122  				}),
   123  			}),
   124  			taskPlan,
   125  		})
   126  	})
   127  
   128  	AfterEach(func() {
   129  		os.RemoveAll(tmpdir)
   130  	})
   131  
   132  	JustBeforeEach(func() {
   133  		uploading := make(chan struct{})
   134  		uploadingBits = uploading
   135  
   136  		atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts",
   137  			ghttp.CombineHandlers(
   138  				func(w http.ResponseWriter, req *http.Request) {
   139  					close(uploading)
   140  
   141  					gr, err := gzip.NewReader(req.Body)
   142  					Expect(err).NotTo(HaveOccurred())
   143  
   144  					tr := tar.NewReader(gr)
   145  
   146  					hdr, err := tr.Next()
   147  					Expect(err).NotTo(HaveOccurred())
   148  
   149  					Expect(hdr.Name).To(Equal("./"))
   150  
   151  					hdr, err = tr.Next()
   152  					Expect(err).NotTo(HaveOccurred())
   153  
   154  					Expect(hdr.Name).To(MatchRegexp("(./)?task.yml$"))
   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(uploadingBits).To(BeClosed())
   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(`---
   342  run: {}
   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  		Context("when arguments include input that is not a git repo", func() {
   365  
   366  			Context("when arguments not include --include-ignored", func() {
   367  				It("uploading with everything", func() {
   368  					flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture="+buildDir)
   369  
   370  					flyCmd.Dir = buildDir
   371  
   372  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   373  					Expect(err).NotTo(HaveOccurred())
   374  
   375  					// sync with after create
   376  					Eventually(streaming).Should(BeClosed())
   377  
   378  					close(events)
   379  
   380  					<-sess.Exited
   381  					Expect(sess.ExitCode()).To(Equal(0))
   382  
   383  					Expect(uploadingBits).To(BeClosed())
   384  				})
   385  			})
   386  		})
   387  
   388  		Context("when arguments include input that is a git repo", func() {
   389  
   390  			BeforeEach(func() {
   391  				gitIgnorePath := filepath.Join(buildDir, ".gitignore")
   392  
   393  				err := ioutil.WriteFile(gitIgnorePath, []byte(`*.test`), 0644)
   394  				Expect(err).NotTo(HaveOccurred())
   395  
   396  				fileToBeIgnoredPath := filepath.Join(buildDir, "dev.test")
   397  				err = ioutil.WriteFile(fileToBeIgnoredPath, []byte(`test file content`), 0644)
   398  				Expect(err).NotTo(HaveOccurred())
   399  
   400  				err = os.Mkdir(filepath.Join(buildDir, ".git"), 0755)
   401  				Expect(err).NotTo(HaveOccurred())
   402  
   403  				err = os.Mkdir(filepath.Join(buildDir, ".git/refs"), 0755)
   404  				Expect(err).NotTo(HaveOccurred())
   405  
   406  				err = os.Mkdir(filepath.Join(buildDir, ".git/objects"), 0755)
   407  				Expect(err).NotTo(HaveOccurred())
   408  
   409  				gitHEADPath := filepath.Join(buildDir, ".git/HEAD")
   410  				err = ioutil.WriteFile(gitHEADPath, []byte(`ref: refs/heads/master`), 0644)
   411  				Expect(err).NotTo(HaveOccurred())
   412  			})
   413  
   414  			Context("when arguments not include --include-ignored", func() {
   415  				It("by default apply .gitignore", func() {
   416  					uploading := make(chan struct{})
   417  					uploadingBits = uploading
   418  					atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts",
   419  						ghttp.CombineHandlers(
   420  							func(w http.ResponseWriter, req *http.Request) {
   421  								close(uploading)
   422  
   423  								gr, err := gzip.NewReader(req.Body)
   424  								Expect(err).NotTo(HaveOccurred())
   425  
   426  								tr := tar.NewReader(gr)
   427  
   428  								var matchFound = false
   429  								for {
   430  									hdr, err := tr.Next()
   431  									if err != nil {
   432  										break
   433  									}
   434  									if strings.Contains(hdr.Name, "dev.test") {
   435  										matchFound = true
   436  										break
   437  									}
   438  								}
   439  
   440  								Expect(matchFound).To(Equal(false))
   441  							},
   442  							ghttp.RespondWith(201, `{"id":125}`),
   443  						),
   444  					)
   445  
   446  					flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   447  					flyCmd.Dir = buildDir
   448  
   449  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   450  					Expect(err).NotTo(HaveOccurred())
   451  
   452  					// sync with after create
   453  					Eventually(streaming).Should(BeClosed())
   454  
   455  					close(events)
   456  
   457  					<-sess.Exited
   458  					Expect(sess.ExitCode()).To(Equal(0))
   459  
   460  					Expect(uploadingBits).To(BeClosed())
   461  				})
   462  			})
   463  
   464  			Context("when arguments include --include-ignored", func() {
   465  				It("uploading with everything", func() {
   466  					uploading := make(chan struct{})
   467  					uploadingBits = uploading
   468  					atcServer.RouteToHandler("POST", "/api/v1/teams/main/artifacts",
   469  						ghttp.CombineHandlers(
   470  							func(w http.ResponseWriter, req *http.Request) {
   471  								close(uploading)
   472  
   473  								Expect(req.FormValue("platform")).To(Equal("some-platform"))
   474  
   475  								gr, err := gzip.NewReader(req.Body)
   476  								Expect(err).NotTo(HaveOccurred())
   477  
   478  								tr := tar.NewReader(gr)
   479  
   480  								var matchFound = false
   481  								for {
   482  									hdr, err := tr.Next()
   483  									if err != nil {
   484  										break
   485  									}
   486  									if strings.Contains(hdr.Name, "dev.test") {
   487  										matchFound = true
   488  										break
   489  									}
   490  								}
   491  
   492  								Expect(matchFound).To(Equal(true))
   493  							},
   494  							ghttp.RespondWith(201, `{"id":125}`),
   495  						),
   496  					)
   497  					flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--include-ignored")
   498  					flyCmd.Dir = buildDir
   499  
   500  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   501  					Expect(err).NotTo(HaveOccurred())
   502  
   503  					// sync with after create
   504  					Eventually(streaming).Should(BeClosed())
   505  
   506  					close(events)
   507  
   508  					<-sess.Exited
   509  					Expect(sess.ExitCode()).To(Equal(0))
   510  
   511  					Expect(uploadingBits).To(BeClosed())
   512  				})
   513  			})
   514  		})
   515  	})
   516  
   517  	Context("when arguments are passed through", func() {
   518  		BeforeEach(func() {
   519  			(*expectedPlan.Do)[1].Task.Config.Run.Args = []string{".", "-name", `foo "bar" baz`}
   520  		})
   521  
   522  		It("inserts them into the config template", func() {
   523  			atcServer.AllowUnhandledRequests = true
   524  
   525  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--", "-name", "foo \"bar\" baz")
   526  			flyCmd.Dir = buildDir
   527  
   528  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   529  			Expect(err).NotTo(HaveOccurred())
   530  
   531  			// sync with after create
   532  			Eventually(streaming).Should(BeClosed())
   533  
   534  			close(events)
   535  
   536  			<-sess.Exited
   537  			Expect(sess.ExitCode()).To(Equal(0))
   538  
   539  			Expect(uploadingBits).To(BeClosed())
   540  		})
   541  	})
   542  
   543  	Context("when tags are specified", func() {
   544  		BeforeEach(func() {
   545  			(*expectedPlan.Do)[1].Task.Tags = []string{"tag-1", "tag-2"}
   546  		})
   547  
   548  		It("sprinkles them on the task", func() {
   549  			atcServer.AllowUnhandledRequests = true
   550  
   551  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--tag", "tag-1", "--tag", "tag-2")
   552  			flyCmd.Dir = buildDir
   553  
   554  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   555  			Expect(err).NotTo(HaveOccurred())
   556  
   557  			// sync with after create
   558  			Eventually(streaming).Should(BeClosed())
   559  
   560  			close(events)
   561  
   562  			<-sess.Exited
   563  			Expect(sess.ExitCode()).To(Equal(0))
   564  
   565  			Expect(uploadingBits).To(BeClosed())
   566  		})
   567  	})
   568  
   569  	Context("when invalid inputs are passed", func() {
   570  		It("prints an error", func() {
   571  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.", "-i", "evan=.")
   572  			flyCmd.Dir = buildDir
   573  
   574  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   575  			Expect(err).NotTo(HaveOccurred())
   576  
   577  			Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`"))
   578  
   579  			<-sess.Exited
   580  			Expect(sess.ExitCode()).To(Equal(1))
   581  		})
   582  
   583  		Context("when input is not a folder", func() {
   584  			It("prints an error", func() {
   585  				testFile := filepath.Join(buildDir, "test-file.txt")
   586  				err := ioutil.WriteFile(
   587  					testFile,
   588  					[]byte(`test file content`),
   589  					0644,
   590  				)
   591  				Expect(err).NotTo(HaveOccurred())
   592  
   593  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=./test-file.txt")
   594  				flyCmd.Dir = buildDir
   595  
   596  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   597  				Expect(err).NotTo(HaveOccurred())
   598  
   599  				Eventually(sess.Err).Should(gbytes.Say("./test-file.txt not a folder"))
   600  
   601  				<-sess.Exited
   602  				Expect(sess.ExitCode()).To(Equal(1))
   603  			})
   604  		})
   605  
   606  		Context("when invalid inputs are passed and the single valid input is correctly omitted", func() {
   607  			It("prints an error about invalid inputs instead of missing inputs", func() {
   608  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "evan=.")
   609  				flyCmd.Dir = buildDir
   610  
   611  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   612  				Expect(err).NotTo(HaveOccurred())
   613  
   614  				Eventually(sess.Err).Should(gbytes.Say("unknown input `evan`"))
   615  
   616  				<-sess.Exited
   617  				Expect(sess.ExitCode()).To(Equal(1))
   618  			})
   619  		})
   620  	})
   621  
   622  	Context("when the task specifies an optional input", func() {
   623  		BeforeEach(func() {
   624  			err := ioutil.WriteFile(
   625  				filepath.Join(buildDir, "task.yml"),
   626  				[]byte(`---
   627  platform: some-platform
   628  
   629  image_resource:
   630    type: registry-image
   631    source:
   632      repository: ubuntu
   633  
   634  inputs:
   635  - name: fixture
   636  - name: some-optional-input
   637    optional: true
   638  
   639  params:
   640    FOO: bar
   641    BAZ: buzz
   642    X: 1
   643    EMPTY:
   644  
   645  run:
   646    path: find
   647    args: [.]
   648  `),
   649  				0644,
   650  			)
   651  			Expect(err).NotTo(HaveOccurred())
   652  			(*expectedPlan.Do)[1].Task.Config.Inputs = []atc.TaskInputConfig{
   653  				{Name: "fixture"},
   654  				{Name: "some-optional-input", Optional: true},
   655  			}
   656  		})
   657  
   658  		Context("when the required input is specified but the optional input is omitted", func() {
   659  			It("runs successfully", func() {
   660  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "fixture=.")
   661  				flyCmd.Dir = buildDir
   662  
   663  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   664  				Expect(err).NotTo(HaveOccurred())
   665  
   666  				Eventually(streaming).Should(BeClosed())
   667  
   668  				buildURL, _ := url.Parse(atcServer.URL())
   669  				buildURL.Path = path.Join(buildURL.Path, "builds/128")
   670  				Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String()))
   671  
   672  				events <- event.Log{Payload: "sup"}
   673  
   674  				Eventually(sess.Out).Should(gbytes.Say("sup"))
   675  
   676  				close(events)
   677  
   678  				<-sess.Exited
   679  				Expect(sess.ExitCode()).To(Equal(0))
   680  
   681  				Expect(uploadingBits).To(BeClosed())
   682  			})
   683  		})
   684  
   685  		Context("when the required input is not specified on the command line", func() {
   686  			It("runs infers the required input successfully", func() {
   687  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   688  				flyCmd.Dir = buildDir
   689  
   690  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   691  				Expect(err).NotTo(HaveOccurred())
   692  
   693  				Eventually(streaming).Should(BeClosed())
   694  
   695  				buildURL, _ := url.Parse(atcServer.URL())
   696  				buildURL.Path = path.Join(buildURL.Path, "builds/128")
   697  				Eventually(sess.Out).Should(gbytes.Say("executing build 128 at %s", buildURL.String()))
   698  
   699  				events <- event.Log{Payload: "sup"}
   700  
   701  				Eventually(sess.Out).Should(gbytes.Say("sup"))
   702  
   703  				close(events)
   704  
   705  				<-sess.Exited
   706  				Expect(sess.ExitCode()).To(Equal(0))
   707  
   708  				Expect(uploadingBits).To(BeClosed())
   709  			})
   710  		})
   711  	})
   712  
   713  	Context("when the task specifies more than one required input", func() {
   714  		BeforeEach(func() {
   715  			err := ioutil.WriteFile(
   716  				filepath.Join(buildDir, "task.yml"),
   717  				[]byte(`---
   718  platform: some-platform
   719  
   720  image_resource:
   721    type: registry-image
   722    source:
   723      repository: ubuntu
   724  
   725  inputs:
   726  - name: fixture
   727  - name: something
   728  
   729  params:
   730    FOO: bar
   731    BAZ: buzz
   732    X: 1
   733    EMPTY:
   734  
   735  run:
   736    path: find
   737    args: [.]
   738  `),
   739  				0644,
   740  			)
   741  			Expect(err).NotTo(HaveOccurred())
   742  		})
   743  
   744  		Context("When some required inputs are not passed", func() {
   745  			It("Prints an error", func() {
   746  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-i", "something=.")
   747  				flyCmd.Dir = buildDir
   748  
   749  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   750  				Expect(err).NotTo(HaveOccurred())
   751  
   752  				Eventually(sess.Err).Should(gbytes.Say("missing required input `fixture`"))
   753  
   754  				<-sess.Exited
   755  				Expect(sess.ExitCode()).To(Equal(1))
   756  			})
   757  
   758  		})
   759  
   760  		Context("When no inputs are passed", func() {
   761  			It("Prints an error", func() {
   762  				flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   763  				flyCmd.Dir = buildDir
   764  
   765  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   766  				Expect(err).NotTo(HaveOccurred())
   767  
   768  				Eventually(sess.Err).Should(gbytes.Say("missing required input"))
   769  
   770  				<-sess.Exited
   771  				Expect(sess.ExitCode()).To(Equal(1))
   772  			})
   773  		})
   774  	})
   775  
   776  	Context("when running with --privileged", func() {
   777  		BeforeEach(func() {
   778  			(*expectedPlan.Do)[1].Task.Privileged = true
   779  		})
   780  
   781  		It("inserts them into the config template", func() {
   782  			atcServer.AllowUnhandledRequests = true
   783  
   784  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--privileged")
   785  			flyCmd.Dir = buildDir
   786  
   787  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   788  			Expect(err).NotTo(HaveOccurred())
   789  
   790  			// sync with after create
   791  			Eventually(streaming).Should(BeClosed())
   792  
   793  			close(events)
   794  
   795  			<-sess.Exited
   796  			Expect(sess.ExitCode()).To(Equal(0))
   797  
   798  			Expect(uploadingBits).To(BeClosed())
   799  		})
   800  	})
   801  
   802  	Context("when running with bogus flags", func() {
   803  		It("exits 1", func() {
   804  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "--bogus-flag")
   805  			flyCmd.Dir = buildDir
   806  
   807  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   808  			Expect(err).NotTo(HaveOccurred())
   809  
   810  			Eventually(sess.Err).Should(gbytes.Say("unknown flag `bogus-flag'"))
   811  
   812  			<-sess.Exited
   813  			Expect(sess.ExitCode()).To(Equal(1))
   814  		})
   815  	})
   816  
   817  	Context("when running with invalid -j flag", func() {
   818  		It("exits 1", func() {
   819  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath, "-j", "some-pipeline/invalid/some-job")
   820  			flyCmd.Dir = buildDir
   821  
   822  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   823  			Expect(err).NotTo(HaveOccurred())
   824  
   825  			Eventually(sess.Err).Should(gbytes.Say("argument format should be <pipeline>/<job>"))
   826  
   827  			<-sess.Exited
   828  			Expect(sess.ExitCode()).To(Equal(1))
   829  		})
   830  	})
   831  
   832  	Context("when parameters are specified in the environment", func() {
   833  		BeforeEach(func() {
   834  			(*expectedPlan.Do)[1].Task.Config.Params = map[string]string{
   835  				"FOO":   "newbar",
   836  				"BAZ":   "buzz",
   837  				"X":     "",
   838  				"EMPTY": "",
   839  			}
   840  		})
   841  
   842  		It("overrides the builds parameter values", func() {
   843  			atcServer.AllowUnhandledRequests = true
   844  
   845  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   846  			flyCmd.Dir = buildDir
   847  			flyCmd.Env = append(os.Environ(), "FOO=newbar", "X=")
   848  
   849  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   850  			Expect(err).NotTo(HaveOccurred())
   851  
   852  			// sync with after create
   853  			Eventually(streaming).Should(BeClosed())
   854  
   855  			close(events)
   856  
   857  			<-sess.Exited
   858  			Expect(sess.ExitCode()).To(Equal(0))
   859  
   860  			Expect(uploadingBits).To(BeClosed())
   861  		})
   862  	})
   863  
   864  	Context("when the build is interrupted", func() {
   865  		var aborted chan struct{}
   866  
   867  		JustBeforeEach(func() {
   868  			aborted = make(chan struct{})
   869  
   870  			atcServer.AppendHandlers(
   871  				ghttp.CombineHandlers(
   872  					ghttp.VerifyRequest("PUT", "/api/v1/builds/128/abort"),
   873  					func(w http.ResponseWriter, r *http.Request) {
   874  						close(aborted)
   875  					},
   876  				),
   877  			)
   878  		})
   879  
   880  		if runtime.GOOS != "windows" {
   881  			Describe("with SIGINT", func() {
   882  				It("aborts the build and exits nonzero", func() {
   883  					flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   884  					flyCmd.Dir = buildDir
   885  
   886  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   887  					Expect(err).ToNot(HaveOccurred())
   888  
   889  					Eventually(streaming).Should(BeClosed())
   890  
   891  					Eventually(uploadingBits).Should(BeClosed())
   892  
   893  					sess.Signal(os.Interrupt)
   894  
   895  					Eventually(aborted).Should(BeClosed())
   896  
   897  					events <- event.Status{Status: atc.StatusErrored}
   898  					close(events)
   899  
   900  					<-sess.Exited
   901  					Expect(sess.ExitCode()).To(Equal(2))
   902  				})
   903  			})
   904  
   905  			Describe("with SIGTERM", func() {
   906  				It("aborts the build and exits nonzero", func() {
   907  					flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   908  					flyCmd.Dir = buildDir
   909  
   910  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   911  					Expect(err).ToNot(HaveOccurred())
   912  
   913  					Eventually(streaming).Should(BeClosed())
   914  
   915  					Eventually(uploadingBits).Should(BeClosed())
   916  
   917  					sess.Signal(syscall.SIGTERM)
   918  
   919  					Eventually(aborted).Should(BeClosed())
   920  
   921  					events <- event.Status{Status: atc.StatusErrored}
   922  					close(events)
   923  
   924  					<-sess.Exited
   925  					Expect(sess.ExitCode()).To(Equal(2))
   926  				})
   927  			})
   928  		}
   929  	})
   930  
   931  	Context("when the build succeeds", func() {
   932  		It("exits 0", func() {
   933  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   934  			flyCmd.Dir = buildDir
   935  
   936  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   937  			Expect(err).ToNot(HaveOccurred())
   938  
   939  			Eventually(streaming).Should(BeClosed())
   940  
   941  			events <- event.Status{Status: atc.StatusSucceeded}
   942  			close(events)
   943  
   944  			<-sess.Exited
   945  			Expect(sess.ExitCode()).To(Equal(0))
   946  
   947  			Expect(uploadingBits).To(BeClosed())
   948  		})
   949  	})
   950  
   951  	Context("when the build fails", func() {
   952  		It("exits 1", func() {
   953  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   954  			flyCmd.Dir = buildDir
   955  
   956  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   957  			Expect(err).ToNot(HaveOccurred())
   958  
   959  			Eventually(streaming).Should(BeClosed())
   960  
   961  			events <- event.Status{Status: atc.StatusFailed}
   962  			close(events)
   963  
   964  			<-sess.Exited
   965  			Expect(sess.ExitCode()).To(Equal(1))
   966  
   967  			Expect(uploadingBits).To(BeClosed())
   968  		})
   969  	})
   970  
   971  	Context("when the build errors", func() {
   972  		It("exits 2", func() {
   973  			flyCmd := exec.Command(flyPath, "-t", targetName, "e", "-c", taskConfigPath)
   974  			flyCmd.Dir = buildDir
   975  
   976  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   977  			Expect(err).ToNot(HaveOccurred())
   978  
   979  			Eventually(streaming).Should(BeClosed())
   980  
   981  			events <- event.Status{Status: atc.StatusErrored}
   982  			close(events)
   983  
   984  			<-sess.Exited
   985  			Expect(sess.ExitCode()).To(Equal(2))
   986  
   987  			Expect(uploadingBits).To(BeClosed())
   988  		})
   989  	})
   990  })