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  })