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