github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/integration/set_pipeline_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"os"
     9  	"os/exec"
    10  	"regexp"
    11  
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  
    15  	"code.cloudfoundry.org/urljoiner"
    16  	"github.com/mgutz/ansi"
    17  	"github.com/onsi/gomega/gbytes"
    18  	"github.com/onsi/gomega/gexec"
    19  	"github.com/onsi/gomega/ghttp"
    20  	"github.com/tedsuo/rata"
    21  	"sigs.k8s.io/yaml"
    22  
    23  	"github.com/pf-qiu/concourse/v6/atc"
    24  )
    25  
    26  var _ = Describe("Fly CLI", func() {
    27  	Describe("set-pipeline", func() {
    28  		var (
    29  			config atc.Config
    30  		)
    31  
    32  		yes := func(stdin io.Writer) {
    33  			fmt.Fprintf(stdin, "y\n")
    34  		}
    35  
    36  		no := func(stdin io.Writer) {
    37  			fmt.Fprintf(stdin, "n\n")
    38  		}
    39  
    40  		expectSaveConfig := func(config atc.Config) {
    41  			path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
    42  			Expect(err).NotTo(HaveOccurred())
    43  
    44  			atcServer.RouteToHandler("PUT", path,
    45  				ghttp.CombineHandlers(
    46  					ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
    47  					func(w http.ResponseWriter, r *http.Request) {
    48  						bodyConfig := getConfig(r)
    49  
    50  						receivedConfig := atc.Config{}
    51  						err = yaml.Unmarshal(bodyConfig, &receivedConfig)
    52  						Expect(err).NotTo(HaveOccurred())
    53  
    54  						Expect(receivedConfig).To(Equal(config))
    55  
    56  						w.WriteHeader(http.StatusOK)
    57  						w.Write([]byte(`{}`))
    58  					},
    59  				),
    60  			)
    61  
    62  			path_get, err := atc.Routes.CreatePathForRoute(atc.GetPipeline, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
    63  			Expect(err).NotTo(HaveOccurred())
    64  
    65  			atcServer.RouteToHandler("GET", path_get,
    66  				ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Pipeline{Name: "awesome-pipeline", Paused: false, TeamName: "main"}),
    67  			)
    68  		}
    69  
    70  		BeforeEach(func() {
    71  			config = atc.Config{
    72  				Groups: atc.GroupConfigs{
    73  					{
    74  						Name:      "some-group",
    75  						Jobs:      []string{"job-1", "job-2"},
    76  						Resources: []string{"resource-1", "resource-2"},
    77  					},
    78  					{
    79  						Name:      "some-other-group",
    80  						Jobs:      []string{"job-3", "job-4"},
    81  						Resources: []string{"resource-6", "resource-4"},
    82  					},
    83  				},
    84  
    85  				Resources: atc.ResourceConfigs{
    86  					{
    87  						Name: "some-resource",
    88  						Type: "some-type",
    89  						Source: atc.Source{
    90  							"source-config": "some-value",
    91  						},
    92  					},
    93  					{
    94  						Name: "some-other-resource",
    95  						Type: "some-other-type",
    96  						Source: atc.Source{
    97  							"source-config": "some-value",
    98  						},
    99  					},
   100  					{
   101  						Name: "some-resource-with-int-field",
   102  						Type: "some-type",
   103  						Source: atc.Source{
   104  							"source-config": 5,
   105  						},
   106  					},
   107  				},
   108  
   109  				ResourceTypes: atc.ResourceTypes{
   110  					{
   111  						Name: "some-resource-type",
   112  						Type: "some-type",
   113  						Source: atc.Source{
   114  							"source-config": "some-value",
   115  						},
   116  					},
   117  					{
   118  						Name: "some-other-resource-type",
   119  						Type: "some-other-type",
   120  						Source: atc.Source{
   121  							"source-config": "some-value",
   122  						},
   123  					},
   124  				},
   125  
   126  				Jobs: atc.JobConfigs{
   127  					{
   128  						Name:   "some-job",
   129  						Public: true,
   130  						Serial: true,
   131  					},
   132  					{
   133  						Name: "some-unchanged-job",
   134  					},
   135  					{
   136  						Name: "some-other-job",
   137  					},
   138  					{
   139  						Name: "pinned-resource-job",
   140  						PlanSequence: []atc.Step{
   141  							{
   142  								Config: &atc.GetStep{
   143  									Name: "some-resource",
   144  									Version: &atc.VersionConfig{
   145  										Pinned: atc.Version{
   146  											"ref": "some-ref",
   147  										},
   148  									},
   149  								},
   150  							},
   151  						},
   152  					},
   153  				},
   154  			}
   155  		})
   156  
   157  		Describe("templating", func() {
   158  			BeforeEach(func() {
   159  				config = atc.Config{
   160  					Groups: atc.GroupConfigs{},
   161  
   162  					Resources: atc.ResourceConfigs{
   163  						{
   164  							Name: "some-resource",
   165  							Type: "template-type",
   166  							Source: atc.Source{
   167  								"source-config": "some-value",
   168  							},
   169  						},
   170  						{
   171  							Name: "some-other-resource",
   172  							Type: "some-other-type",
   173  							Source: atc.Source{
   174  								"secret_key": "verysecret",
   175  							},
   176  						},
   177  					},
   178  
   179  					Jobs: atc.JobConfigs{},
   180  				}
   181  
   182  				path, err := atc.Routes.CreatePathForRoute(atc.GetConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   183  				Expect(err).NotTo(HaveOccurred())
   184  
   185  				atcServer.AppendHandlers(
   186  					ghttp.CombineHandlers(
   187  						ghttp.VerifyRequest("GET", path),
   188  						ghttp.RespondWithJSONEncoded(http.StatusOK, atc.ConfigResponse{Config: config}, http.Header{atc.ConfigVersionHeader: {"42"}}),
   189  					),
   190  				)
   191  			})
   192  
   193  			Context("when configuring container limits in task", func() {
   194  				It("succeeds", func() {
   195  					flyCmd := exec.Command(
   196  						flyPath, "-t", targetName,
   197  						"set-pipeline",
   198  						"--pipeline", "awesome-pipeline",
   199  						"-c", "fixtures/testConfigContainerLimits.yml",
   200  					)
   201  					stdin, err := flyCmd.StdinPipe()
   202  					Expect(err).NotTo(HaveOccurred())
   203  
   204  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   205  					Expect(err).NotTo(HaveOccurred())
   206  
   207  					Eventually(sess).Should(gbytes.Say(`cpu: 1024`))
   208  					Eventually(sess).Should(gbytes.Say(`memory: 2147483648`))
   209  					Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
   210  					no(stdin)
   211  
   212  					<-sess.Exited
   213  					Expect(sess.ExitCode()).To(Equal(0))
   214  				})
   215  			})
   216  
   217  			Context("when configuring with old-style templated value that fails", func() {
   218  				It("shows helpful error messages", func() {
   219  					flyCmd := exec.Command(
   220  						flyPath, "-t", targetName,
   221  						"set-pipeline",
   222  						"--pipeline", "awesome-pipeline",
   223  						"-c", "fixtures/testConfig.yml",
   224  					)
   225  
   226  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   227  					Expect(err).NotTo(HaveOccurred())
   228  
   229  					Eventually(sess.Err).Should(gbytes.Say(`2 errors occurred:`))
   230  					Eventually(sess.Err).Should(gbytes.Say(`\* unbound variable in template: 'resource-type'`))
   231  					Eventually(sess.Err).Should(gbytes.Say(`\* unbound variable in template: 'resource-key'`))
   232  
   233  					<-sess.Exited
   234  					Expect(sess.ExitCode()).NotTo(Equal(0))
   235  				})
   236  			})
   237  
   238  			Context("when configuring with old-style templated value succeeds", func() {
   239  				BeforeEach(func() {
   240  					expectSaveConfig(atc.Config{
   241  						Groups: atc.GroupConfigs{},
   242  
   243  						Resources: atc.ResourceConfigs{
   244  							{
   245  								Name: "some-resource",
   246  								Type: "template-type",
   247  								Source: atc.Source{
   248  									"source-config": "some-value",
   249  								},
   250  							},
   251  							{
   252  								Name: "some-other-resource",
   253  								Type: "some-other-type",
   254  								Source: atc.Source{
   255  									"secret_key": "overridden-secret",
   256  								},
   257  							},
   258  						},
   259  
   260  						Jobs: atc.JobConfigs{},
   261  					})
   262  				})
   263  
   264  				It("parses the config file and sends it to the ATC", func() {
   265  					Expect(func() {
   266  						flyCmd := exec.Command(
   267  							flyPath, "-t", targetName,
   268  							"set-pipeline",
   269  							"--pipeline", "awesome-pipeline",
   270  							"-c", "fixtures/testConfig.yml",
   271  							"--var", "resource-key=overridden-secret",
   272  							"--load-vars-from", "fixtures/vars.yml",
   273  						)
   274  
   275  						stdin, err := flyCmd.StdinPipe()
   276  						Expect(err).NotTo(HaveOccurred())
   277  
   278  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   279  						Expect(err).NotTo(HaveOccurred())
   280  
   281  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
   282  						yes(stdin)
   283  						Eventually(sess).Should(gbytes.Say("configuration updated"))
   284  
   285  						<-sess.Exited
   286  						Expect(sess.ExitCode()).To(Equal(0))
   287  					}).To(Change(func() int {
   288  						return len(atcServer.ReceivedRequests())
   289  					}).By(4))
   290  				})
   291  
   292  				Context("when a non-stringy var is specified with -v", func() {
   293  					BeforeEach(func() {
   294  						config = atc.Config{
   295  							Groups: atc.GroupConfigs{},
   296  
   297  							Resources: atc.ResourceConfigs{
   298  								{
   299  									Name: "some-resource",
   300  									Type: "template-type",
   301  									Source: atc.Source{
   302  										"source-config": "some-value",
   303  									},
   304  								},
   305  								{
   306  									Name: "some-other-resource",
   307  									Type: "some-other-type",
   308  									Source: atc.Source{
   309  										"secret_key": `{"complicated": "secret"}`,
   310  									},
   311  								},
   312  							},
   313  
   314  							Jobs: atc.JobConfigs{},
   315  						}
   316  
   317  						path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   318  						Expect(err).NotTo(HaveOccurred())
   319  
   320  						atcServer.RouteToHandler("PUT", path,
   321  							ghttp.CombineHandlers(
   322  								ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
   323  								func(w http.ResponseWriter, r *http.Request) {
   324  									bodyConfig := getConfig(r)
   325  
   326  									receivedConfig := atc.Config{}
   327  									err = yaml.Unmarshal(bodyConfig, &receivedConfig)
   328  									Expect(err).NotTo(HaveOccurred())
   329  
   330  									Expect(receivedConfig).To(Equal(config))
   331  
   332  									w.WriteHeader(http.StatusOK)
   333  									w.Write([]byte(`{}`))
   334  								},
   335  							),
   336  						)
   337  					})
   338  
   339  					It("succeeds", func() {
   340  						Expect(func() {
   341  							flyCmd := exec.Command(
   342  								flyPath, "-t", targetName,
   343  								"set-pipeline",
   344  								"-n",
   345  								"--pipeline", "awesome-pipeline",
   346  								"-c", "fixtures/testConfig.yml",
   347  								"--var", `resource-key={"complicated": "secret"}`,
   348  								"--load-vars-from", "fixtures/vars.yml",
   349  							)
   350  
   351  							sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   352  							Expect(err).NotTo(HaveOccurred())
   353  							<-sess.Exited
   354  							Expect(sess.ExitCode()).To(Equal(0))
   355  						}).To(Change(func() int {
   356  							return len(atcServer.ReceivedRequests())
   357  						}).By(4))
   358  					})
   359  				})
   360  
   361  				Context("when the --non-interactive is passed", func() {
   362  					It("parses the config file and sends it to the ATC without interaction", func() {
   363  						Expect(func() {
   364  							flyCmd := exec.Command(
   365  								flyPath, "-t", targetName,
   366  								"set-pipeline",
   367  								"--pipeline", "awesome-pipeline",
   368  								"-c", "fixtures/testConfig.yml",
   369  								"--var", "resource-key=overridden-secret",
   370  								"--load-vars-from", "fixtures/vars.yml",
   371  								"--non-interactive",
   372  							)
   373  
   374  							sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   375  							Expect(err).NotTo(HaveOccurred())
   376  
   377  							Eventually(sess).Should(gbytes.Say("configuration updated"))
   378  
   379  							<-sess.Exited
   380  							Expect(sess.ExitCode()).To(Equal(0))
   381  
   382  						}).To(Change(func() int {
   383  							return len(atcServer.ReceivedRequests())
   384  						}).By(4))
   385  					})
   386  				})
   387  			})
   388  
   389  			Context("when a var is specified with -v", func() {
   390  				BeforeEach(func() {
   391  					expectSaveConfig(atc.Config{
   392  						Resources: atc.ResourceConfigs{
   393  							{
   394  								Name: "some-resource",
   395  								Type: "some-type",
   396  								Tags: atc.Tags{"val-1", "val-2"},
   397  								Source: atc.Source{
   398  									"private_key": `-----BEGIN SOME KEY-----
   399  this is super secure
   400  -----END SOME KEY-----
   401  `,
   402  									"config-a": "some-param-a",
   403  									"config-b": "some-param-b-via-v",
   404  									"bool":     true,
   405  									"number":   1.23,
   406  								},
   407  							},
   408  						},
   409  
   410  						Jobs: atc.JobConfigs{
   411  							{
   412  								Name: "some-job",
   413  								PlanSequence: []atc.Step{
   414  									{
   415  										Config: &atc.GetStep{
   416  											Name: "some-resource",
   417  										},
   418  									},
   419  								},
   420  							},
   421  						},
   422  					})
   423  				})
   424  
   425  				It("succeeds", func() {
   426  					Expect(func() {
   427  						flyCmd := exec.Command(
   428  							flyPath, "-t", targetName,
   429  							"set-pipeline",
   430  							"-n",
   431  							"--pipeline", "awesome-pipeline",
   432  							"-c", "fixtures/vars-pipeline.yml",
   433  							"-l", "fixtures/vars-pipeline-params-a.yml",
   434  							"-l", "fixtures/vars-pipeline-params-types.yml",
   435  							"-v", "param-b=some-param-b-via-v",
   436  						)
   437  
   438  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   439  						Expect(err).NotTo(HaveOccurred())
   440  						<-sess.Exited
   441  						Expect(sess.ExitCode()).To(Equal(0))
   442  					}).To(Change(func() int {
   443  						return len(atcServer.ReceivedRequests())
   444  					}).By(4))
   445  				})
   446  			})
   447  
   448  			Context("when vars are overridden with -v, some with special types", func() {
   449  				BeforeEach(func() {
   450  					expectSaveConfig(atc.Config{
   451  						Resources: atc.ResourceConfigs{
   452  							{
   453  								Name: "some-resource",
   454  								Type: "some-type",
   455  								Tags: atc.Tags{"val-1", "val-2"},
   456  								Source: atc.Source{
   457  									"private_key": `-----BEGIN SOME KEY-----
   458  this is super secure
   459  -----END SOME KEY-----
   460  `,
   461  									"config-a": "some-param-a",
   462  									"config-b": "some\nmultiline\nbusiness\n",
   463  									"bool":     false,
   464  									"number":   3.14,
   465  								},
   466  							},
   467  						},
   468  
   469  						Jobs: atc.JobConfigs{
   470  							{
   471  								Name: "some-job",
   472  								PlanSequence: []atc.Step{
   473  									{
   474  										Config: &atc.GetStep{
   475  											Name: "some-resource",
   476  										},
   477  									},
   478  								},
   479  							},
   480  						},
   481  					})
   482  				})
   483  
   484  				It("succeeds", func() {
   485  					Expect(func() {
   486  						flyCmd := exec.Command(
   487  							flyPath, "-t", targetName,
   488  							"set-pipeline",
   489  							"-n",
   490  							"--pipeline", "awesome-pipeline",
   491  							"-c", "fixtures/vars-pipeline.yml",
   492  							"-l", "fixtures/vars-pipeline-params-a.yml",
   493  							"-l", "fixtures/vars-pipeline-params-b.yml",
   494  							"-l", "fixtures/vars-pipeline-params-types.yml",
   495  							"-v", "param-b=some\nmultiline\nbusiness\n",
   496  							"-y", "bool-param=false",
   497  							"-y", "number-param=3.14",
   498  						)
   499  
   500  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   501  						Expect(err).NotTo(HaveOccurred())
   502  						<-sess.Exited
   503  						Expect(sess.ExitCode()).To(Equal(0))
   504  					}).To(Change(func() int {
   505  						return len(atcServer.ReceivedRequests())
   506  					}).By(4))
   507  				})
   508  			})
   509  
   510  			Context("when var flags use dot notation", func() {
   511  				BeforeEach(func() {
   512  					expectSaveConfig(atc.Config{
   513  						Resources: atc.ResourceConfigs{
   514  							{
   515  								Name: "some-resource",
   516  								Type: "some-type",
   517  								Source: atc.Source{
   518  									"a":     "foo",
   519  									"b":     "bar",
   520  									"other": "baz",
   521  								},
   522  							},
   523  						},
   524  
   525  						Jobs: atc.JobConfigs{
   526  							{
   527  								Name: "some-job",
   528  								PlanSequence: []atc.Step{
   529  									{
   530  										Config: &atc.GetStep{
   531  											Name: "some-resource",
   532  										},
   533  									},
   534  								},
   535  							},
   536  						},
   537  					})
   538  				})
   539  
   540  				It("succeeds", func() {
   541  					Expect(func() {
   542  						flyCmd := exec.Command(
   543  							flyPath, "-t", targetName,
   544  							"set-pipeline",
   545  							"-n",
   546  							"--pipeline", "awesome-pipeline",
   547  							"-c", "fixtures/nested-vars-pipeline.yml",
   548  							"-v", "source.a=foo",
   549  							"-v", "source.b=bar",
   550  							"-v", `"source.a"=baz`,
   551  						)
   552  
   553  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   554  						Expect(err).NotTo(HaveOccurred())
   555  						<-sess.Exited
   556  						Expect(sess.ExitCode()).To(Equal(0))
   557  					}).To(Change(func() int {
   558  						return len(atcServer.ReceivedRequests())
   559  					}).By(4))
   560  				})
   561  			})
   562  
   563  			Context("when a var is not specified", func() {
   564  				BeforeEach(func() {
   565  					config = atc.Config{
   566  						Resources: atc.ResourceConfigs{
   567  							{
   568  								Name: "some-resource",
   569  								Type: "some-type",
   570  								Tags: atc.Tags{"val-1", "val-2"},
   571  								Source: atc.Source{
   572  									"private_key": `-----BEGIN SOME KEY-----
   573  this is super secure
   574  -----END SOME KEY-----
   575  `,
   576  									"config-a": "some-param-a",
   577  									"config-b": "((param-b))",
   578  									"bool":     true,
   579  									"number":   1.23,
   580  								},
   581  							},
   582  						},
   583  
   584  						Jobs: atc.JobConfigs{
   585  							{
   586  								Name: "some-job",
   587  								PlanSequence: []atc.Step{
   588  									{
   589  										Config: &atc.GetStep{
   590  											Name: "some-resource",
   591  										},
   592  									},
   593  								},
   594  							},
   595  						},
   596  					}
   597  					expectSaveConfig(config)
   598  				})
   599  
   600  				It("succeeds, sending the remaining vars uninterpolated", func() {
   601  					Expect(func() {
   602  						flyCmd := exec.Command(
   603  							flyPath, "-t", targetName,
   604  							"set-pipeline",
   605  							"-n",
   606  							"--pipeline", "awesome-pipeline",
   607  							"-c", "fixtures/vars-pipeline.yml",
   608  							"-l", "fixtures/vars-pipeline-params-a.yml",
   609  							"-l", "fixtures/vars-pipeline-params-types.yml",
   610  						)
   611  
   612  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   613  						Expect(err).NotTo(HaveOccurred())
   614  						<-sess.Exited
   615  						Expect(sess.ExitCode()).To(Equal(0))
   616  					}).To(Change(func() int {
   617  						return len(atcServer.ReceivedRequests())
   618  					}).By(4))
   619  				})
   620  
   621  				Context("when the --check-creds option is used", func() {
   622  					Context("when the variable exists in the credentials manager", func() {
   623  						It("should succeed and send the vars uninterpolated", func() {
   624  							Expect(func() {
   625  								flyCmd := exec.Command(
   626  									flyPath, "-t", targetName,
   627  									"set-pipeline",
   628  									"-n",
   629  									"--pipeline", "awesome-pipeline",
   630  									"-c", "fixtures/vars-pipeline.yml",
   631  									"-l", "fixtures/vars-pipeline-params-a.yml",
   632  									"-l", "fixtures/vars-pipeline-params-types.yml",
   633  									"--check-creds",
   634  								)
   635  
   636  								sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   637  								Expect(err).NotTo(HaveOccurred())
   638  								<-sess.Exited
   639  								Expect(sess.ExitCode()).To(Equal(0))
   640  							}).To(Change(func() int {
   641  								return len(atcServer.ReceivedRequests())
   642  							}).By(4))
   643  						})
   644  					})
   645  
   646  					Context("when the variable does not exist in the credentials manager", func() {
   647  						BeforeEach(func() {
   648  							path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   649  							Expect(err).NotTo(HaveOccurred())
   650  
   651  							configResponse := atc.SaveConfigResponse{Errors: []string{"some-error"}}
   652  							atcServer.RouteToHandler("PUT", path,
   653  								ghttp.CombineHandlers(
   654  									ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
   655  									func(w http.ResponseWriter, r *http.Request) {
   656  										bodyConfig := getConfig(r)
   657  
   658  										receivedConfig := atc.Config{}
   659  										err = yaml.Unmarshal(bodyConfig, &receivedConfig)
   660  										Expect(err).NotTo(HaveOccurred())
   661  
   662  										Expect(receivedConfig).To(Equal(config))
   663  									},
   664  									ghttp.RespondWithJSONEncoded(http.StatusBadRequest, configResponse, http.Header{atc.ConfigVersionHeader: {"42"}}),
   665  								),
   666  							)
   667  						})
   668  
   669  						It("should error and return the missing field", func() {
   670  							Expect(func() {
   671  								flyCmd := exec.Command(
   672  									flyPath, "-t", targetName,
   673  									"set-pipeline",
   674  									"-n",
   675  									"--pipeline", "awesome-pipeline",
   676  									"-c", "fixtures/vars-pipeline.yml",
   677  									"-l", "fixtures/vars-pipeline-params-a.yml",
   678  									"-l", "fixtures/vars-pipeline-params-types.yml",
   679  									"--check-creds",
   680  								)
   681  
   682  								sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   683  								Expect(err).NotTo(HaveOccurred())
   684  
   685  								Eventually(sess.Err).Should(gbytes.Say(`error: invalid pipeline config:`))
   686  								Eventually(sess.Err).Should(gbytes.Say(`some-error`))
   687  
   688  								<-sess.Exited
   689  								Expect(sess.ExitCode()).NotTo(Equal(0))
   690  							}).To(Change(func() int {
   691  								return len(atcServer.ReceivedRequests())
   692  							}).By(3))
   693  						})
   694  					})
   695  				})
   696  
   697  			})
   698  		})
   699  
   700  		Describe("setting", func() {
   701  			var (
   702  				changedConfig atc.Config
   703  
   704  				payload    []byte
   705  				configFile *os.File
   706  			)
   707  
   708  			BeforeEach(func() {
   709  				var err error
   710  
   711  				configFile, err = ioutil.TempFile("", "fly-config-file")
   712  				Expect(err).NotTo(HaveOccurred())
   713  
   714  				changedConfig = config
   715  
   716  				path, err := atc.Routes.CreatePathForRoute(atc.GetConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   717  				Expect(err).NotTo(HaveOccurred())
   718  
   719  				atcServer.RouteToHandler("GET", path,
   720  					ghttp.RespondWithJSONEncoded(http.StatusOK, atc.ConfigResponse{Config: config}, http.Header{atc.ConfigVersionHeader: {"42"}}),
   721  				)
   722  
   723  				path_get, err := atc.Routes.CreatePathForRoute(atc.GetPipeline, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   724  				Expect(err).NotTo(HaveOccurred())
   725  
   726  				atcServer.RouteToHandler("GET", path_get,
   727  					ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Pipeline{Name: "awesome-pipeline", Paused: false, TeamName: "main"}),
   728  				)
   729  			})
   730  
   731  			JustBeforeEach(func() {
   732  				var err error
   733  
   734  				payload, err = yaml.Marshal(changedConfig)
   735  				Expect(err).NotTo(HaveOccurred())
   736  
   737  				_, err = configFile.Write(payload)
   738  				Expect(err).NotTo(HaveOccurred())
   739  
   740  				err = configFile.Close()
   741  				Expect(err).NotTo(HaveOccurred())
   742  			})
   743  
   744  			AfterEach(func() {
   745  				err := os.RemoveAll(configFile.Name())
   746  				Expect(err).NotTo(HaveOccurred())
   747  			})
   748  
   749  			Context("when not specifying a pipeline name", func() {
   750  				It("fails and says you should give a pipeline name", func() {
   751  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-c", configFile.Name())
   752  
   753  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   754  					Expect(err).NotTo(HaveOccurred())
   755  
   756  					<-sess.Exited
   757  					Expect(sess.ExitCode()).To(Equal(1))
   758  
   759  					Expect(sess.Err).To(gbytes.Say("error: the required flag `" + osFlag("p", "pipeline") + "' was not specified"))
   760  				})
   761  			})
   762  
   763  			Context("when specifying a pipeline name with a '/' character in it", func() {
   764  				It("fails and says '/' characters are not allowed", func() {
   765  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "forbidden/pipelinename", "-c", configFile.Name())
   766  
   767  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   768  					Expect(err).NotTo(HaveOccurred())
   769  
   770  					<-sess.Exited
   771  					Expect(sess.ExitCode()).To(Equal(1))
   772  
   773  					Expect(sess.Err).To(gbytes.Say("error: pipeline name cannot contain '/'"))
   774  				})
   775  			})
   776  
   777  			Context("when not specifying a config file", func() {
   778  				It("fails and says you should give a config file", func() {
   779  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline")
   780  
   781  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   782  					Expect(err).NotTo(HaveOccurred())
   783  
   784  					<-sess.Exited
   785  					Expect(sess.ExitCode()).To(Equal(1))
   786  
   787  					Expect(sess.Err).To(gbytes.Say("error: the required flag `" + osFlag("c", "config") + "' was not specified"))
   788  				})
   789  			})
   790  
   791  			Context("when configuring with groups re-ordered", func() {
   792  				BeforeEach(func() {
   793  					changedConfig.Groups = atc.GroupConfigs{
   794  						{
   795  							Name:      "some-other-group",
   796  							Jobs:      []string{"job-3", "job-4"},
   797  							Resources: []string{"resource-6", "resource-4"},
   798  						},
   799  						{
   800  							Name:      "some-group",
   801  							Jobs:      []string{"job-1", "job-2"},
   802  							Resources: []string{"resource-1", "resource-2"},
   803  						},
   804  					}
   805  
   806  					path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   807  					Expect(err).NotTo(HaveOccurred())
   808  
   809  					atcServer.RouteToHandler("PUT", path,
   810  						ghttp.CombineHandlers(
   811  							ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
   812  							func(w http.ResponseWriter, r *http.Request) {
   813  								config := getConfig(r)
   814  								Expect(config).To(MatchYAML(payload))
   815  							},
   816  							ghttp.RespondWith(http.StatusOK, "{}"),
   817  						),
   818  					)
   819  				})
   820  
   821  				It("parses the config file and sends it to the ATC", func() {
   822  					Expect(func() {
   823  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
   824  
   825  						stdin, err := flyCmd.StdinPipe()
   826  						Expect(err).NotTo(HaveOccurred())
   827  
   828  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   829  						Expect(err).NotTo(HaveOccurred())
   830  
   831  						Eventually(sess).Should(gbytes.Say("group some-group has changed"))
   832  
   833  						Eventually(sess).Should(gbytes.Say("group some-other-group has changed"))
   834  
   835  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
   836  						yes(stdin)
   837  
   838  						Eventually(sess).Should(gbytes.Say("configuration updated"))
   839  
   840  						<-sess.Exited
   841  						Expect(sess.ExitCode()).To(Equal(0))
   842  
   843  					}).To(Change(func() int {
   844  						return len(atcServer.ReceivedRequests())
   845  					}).By(4))
   846  				})
   847  			})
   848  
   849  			Context("when configuring succeeds", func() {
   850  				BeforeEach(func() {
   851  					newGroup := changedConfig.Groups[1]
   852  					newGroup.Name = "some-new-group"
   853  					changedConfig.Groups[0].Jobs = append(changedConfig.Groups[0].Jobs, "some-new-job")
   854  					changedConfig.Groups = append(changedConfig.Groups[:1], newGroup)
   855  
   856  					newResource := changedConfig.Resources[1]
   857  					newResource.Name = "some-new-resource"
   858  
   859  					newResources := make(atc.ResourceConfigs, len(changedConfig.Resources))
   860  					copy(newResources, changedConfig.Resources)
   861  					newResources[0].Type = "some-new-type"
   862  					newResources[1] = newResource
   863  					newResources[2].Source = atc.Source{"source-config": 5.0}
   864  
   865  					changedConfig.Resources = newResources
   866  
   867  					newResourceType := changedConfig.ResourceTypes[1]
   868  					newResourceType.Name = "some-new-resource-type"
   869  
   870  					newResourceTypes := make(atc.ResourceTypes, len(changedConfig.ResourceTypes))
   871  					copy(newResourceTypes, changedConfig.ResourceTypes)
   872  					newResourceTypes[0].Type = "some-new-type"
   873  					newResourceTypes[1] = newResourceType
   874  
   875  					changedConfig.ResourceTypes = newResourceTypes
   876  
   877  					newJob := changedConfig.Jobs[2]
   878  					newJob.Name = "some-new-job"
   879  					changedConfig.Jobs[0].Serial = false
   880  					changedConfig.Jobs = append(changedConfig.Jobs[:2], newJob)
   881  
   882  					path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
   883  					Expect(err).NotTo(HaveOccurred())
   884  
   885  					atcServer.RouteToHandler("PUT", path,
   886  						ghttp.CombineHandlers(
   887  							ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
   888  							func(w http.ResponseWriter, r *http.Request) {
   889  								config := getConfig(r)
   890  								Expect(config).To(MatchYAML(payload))
   891  							},
   892  							ghttp.RespondWith(http.StatusOK, "{}"),
   893  						),
   894  					)
   895  				})
   896  
   897  				It("parses the config file and sends it to the ATC", func() {
   898  					Expect(func() {
   899  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
   900  
   901  						stdin, err := flyCmd.StdinPipe()
   902  						Expect(err).NotTo(HaveOccurred())
   903  
   904  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   905  						Expect(err).NotTo(HaveOccurred())
   906  
   907  						Eventually(sess).Should(gbytes.Say("group some-group has changed"))
   908  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("- some-new-job", "green")))
   909  
   910  						Eventually(sess).Should(gbytes.Say("group some-other-group has been removed"))
   911  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-group", "red")))
   912  
   913  						Eventually(sess).Should(gbytes.Say("group some-new-group has been added"))
   914  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-group", "green")))
   915  
   916  						Eventually(sess).Should(gbytes.Say("resource some-resource has changed"))
   917  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-type", "red")))
   918  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-new-type", "green")))
   919  
   920  						Eventually(sess).Should(gbytes.Say("resource some-other-resource has been removed"))
   921  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-resource", "red")))
   922  
   923  						Eventually(sess).Should(gbytes.Say("resource some-new-resource has been added"))
   924  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-resource", "green")))
   925  
   926  						Eventually(sess).Should(gbytes.Say("resource type some-resource-type has changed"))
   927  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-type", "red")))
   928  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-new-type", "green")))
   929  
   930  						Eventually(sess).Should(gbytes.Say("resource type some-other-resource-type has been removed"))
   931  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-resource-type", "red")))
   932  
   933  						Eventually(sess).Should(gbytes.Say("resource type some-new-resource-type has been added"))
   934  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-resource-type", "green")))
   935  
   936  						Eventually(sess).Should(gbytes.Say("job some-job has changed"))
   937  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("serial: true", "red")))
   938  
   939  						Eventually(sess).Should(gbytes.Say("job some-other-job has been removed"))
   940  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-job", "red")))
   941  
   942  						Eventually(sess).Should(gbytes.Say("job some-new-job has been added"))
   943  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-job", "green")))
   944  
   945  						Eventually(sess).Should(gbytes.Say("pipeline name:"))
   946  						Eventually(sess).Should(gbytes.Say("awesome-pipeline"))
   947  						Consistently(sess).ShouldNot(gbytes.Say("pipeline instance vars:"))
   948  
   949  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
   950  						yes(stdin)
   951  
   952  						Eventually(sess).Should(gbytes.Say("configuration updated"))
   953  
   954  						<-sess.Exited
   955  						Expect(sess.ExitCode()).To(Equal(0))
   956  
   957  						Expect(sess.Out.Contents()).ToNot(ContainSubstring("some-resource-with-int-field"))
   958  
   959  						Expect(sess.Out.Contents()).ToNot(ContainSubstring("some-unchanged-job"))
   960  
   961  					}).To(Change(func() int {
   962  						return len(atcServer.ReceivedRequests())
   963  					}).By(4))
   964  				})
   965  
   966  				It("bails if the user rejects the diff", func() {
   967  					Expect(func() {
   968  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
   969  
   970  						stdin, err := flyCmd.StdinPipe()
   971  						Expect(err).NotTo(HaveOccurred())
   972  
   973  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   974  						Expect(err).NotTo(HaveOccurred())
   975  
   976  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
   977  						no(stdin)
   978  
   979  						<-sess.Exited
   980  						Expect(sess.ExitCode()).To(Equal(0))
   981  					}).To(Change(func() int {
   982  						return len(atcServer.ReceivedRequests())
   983  					}).By(2))
   984  				})
   985  
   986  				It("parses the config from stdin and sends it to the ATC", func() {
   987  					Expect(func() {
   988  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", "-")
   989  
   990  						stdin, err := flyCmd.StdinPipe()
   991  						Expect(err).NotTo(HaveOccurred())
   992  
   993  						file, err := os.Open(configFile.Name())
   994  						Expect(err).NotTo(HaveOccurred())
   995  						_, err = io.Copy(stdin, file)
   996  						Expect(err).NotTo(HaveOccurred())
   997  						file.Close()
   998  						stdin.Close()
   999  
  1000  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1001  						Expect(err).NotTo(HaveOccurred())
  1002  
  1003  						Eventually(sess).Should(gbytes.Say("group some-group has changed"))
  1004  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("- some-new-job", "green")))
  1005  
  1006  						Eventually(sess).Should(gbytes.Say("group some-other-group has been removed"))
  1007  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-group", "red")))
  1008  
  1009  						Eventually(sess).Should(gbytes.Say("group some-new-group has been added"))
  1010  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-group", "green")))
  1011  
  1012  						Eventually(sess).Should(gbytes.Say("resource some-resource has changed"))
  1013  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-type", "red")))
  1014  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-new-type", "green")))
  1015  
  1016  						Eventually(sess).Should(gbytes.Say("resource some-other-resource has been removed"))
  1017  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-resource", "red")))
  1018  
  1019  						Eventually(sess).Should(gbytes.Say("resource some-new-resource has been added"))
  1020  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-resource", "green")))
  1021  
  1022  						Eventually(sess).Should(gbytes.Say("resource type some-resource-type has changed"))
  1023  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-type", "red")))
  1024  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("type: some-new-type", "green")))
  1025  
  1026  						Eventually(sess).Should(gbytes.Say("resource type some-other-resource-type has been removed"))
  1027  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-resource-type", "red")))
  1028  
  1029  						Eventually(sess).Should(gbytes.Say("resource type some-new-resource-type has been added"))
  1030  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-resource-type", "green")))
  1031  
  1032  						Eventually(sess).Should(gbytes.Say("job some-job has changed"))
  1033  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("serial: true", "red")))
  1034  
  1035  						Eventually(sess).Should(gbytes.Say("job some-other-job has been removed"))
  1036  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-other-job", "red")))
  1037  
  1038  						Eventually(sess).Should(gbytes.Say("job some-new-job has been added"))
  1039  						Eventually(sess.Out.Contents).Should(ContainSubstring(ansi.Color("name: some-new-job", "green")))
  1040  
  1041  						// When read pipeline configure from stdin, it should do non-interactive mode.
  1042  						Consistently(sess).ShouldNot(gbytes.Say(`apply configuration\? \[yN\]: `))
  1043  
  1044  						Eventually(sess).Should(gbytes.Say("configuration updated"))
  1045  
  1046  						<-sess.Exited
  1047  						Expect(sess.ExitCode()).To(Equal(0))
  1048  
  1049  						Expect(sess.Out.Contents()).ToNot(ContainSubstring("some-resource-with-int-field"))
  1050  
  1051  						Expect(sess.Out.Contents()).ToNot(ContainSubstring("some-unchanged-job"))
  1052  					}).To(Change(func() int {
  1053  						return len(atcServer.ReceivedRequests())
  1054  					}).By(4))
  1055  				})
  1056  
  1057  				Context("when setting an instanced pipeline", func() {
  1058  					It("prints the instance vars as YAML", func() {
  1059  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-i", "version=1.2.3", "-c", configFile.Name())
  1060  
  1061  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1062  						Expect(err).NotTo(HaveOccurred())
  1063  
  1064  						Eventually(sess).Should(gbytes.Say("pipeline name:"))
  1065  						Eventually(sess).Should(gbytes.Say("awesome-pipeline"))
  1066  
  1067  						Eventually(sess).Should(gbytes.Say("pipeline instance vars:"))
  1068  						Eventually(sess).Should(gbytes.Say("  version: 1.2.3"))
  1069  					})
  1070  				})
  1071  			})
  1072  
  1073  			Context("when setting new pipeline with non-default team", func() {
  1074  				BeforeEach(func() {
  1075  					atcServer.AppendHandlers(
  1076  						ghttp.CombineHandlers(
  1077  							ghttp.VerifyRequest("GET", "/api/v1/teams/other-team"),
  1078  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{
  1079  								Name: "other-team",
  1080  							}),
  1081  						),
  1082  						ghttp.CombineHandlers(
  1083  							ghttp.VerifyRequest("GET", "/api/v1/teams/other-team/pipelines/awesome-pipeline/config"),
  1084  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{
  1085  								Name: "other-team",
  1086  							}),
  1087  						),
  1088  						ghttp.CombineHandlers(
  1089  							ghttp.VerifyRequest("PUT", "/api/v1/teams/other-team/pipelines/awesome-pipeline/config"),
  1090  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{
  1091  								Name: "other-team",
  1092  							}),
  1093  						),
  1094  						ghttp.CombineHandlers(
  1095  							ghttp.VerifyRequest("GET", "/api/v1/teams/other-team/pipelines/awesome-pipeline"),
  1096  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Pipeline{
  1097  								Name: "awesome-pipeline",
  1098  							}),
  1099  						),
  1100  					)
  1101  				})
  1102  
  1103  				It("successfully sets new pipeline to non-default team", func() {
  1104  					Expect(func() {
  1105  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name(), "--team", "other-team")
  1106  
  1107  						stdin, err := flyCmd.StdinPipe()
  1108  						Expect(err).NotTo(HaveOccurred())
  1109  
  1110  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1111  						Expect(err).NotTo(HaveOccurred())
  1112  
  1113  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1114  						yes(stdin)
  1115  
  1116  						Eventually(sess).Should(gbytes.Say("configuration updated"))
  1117  
  1118  						<-sess.Exited
  1119  						Expect(sess.ExitCode()).To(Equal(0))
  1120  
  1121  					}).To(Change(func() int {
  1122  						return len(atcServer.ReceivedRequests())
  1123  					}).By(5))
  1124  				})
  1125  
  1126  				It("bails if the user rejects the configuration", func() {
  1127  					Expect(func() {
  1128  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name(), "--team", "other-team")
  1129  
  1130  						stdin, err := flyCmd.StdinPipe()
  1131  						Expect(err).NotTo(HaveOccurred())
  1132  
  1133  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1134  						Expect(err).NotTo(HaveOccurred())
  1135  
  1136  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1137  						no(stdin)
  1138  						Eventually(sess).Should(gbytes.Say("bailing out"))
  1139  
  1140  						<-sess.Exited
  1141  						Expect(sess.ExitCode()).To(Equal(0))
  1142  					}).To(Change(func() int {
  1143  						return len(atcServer.ReceivedRequests())
  1144  					}).By(3))
  1145  				})
  1146  			})
  1147  
  1148  			Context("when configuring fails", func() {
  1149  				BeforeEach(func() {
  1150  					path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1151  					Expect(err).NotTo(HaveOccurred())
  1152  
  1153  					atcServer.RouteToHandler("PUT", path,
  1154  						ghttp.RespondWith(http.StatusInternalServerError, "nope"),
  1155  					)
  1156  					config.Resources[0].Name = "updated-name"
  1157  				})
  1158  
  1159  				It("prints the error to stderr and exits 1", func() {
  1160  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-c", configFile.Name(), "-p", "awesome-pipeline")
  1161  
  1162  					stdin, err := flyCmd.StdinPipe()
  1163  					Expect(err).NotTo(HaveOccurred())
  1164  
  1165  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1166  					Expect(err).NotTo(HaveOccurred())
  1167  
  1168  					Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1169  					yes(stdin)
  1170  
  1171  					Eventually(sess.Err).Should(gbytes.Say("500 Internal Server Error"))
  1172  					Eventually(sess.Err).Should(gbytes.Say("nope"))
  1173  
  1174  					<-sess.Exited
  1175  					Expect(sess.ExitCode()).To(Equal(1))
  1176  				})
  1177  			})
  1178  
  1179  			Context("when the pipeline is paused", func() {
  1180  				AssertSuccessWithPausedPipelineHelp := func(expectCreationMessage bool) {
  1181  					It("succeeds and prints an error message to help the user", func() {
  1182  						Expect(func() {
  1183  							flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
  1184  
  1185  							stdin, err := flyCmd.StdinPipe()
  1186  							Expect(err).NotTo(HaveOccurred())
  1187  
  1188  							sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1189  							Expect(err).NotTo(HaveOccurred())
  1190  
  1191  							Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1192  							yes(stdin)
  1193  
  1194  							if expectCreationMessage {
  1195  								pipelineURL := urljoiner.Join(atcServer.URL(), "teams/main/pipelines", "awesome-pipeline")
  1196  
  1197  								Eventually(sess).Should(gbytes.Say("pipeline created!"))
  1198  								Eventually(sess).Should(gbytes.Say(fmt.Sprintf("you can view your pipeline here: %s", pipelineURL)))
  1199  							}
  1200  
  1201  							Eventually(sess).Should(gbytes.Say("the pipeline is currently paused. to unpause, either:"))
  1202  							Eventually(sess).Should(gbytes.Say("  - run the unpause-pipeline command:"))
  1203  							Eventually(sess).Should(gbytes.Say("    %s -t %s unpause-pipeline -p awesome-pipeline", regexp.QuoteMeta(flyPath), targetName))
  1204  							Eventually(sess).Should(gbytes.Say("  - click play next to the pipeline in the web ui"))
  1205  
  1206  							<-sess.Exited
  1207  							Expect(sess.ExitCode()).To(Equal(0))
  1208  						}).To(Change(func() int {
  1209  							return len(atcServer.ReceivedRequests())
  1210  						}).By(4))
  1211  					})
  1212  				}
  1213  
  1214  				Context("when updating an existing pipeline", func() {
  1215  					BeforeEach(func() {
  1216  						path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1217  						Expect(err).NotTo(HaveOccurred())
  1218  
  1219  						path_get, err := atc.Routes.CreatePathForRoute(atc.GetPipeline, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1220  						Expect(err).NotTo(HaveOccurred())
  1221  
  1222  						atcServer.RouteToHandler("PUT", path, ghttp.CombineHandlers(
  1223  							ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
  1224  							func(w http.ResponseWriter, r *http.Request) {
  1225  								config := getConfig(r)
  1226  								Expect(config).To(MatchYAML(payload))
  1227  							},
  1228  							ghttp.RespondWith(http.StatusOK, "{}"),
  1229  						))
  1230  
  1231  						atcServer.RouteToHandler("GET", path_get, ghttp.RespondWithJSONEncoded(http.StatusOK,
  1232  							atc.Pipeline{Name: "awesome-pipeline", Paused: true, TeamName: "main"}))
  1233  
  1234  						config.Resources[0].Name = "updated-name"
  1235  					})
  1236  
  1237  					AssertSuccessWithPausedPipelineHelp(false)
  1238  				})
  1239  
  1240  				Context("when the pipeline is being created for the first time", func() {
  1241  					BeforeEach(func() {
  1242  						path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1243  						Expect(err).NotTo(HaveOccurred())
  1244  
  1245  						path_get, err := atc.Routes.CreatePathForRoute(atc.GetPipeline, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1246  						Expect(err).NotTo(HaveOccurred())
  1247  
  1248  						atcServer.RouteToHandler("PUT", path, ghttp.CombineHandlers(
  1249  							ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
  1250  							func(w http.ResponseWriter, r *http.Request) {
  1251  								config := getConfig(r)
  1252  								Expect(config).To(MatchYAML(payload))
  1253  							},
  1254  							ghttp.RespondWith(http.StatusCreated, "{}"),
  1255  						))
  1256  
  1257  						atcServer.RouteToHandler("GET", path_get, ghttp.RespondWithJSONEncoded(http.StatusOK,
  1258  							atc.Pipeline{Name: "awesome-pipeline", Paused: true, TeamName: "main"}))
  1259  
  1260  						config.Resources[0].Name = "updated-name"
  1261  					})
  1262  
  1263  					AssertSuccessWithPausedPipelineHelp(true)
  1264  				})
  1265  			})
  1266  
  1267  			Context("when the server returns warnings", func() {
  1268  				BeforeEach(func() {
  1269  					path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1270  					Expect(err).NotTo(HaveOccurred())
  1271  
  1272  					atcServer.RouteToHandler("PUT", path, ghttp.CombineHandlers(
  1273  						ghttp.VerifyHeaderKV(atc.ConfigVersionHeader, "42"),
  1274  						func(w http.ResponseWriter, r *http.Request) {
  1275  							config := getConfig(r)
  1276  							Expect(config).To(MatchYAML(payload))
  1277  						},
  1278  						ghttp.RespondWith(http.StatusCreated, `{"warnings":[
  1279  							{"type":"deprecation","message":"warning-1"},
  1280  							{"type":"deprecation","message":"warning-2"}
  1281  						]}`),
  1282  					))
  1283  					config.Resources[0].Name = "updated-name"
  1284  				})
  1285  
  1286  				It("succeeds and prints warnings", func() {
  1287  					Expect(func() {
  1288  						flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
  1289  
  1290  						stdin, err := flyCmd.StdinPipe()
  1291  						Expect(err).NotTo(HaveOccurred())
  1292  
  1293  						sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1294  						Expect(err).NotTo(HaveOccurred())
  1295  
  1296  						Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1297  						yes(stdin)
  1298  
  1299  						Eventually(sess.Err).Should(gbytes.Say("DEPRECATION WARNING:"))
  1300  						Eventually(sess.Err).Should(gbytes.Say("  - warning-1"))
  1301  						Eventually(sess.Err).Should(gbytes.Say("  - warning-2"))
  1302  						Eventually(sess).Should(gbytes.Say("pipeline created!"))
  1303  
  1304  						<-sess.Exited
  1305  						Expect(sess.ExitCode()).To(Equal(0))
  1306  					}).To(Change(func() int {
  1307  						return len(atcServer.ReceivedRequests())
  1308  					}).By(4))
  1309  				})
  1310  			})
  1311  
  1312  			Context("when there are no pipeline changes", func() {
  1313  				It("does not ask for user interaction to apply changes", func() {
  1314  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-p", "awesome-pipeline", "-c", configFile.Name())
  1315  
  1316  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1317  					Expect(err).NotTo(HaveOccurred())
  1318  
  1319  					Consistently(sess).ShouldNot(gbytes.Say("pipeline name:"))
  1320  					Consistently(sess).ShouldNot(gbytes.Say(`apply configuration\? \[yN\]: `))
  1321  
  1322  					Eventually(sess).Should(gbytes.Say("no changes to apply"))
  1323  
  1324  					<-sess.Exited
  1325  					Expect(sess.ExitCode()).To(Equal(0))
  1326  
  1327  				})
  1328  			})
  1329  
  1330  			Context("when the server rejects the request", func() {
  1331  				BeforeEach(func() {
  1332  					path, err := atc.Routes.CreatePathForRoute(atc.SaveConfig, rata.Params{"pipeline_name": "awesome-pipeline", "team_name": "main"})
  1333  					Expect(err).NotTo(HaveOccurred())
  1334  
  1335  					atcServer.RouteToHandler("PUT", path, func(w http.ResponseWriter, r *http.Request) {
  1336  						atcServer.CloseClientConnections()
  1337  					})
  1338  					config.Resources[0].Name = "updated-name"
  1339  				})
  1340  
  1341  				It("prints the error to stderr and exits 1", func() {
  1342  					flyCmd := exec.Command(flyPath, "-t", targetName, "set-pipeline", "-c", configFile.Name(), "-p", "awesome-pipeline")
  1343  
  1344  					stdin, err := flyCmd.StdinPipe()
  1345  					Expect(err).NotTo(HaveOccurred())
  1346  
  1347  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1348  					Expect(err).NotTo(HaveOccurred())
  1349  
  1350  					Eventually(sess).Should(gbytes.Say(`apply configuration\? \[yN\]: `))
  1351  					yes(stdin)
  1352  
  1353  					Eventually(sess.Err).Should(gbytes.Say("EOF"))
  1354  
  1355  					<-sess.Exited
  1356  					Expect(sess.ExitCode()).To(Equal(1))
  1357  				})
  1358  			})
  1359  		})
  1360  	})
  1361  })
  1362  
  1363  func getConfig(r *http.Request) []byte {
  1364  	defer r.Body.Close()
  1365  	payload, err := ioutil.ReadAll(r.Body)
  1366  	Expect(err).NotTo(HaveOccurred())
  1367  
  1368  	return payload
  1369  }