github.com/chenbh/concourse/v6@v6.4.2/fly/integration/hijack_test.go (about)

     1  package integration_test
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"os/exec"
     7  
     8  	"github.com/chenbh/concourse/v6/atc"
     9  	"github.com/gorilla/websocket"
    10  	"github.com/mgutz/ansi"
    11  	. "github.com/onsi/ginkgo"
    12  	. "github.com/onsi/gomega"
    13  	"github.com/onsi/gomega/gbytes"
    14  	"github.com/onsi/gomega/gexec"
    15  	"github.com/onsi/gomega/ghttp"
    16  )
    17  
    18  var _ = Describe("Hijacking", func() {
    19  	var hijacked <-chan struct{}
    20  	var workingDirectory string
    21  	var user string
    22  	var path string
    23  	var args []string
    24  
    25  	BeforeEach(func() {
    26  		hijacked = nil
    27  		workingDirectory = ""
    28  		user = "root"
    29  		path = "bash"
    30  		args = nil
    31  	})
    32  
    33  	upgrader := websocket.Upgrader{}
    34  
    35  	hijackHandler := func(id string, didHijack chan<- struct{}, errorMessages []string, teamName string) http.HandlerFunc {
    36  		return ghttp.CombineHandlers(
    37  			ghttp.VerifyRequest("GET", fmt.Sprintf("/api/v1/teams/"+teamName+"/containers/%s/hijack", id)),
    38  			func(w http.ResponseWriter, r *http.Request) {
    39  				defer GinkgoRecover()
    40  
    41  				conn, err := upgrader.Upgrade(w, r, nil)
    42  				Expect(err).NotTo(HaveOccurred())
    43  
    44  				defer conn.Close()
    45  
    46  				close(didHijack)
    47  
    48  				var processSpec atc.HijackProcessSpec
    49  				err = conn.ReadJSON(&processSpec)
    50  				Expect(err).NotTo(HaveOccurred())
    51  
    52  				Expect(processSpec.User).To(Equal(user))
    53  				Expect(processSpec.Dir).To(Equal(workingDirectory))
    54  				Expect(processSpec.Path).To(Equal(path))
    55  				Expect(processSpec.Args).To(Equal(args))
    56  
    57  				var payload atc.HijackInput
    58  
    59  				err = conn.ReadJSON(&payload)
    60  				Expect(err).NotTo(HaveOccurred())
    61  
    62  				Expect(payload).To(Equal(atc.HijackInput{
    63  					Stdin: []byte("some stdin"),
    64  				}))
    65  
    66  				err = conn.WriteJSON(atc.HijackOutput{
    67  					Stdout: []byte("some stdout"),
    68  				})
    69  				Expect(err).NotTo(HaveOccurred())
    70  
    71  				err = conn.WriteJSON(atc.HijackOutput{
    72  					Stderr: []byte("some stderr"),
    73  				})
    74  				Expect(err).NotTo(HaveOccurred())
    75  
    76  				if len(errorMessages) > 0 {
    77  					for _, msg := range errorMessages {
    78  						err := conn.WriteJSON(atc.HijackOutput{
    79  							Error: msg,
    80  						})
    81  						Expect(err).NotTo(HaveOccurred())
    82  					}
    83  
    84  					return
    85  				}
    86  
    87  				var closePayload atc.HijackInput
    88  
    89  				err = conn.ReadJSON(&closePayload)
    90  				Expect(err).NotTo(HaveOccurred())
    91  
    92  				Expect(closePayload).To(Equal(atc.HijackInput{
    93  					Closed: true,
    94  				}))
    95  
    96  				exitStatus := 123
    97  				err = conn.WriteJSON(atc.HijackOutput{
    98  					ExitStatus: &exitStatus,
    99  				})
   100  				Expect(err).NotTo(HaveOccurred())
   101  			},
   102  		)
   103  	}
   104  
   105  	fly := func(command string, args ...string) {
   106  		commandWithArgs := append([]string{command}, args...)
   107  
   108  		flyCmd := exec.Command(flyPath, append([]string{"-t", targetName}, commandWithArgs...)...)
   109  
   110  		stdin, err := flyCmd.StdinPipe()
   111  		Expect(err).NotTo(HaveOccurred())
   112  
   113  		sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   114  		Expect(err).NotTo(HaveOccurred())
   115  
   116  		Eventually(hijacked).Should(BeClosed())
   117  
   118  		_, err = fmt.Fprintf(stdin, "some stdin")
   119  		Expect(err).NotTo(HaveOccurred())
   120  
   121  		Eventually(sess.Out).Should(gbytes.Say("some stdout"))
   122  		Eventually(sess.Err).Should(gbytes.Say("some stderr"))
   123  
   124  		err = stdin.Close()
   125  		Expect(err).NotTo(HaveOccurred())
   126  
   127  		<-sess.Exited
   128  		Expect(sess.ExitCode()).To(Equal(123))
   129  	}
   130  
   131  	hijack := func(args ...string) {
   132  		fly("hijack", args...)
   133  	}
   134  
   135  	Context("with only a step name specified", func() {
   136  		BeforeEach(func() {
   137  			didHijack := make(chan struct{})
   138  			hijacked = didHijack
   139  
   140  			atcServer.AppendHandlers(
   141  				ghttp.CombineHandlers(
   142  					ghttp.VerifyRequest("GET", "/api/v1/builds"),
   143  					ghttp.RespondWithJSONEncoded(200, []atc.Build{
   144  						{ID: 4, Name: "1", Status: "started", JobName: "some-job"},
   145  						{ID: 3, Name: "3", Status: "started"},
   146  						{ID: 2, Name: "2", Status: "started"},
   147  						{ID: 1, Name: "1", Status: "finished"},
   148  					}),
   149  				),
   150  				ghttp.CombineHandlers(
   151  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"),
   152  					ghttp.RespondWithJSONEncoded(200, []atc.Container{
   153  						{ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", User: user},
   154  					}),
   155  				),
   156  				hijackHandler("container-id-1", didHijack, nil, "main"),
   157  			)
   158  		})
   159  
   160  		It("hijacks the most recent one-off build", func() {
   161  			hijack("-s", "some-step")
   162  		})
   163  
   164  		It("hijacks the most recent one-off build with a more politically correct command", func() {
   165  			fly("intercept", "-s", "some-step")
   166  		})
   167  	})
   168  
   169  	Context("when the container specifies a working directory", func() {
   170  		BeforeEach(func() {
   171  			didHijack := make(chan struct{})
   172  			hijacked = didHijack
   173  			workingDirectory = "/tmp/build/my-favorite-guid"
   174  
   175  			atcServer.AppendHandlers(
   176  				ghttp.CombineHandlers(
   177  					ghttp.VerifyRequest("GET", "/api/v1/builds"),
   178  					ghttp.RespondWithJSONEncoded(200, []atc.Build{
   179  						{ID: 3, Name: "3", Status: "started"},
   180  					}),
   181  				),
   182  				ghttp.CombineHandlers(
   183  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"),
   184  					ghttp.RespondWithJSONEncoded(200, []atc.Container{
   185  						{ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", WorkingDirectory: workingDirectory, User: user},
   186  					}),
   187  				),
   188  				hijackHandler("container-id-1", didHijack, nil, "main"),
   189  			)
   190  		})
   191  
   192  		It("hijacks the most recent one-off build in the specified working directory", func() {
   193  			hijack("-s", "some-step")
   194  		})
   195  	})
   196  
   197  	Context("when the container specifies a user", func() {
   198  		BeforeEach(func() {
   199  			didHijack := make(chan struct{})
   200  			hijacked = didHijack
   201  			user = "amelia"
   202  
   203  			atcServer.AppendHandlers(
   204  				ghttp.CombineHandlers(
   205  					ghttp.VerifyRequest("GET", "/api/v1/builds"),
   206  					ghttp.RespondWithJSONEncoded(200, []atc.Build{
   207  						{ID: 3, Name: "3", Status: "started"},
   208  					}),
   209  				),
   210  				ghttp.CombineHandlers(
   211  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"),
   212  					ghttp.RespondWithJSONEncoded(200, []atc.Container{
   213  						{ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", User: "amelia"},
   214  					}),
   215  				),
   216  				hijackHandler("container-id-1", didHijack, nil, "main"),
   217  			)
   218  		})
   219  
   220  		It("hijacks the most recent one-off build as the specified user", func() {
   221  			hijack("-s", "some-step")
   222  		})
   223  	})
   224  
   225  	Context("when no containers are found", func() {
   226  		BeforeEach(func() {
   227  			didHijack := make(chan struct{})
   228  			hijacked = didHijack
   229  
   230  			atcServer.AppendHandlers(
   231  				ghttp.CombineHandlers(
   232  					ghttp.VerifyRequest("GET", "/api/v1/builds"),
   233  					ghttp.RespondWithJSONEncoded(200, []atc.Build{
   234  						{ID: 1, Name: "1", Status: "finished"},
   235  					}),
   236  				),
   237  				ghttp.CombineHandlers(
   238  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=1&step_name=some-step"),
   239  					ghttp.RespondWithJSONEncoded(200, []atc.Container{}),
   240  				),
   241  				hijackHandler("container-id-1", didHijack, nil, "main"),
   242  			)
   243  		})
   244  
   245  		It("return a friendly error message", func() {
   246  			flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-s", "some-step")
   247  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   248  			Expect(err).NotTo(HaveOccurred())
   249  
   250  			Eventually(sess).Should(gexec.Exit(1))
   251  
   252  			Expect(sess.Err).To(gbytes.Say("no containers matched your search parameters!\n\nthey may have expired if your build hasn't recently finished.\n"))
   253  		})
   254  
   255  		Context("when a url is passed", func() {
   256  			It("return a friendly error message", func() {
   257  				flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s", atcServer.URL(), teamName))
   258  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   259  				Expect(err).NotTo(HaveOccurred())
   260  
   261  				Eventually(sess).Should(gexec.Exit(1))
   262  
   263  				Expect(sess.Err).To(gbytes.Say("no containers matched your search parameters!\n\nthey may have expired if your build hasn't recently finished.\n"))
   264  			})
   265  
   266  			It("returns an error when target from url is not found", func() {
   267  				flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s", "http://faketarget.com", teamName))
   268  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   269  				Expect(err).NotTo(HaveOccurred())
   270  
   271  				Eventually(sess).Should(gexec.Exit(1))
   272  
   273  				Expect(sess.Err).To(gbytes.Say("no target matching url"))
   274  			})
   275  
   276  			It("returns an error when team name from url is not found", func() {
   277  				flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s/builds/0", atcServer.URL(), "faketeam"))
   278  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   279  				Expect(err).NotTo(HaveOccurred())
   280  
   281  				Eventually(sess).Should(gexec.Exit(1))
   282  
   283  				Expect(sess.Err).To(gbytes.Say("no target matching url"))
   284  			})
   285  		})
   286  	})
   287  
   288  	Context("when no containers are found", func() {
   289  		BeforeEach(func() {
   290  			didHijack := make(chan struct{})
   291  			hijacked = didHijack
   292  			atcServer.AppendHandlers(
   293  				ghttp.CombineHandlers(
   294  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=0"),
   295  					ghttp.RespondWithJSONEncoded(200, []atc.Container{}),
   296  				),
   297  			)
   298  		})
   299  
   300  		It("logs an error message and response status/body", func() {
   301  			flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-b", "0")
   302  
   303  			stdin, err := flyCmd.StdinPipe()
   304  			Expect(err).NotTo(HaveOccurred())
   305  
   306  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   307  			Expect(err).NotTo(HaveOccurred())
   308  
   309  			Eventually(sess.Err.Contents).Should(ContainSubstring("no containers matched your search parameters!\n\nthey may have expired if your build hasn't recently finished.\n"))
   310  
   311  			err = stdin.Close()
   312  			Expect(err).NotTo(HaveOccurred())
   313  
   314  			<-sess.Exited
   315  			Expect(sess.ExitCode()).To(Equal(1))
   316  		})
   317  	})
   318  
   319  	Context("when multiple step containers are found", func() {
   320  		var (
   321  			containerList []atc.Container
   322  			didHijack     chan struct{}
   323  		)
   324  
   325  		BeforeEach(func() {
   326  			didHijack = make(chan struct{})
   327  			hijacked = didHijack
   328  			containerList = []atc.Container{
   329  				{
   330  					ID:           "container-id-1",
   331  					WorkerName:   "worker-name-1",
   332  					PipelineName: "pipeline-name-1",
   333  					JobName:      "some-job",
   334  					BuildName:    "2",
   335  					BuildID:      12,
   336  					Type:         "get",
   337  					StepName:     "some-input",
   338  					Attempt:      "1.1.1",
   339  					User:         user,
   340  					State:        atc.ContainerStateCreated,
   341  				},
   342  				{
   343  					ID:           "container-id-2",
   344  					WorkerName:   "worker-name-2",
   345  					PipelineName: "pipeline-name-1",
   346  					JobName:      "some-job",
   347  					BuildName:    "2",
   348  					BuildID:      13,
   349  					Type:         "put",
   350  					StepName:     "some-output",
   351  					Attempt:      "1.1.2",
   352  					User:         user,
   353  					State:        atc.ContainerStateCreated,
   354  				},
   355  				{
   356  					ID:           "container-id-3",
   357  					WorkerName:   "worker-name-2",
   358  					PipelineName: "pipeline-name-2",
   359  					JobName:      "some-job",
   360  					BuildName:    "2",
   361  					BuildID:      13,
   362  					StepName:     "some-output",
   363  					Type:         "task",
   364  					Attempt:      "1",
   365  					User:         user,
   366  					State:        atc.ContainerStateCreated,
   367  				},
   368  				{
   369  					ID:           "container-id-4",
   370  					WorkerName:   "worker-name-2",
   371  					PipelineName: "pipeline-name-2",
   372  					ResourceName: "banana",
   373  					User:         user,
   374  					Type:         "check",
   375  					State:        atc.ContainerStateCreated,
   376  				},
   377  			}
   378  		})
   379  
   380  		JustBeforeEach(func() {
   381  			atcServer.AppendHandlers(
   382  				ghttp.CombineHandlers(
   383  					ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "pipeline_name=pipeline-name-1&job_name=some-job"),
   384  					ghttp.RespondWithJSONEncoded(200, containerList),
   385  				),
   386  				hijackHandler("container-id-2", didHijack, nil, "main"),
   387  			)
   388  		})
   389  
   390  		It("asks the user to select the container from a menu", func() {
   391  			flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-j", "pipeline-name-1/some-job")
   392  
   393  			stdin, err := flyCmd.StdinPipe()
   394  			Expect(err).NotTo(HaveOccurred())
   395  
   396  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   397  			Expect(err).NotTo(HaveOccurred())
   398  
   399  			Eventually(sess.Out).Should(gbytes.Say("1. resource: banana, type: check"))
   400  			Eventually(sess.Out).Should(gbytes.Say("2. build #2, step: some-input, type: get, attempt: 1.1.1"))
   401  			Eventually(sess.Out).Should(gbytes.Say("3. build #2, step: some-output, type: put, attempt: 1.1.2"))
   402  			Eventually(sess.Out).Should(gbytes.Say("4. build #2, step: some-output, type: task, attempt: 1"))
   403  			Eventually(sess.Out).Should(gbytes.Say("choose a container: "))
   404  
   405  			_, err = fmt.Fprintf(stdin, "3\n")
   406  			Expect(err).NotTo(HaveOccurred())
   407  
   408  			Eventually(hijacked).Should(BeClosed())
   409  
   410  			_, err = fmt.Fprintf(stdin, "some stdin")
   411  			Expect(err).NotTo(HaveOccurred())
   412  
   413  			Eventually(sess.Out).Should(gbytes.Say("some stdout"))
   414  			Eventually(sess.Err).Should(gbytes.Say("some stderr"))
   415  
   416  			err = stdin.Close()
   417  			Expect(err).NotTo(HaveOccurred())
   418  
   419  			<-sess.Exited
   420  			Expect(sess.ExitCode()).To(Equal(123))
   421  		})
   422  
   423  		Context("and no containers are in hijackable state", func() {
   424  			BeforeEach(func() {
   425  				containerList = []atc.Container{
   426  					{
   427  						ID:           "container-id-2",
   428  						WorkerName:   "worker-name-1",
   429  						PipelineName: "pipeline-name-1",
   430  						JobName:      "some-job",
   431  						BuildName:    "2",
   432  						BuildID:      12,
   433  						Type:         "get",
   434  						StepName:     "some-input",
   435  						Attempt:      "1.1.1",
   436  						User:         user,
   437  						State:        atc.ContainerStateCreating,
   438  					},
   439  				}
   440  			})
   441  
   442  			It("should show that no containers are hijackable", func() {
   443  				flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-j", "pipeline-name-1/some-job")
   444  
   445  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   446  				Expect(err).NotTo(HaveOccurred())
   447  
   448  				<-sess.Exited
   449  				Expect(sess.ExitCode()).To(Equal(1))
   450  
   451  				Eventually(sess.Err).Should(gbytes.Say("no containers matched"))
   452  				close(didHijack)
   453  			})
   454  		})
   455  
   456  		Context("and some containers are in a non-hijackable state", func() {
   457  			BeforeEach(func() {
   458  				containerList = []atc.Container{
   459  					{
   460  						ID:           "container-id-1",
   461  						WorkerName:   "worker-name-1",
   462  						PipelineName: "pipeline-name-1",
   463  						JobName:      "some-job",
   464  						BuildName:    "2",
   465  						BuildID:      12,
   466  						Type:         "get",
   467  						StepName:     "some-input",
   468  						Attempt:      "1.1.1",
   469  						User:         user,
   470  						State:        atc.ContainerStateCreating,
   471  					},
   472  					{
   473  						ID:           "container-id-2",
   474  						WorkerName:   "worker-name-2",
   475  						PipelineName: "pipeline-name-1",
   476  						JobName:      "some-job",
   477  						BuildName:    "2",
   478  						BuildID:      13,
   479  						Type:         "put",
   480  						StepName:     "some-output",
   481  						Attempt:      "1.1.2",
   482  						User:         user,
   483  						State:        atc.ContainerStateCreated,
   484  					},
   485  					{
   486  						ID:           "container-id-3",
   487  						WorkerName:   "worker-name-2",
   488  						PipelineName: "pipeline-name-2",
   489  						JobName:      "some-job",
   490  						BuildName:    "2",
   491  						BuildID:      13,
   492  						StepName:     "some-output",
   493  						Type:         "task",
   494  						Attempt:      "1",
   495  						User:         user,
   496  						State:        atc.ContainerStateFailed,
   497  					},
   498  					{
   499  						ID:           "container-id-4",
   500  						WorkerName:   "worker-name-2",
   501  						PipelineName: "pipeline-name-2",
   502  						ResourceName: "banana",
   503  						User:         user,
   504  						Type:         "check",
   505  						State:        atc.ContainerStateDestroying,
   506  					},
   507  				}
   508  			})
   509  
   510  			It("should not display those containers in the list of results", func() {
   511  				flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-j", "pipeline-name-1/some-job")
   512  
   513  				stdin, err := flyCmd.StdinPipe()
   514  				Expect(err).NotTo(HaveOccurred())
   515  
   516  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   517  				Expect(err).NotTo(HaveOccurred())
   518  
   519  				Eventually(sess.Out).Should(gbytes.Say("1. build #2, step: some-output, type: put, attempt: 1.1.2"))
   520  				Eventually(sess.Out).Should(gbytes.Say("2. build #2, step: some-output, type: task, attempt: 1"))
   521  				Eventually(sess.Out).Should(gbytes.Say("choose a container: "))
   522  
   523  				_, err = fmt.Fprintf(stdin, "1\n")
   524  				Expect(err).NotTo(HaveOccurred())
   525  
   526  				Eventually(hijacked).Should(BeClosed())
   527  
   528  				_, err = fmt.Fprintf(stdin, "some stdin")
   529  				Expect(err).NotTo(HaveOccurred())
   530  
   531  				Eventually(sess.Out).Should(gbytes.Say("some stdout"))
   532  				Eventually(sess.Err).Should(gbytes.Say("some stderr"))
   533  
   534  				err = stdin.Close()
   535  				Expect(err).NotTo(HaveOccurred())
   536  
   537  				<-sess.Exited
   538  				Expect(sess.ExitCode()).To(Equal(123))
   539  			})
   540  		})
   541  
   542  		Context("and only one container is in hijackable state", func() {
   543  			BeforeEach(func() {
   544  				containerList = []atc.Container{
   545  					{
   546  						ID:           "container-id-1",
   547  						WorkerName:   "worker-name-1",
   548  						PipelineName: "pipeline-name-1",
   549  						JobName:      "some-job",
   550  						BuildName:    "1",
   551  						BuildID:      12,
   552  						Type:         "get",
   553  						StepName:     "some-input",
   554  						Attempt:      "1.1.1",
   555  						User:         user,
   556  						State:        atc.ContainerStateDestroying,
   557  					},
   558  					{
   559  						ID:           "container-id-2",
   560  						WorkerName:   "worker-name-2",
   561  						PipelineName: "pipeline-name-1",
   562  						JobName:      "some-job",
   563  						BuildName:    "2",
   564  						BuildID:      13,
   565  						Type:         "put",
   566  						StepName:     "some-output",
   567  						Attempt:      "1.1.2",
   568  						User:         user,
   569  						State:        atc.ContainerStateCreated,
   570  					},
   571  				}
   572  			})
   573  
   574  			It("hijacks the hijackable container", func() {
   575  				flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-j", "pipeline-name-1/some-job")
   576  
   577  				stdin, err := flyCmd.StdinPipe()
   578  				Expect(err).NotTo(HaveOccurred())
   579  
   580  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   581  				Expect(err).NotTo(HaveOccurred())
   582  
   583  				Eventually(hijacked).Should(BeClosed())
   584  
   585  				_, err = fmt.Fprintf(stdin, "some stdin")
   586  				Expect(err).NotTo(HaveOccurred())
   587  
   588  				Eventually(sess.Out).Should(gbytes.Say("some stdout"))
   589  				Eventually(sess.Err).Should(gbytes.Say("some stderr"))
   590  
   591  				err = stdin.Close()
   592  				Expect(err).NotTo(HaveOccurred())
   593  
   594  				<-sess.Exited
   595  				Expect(sess.ExitCode()).To(Equal(123))
   596  			})
   597  		})
   598  	})
   599  
   600  	Context("when hijack returns a single container", func() {
   601  		var (
   602  			containerArguments string
   603  			stepType           string
   604  			stepName           string
   605  			buildID            int
   606  			hijackHandlerError []string
   607  			pipelineName       string
   608  			resourceName       string
   609  			jobName            string
   610  			buildName          string
   611  			attempt            string
   612  			hijackTeamName     string
   613  		)
   614  
   615  		BeforeEach(func() {
   616  			hijackHandlerError = nil
   617  			pipelineName = "a-pipeline"
   618  			jobName = ""
   619  			buildName = ""
   620  			buildID = 0
   621  			stepType = ""
   622  			stepName = ""
   623  			resourceName = ""
   624  			containerArguments = ""
   625  			attempt = ""
   626  			hijackTeamName = "main"
   627  		})
   628  
   629  		JustBeforeEach(func() {
   630  			didHijack := make(chan struct{})
   631  			hijacked = didHijack
   632  
   633  			atcServer.AppendHandlers(
   634  				ghttp.CombineHandlers(
   635  					ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName+"/containers", containerArguments),
   636  					ghttp.RespondWithJSONEncoded(200, []atc.Container{
   637  						{ID: "container-id-1", State: atc.ContainerStateCreated, WorkerName: "some-worker", PipelineName: pipelineName, JobName: jobName, BuildName: buildName, BuildID: buildID, Type: stepType, StepName: stepName, ResourceName: resourceName, Attempt: attempt, User: user},
   638  					}),
   639  				),
   640  				hijackHandler("container-id-1", didHijack, hijackHandlerError, hijackTeamName),
   641  			)
   642  		})
   643  
   644  		Context("when called with check container", func() {
   645  			BeforeEach(func() {
   646  				resourceName = "some-resource-name"
   647  				containerArguments = "type=check&resource_name=some-resource-name&pipeline_name=a-pipeline"
   648  			})
   649  
   650  			Context("when the team is 'main'", func() {
   651  				BeforeEach(func() {
   652  					hijackTeamName = "main"
   653  				})
   654  				Context("and with pipeline specified", func() {
   655  					It("can accept the check resources name and a pipeline", func() {
   656  						hijack("--check", "a-pipeline/some-resource-name")
   657  					})
   658  				})
   659  
   660  				Context("and with url specified", func() {
   661  					It("hijacks the given check container by URL", func() {
   662  						hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/a-pipeline/resources/some-resource-name")
   663  					})
   664  				})
   665  			})
   666  
   667  			Context("when the team is 'other'", func() {
   668  				BeforeEach(func() {
   669  					hijackTeamName = "other"
   670  
   671  					atcServer.AppendHandlers(
   672  						ghttp.CombineHandlers(
   673  							ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   674  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   675  					)
   676  				})
   677  
   678  				Context("and with pipeline specified", func() {
   679  					It("can accept the check resources name and a pipeline", func() {
   680  						hijack("--check", "a-pipeline/some-resource-name", "--team", hijackTeamName)
   681  					})
   682  				})
   683  
   684  				Context("and with url specified", func() {
   685  					It("hijacks the given check container by URL", func() {
   686  						hijack("--url", atcServer.URL()+"/teams/other/pipelines/a-pipeline/resources/some-resource-name", "--team", "other")
   687  					})
   688  				})
   689  			})
   690  		})
   691  
   692  		Context("when called with a specific build id", func() {
   693  			BeforeEach(func() {
   694  				containerArguments = "build_id=2&step_name=some-step"
   695  				stepType = "task"
   696  				stepName = "some-step"
   697  				buildID = 2
   698  			})
   699  
   700  			Context("when the team is 'main'", func() {
   701  				BeforeEach(func() {
   702  					hijackTeamName = "main"
   703  				})
   704  				It("hijacks the most recent one-off build", func() {
   705  					hijack("-b", "2", "-s", "some-step")
   706  				})
   707  			})
   708  
   709  			Context("when the team is 'other'", func() {
   710  				BeforeEach(func() {
   711  					hijackTeamName = "other"
   712  
   713  					atcServer.AppendHandlers(
   714  						ghttp.CombineHandlers(
   715  							ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   716  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   717  					)
   718  				})
   719  
   720  				It("hijacks the most recent one-off build", func() {
   721  					hijack("-b", "2", "-s", "some-step", "--team", hijackTeamName)
   722  				})
   723  			})
   724  		})
   725  
   726  		Context("when called with a specific job", func() {
   727  			BeforeEach(func() {
   728  				containerArguments = "pipeline_name=some-pipeline&job_name=some-job&step_name=some-step"
   729  				jobName = "some-job"
   730  				buildName = "3"
   731  				buildID = 13
   732  				stepType = "task"
   733  				stepName = "some-step"
   734  			})
   735  
   736  			Context("hijacks the job's next build", func() {
   737  				Context("When the team is 'main'", func() {
   738  					BeforeEach(func() {
   739  						hijackTeamName = "main"
   740  					})
   741  					It("hijacks the job's next build with 'pipelineName/jobName'", func() {
   742  						hijack("--job", "some-pipeline/some-job", "--step", "some-step")
   743  					})
   744  
   745  					It("hijacks the job's next build when URL is specified", func() {
   746  						hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/some-pipeline/jobs/some-job", "--step", "some-step")
   747  					})
   748  				})
   749  
   750  				Context("When the team is 'other'", func() {
   751  					BeforeEach(func() {
   752  						hijackTeamName = "other"
   753  
   754  						atcServer.AppendHandlers(
   755  							ghttp.CombineHandlers(
   756  								ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   757  								ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   758  						)
   759  					})
   760  					It("hijacks the job's next build with 'pipelineName/jobName'", func() {
   761  						hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--team", hijackTeamName)
   762  					})
   763  
   764  					It("hijacks the job's next build when URL is specified", func() {
   765  						hijack("--url", atcServer.URL()+"/teams/other/pipelines/some-pipeline/jobs/some-job", "--step", "some-step", "--team", hijackTeamName)
   766  					})
   767  				})
   768  
   769  			})
   770  
   771  			Context("with a specific build of the job", func() {
   772  				BeforeEach(func() {
   773  					containerArguments = "pipeline_name=some-pipeline&job_name=some-job&build_name=3&step_name=some-step"
   774  				})
   775  
   776  				Context("When the team is 'main'", func() {
   777  					BeforeEach(func() {
   778  						hijackTeamName = "main"
   779  					})
   780  					It("hijacks the given build", func() {
   781  						hijack("--job", "some-pipeline/some-job", "--build", "3", "--step", "some-step")
   782  					})
   783  					It("hijacks the given build with URL", func() {
   784  						hijack("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job/builds/3", "--step", "some-step")
   785  					})
   786  				})
   787  
   788  				Context("When the team is 'other'", func() {
   789  					BeforeEach(func() {
   790  						hijackTeamName = "other"
   791  
   792  						atcServer.AppendHandlers(
   793  							ghttp.CombineHandlers(
   794  								ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   795  								ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   796  						)
   797  					})
   798  					It("hijacks the given build", func() {
   799  						hijack("--job", "some-pipeline/some-job", "--build", "3", "--step", "some-step", "--team", hijackTeamName)
   800  					})
   801  					It("hijacks the given build with URL", func() {
   802  						hijack("--url", atcServer.URL()+"/teams/other/pipelines/some-pipeline/jobs/some-job/builds/3", "--step", "some-step", "--team", hijackTeamName)
   803  					})
   804  				})
   805  
   806  			})
   807  		})
   808  
   809  		Context("when called with a specific attempt number", func() {
   810  			BeforeEach(func() {
   811  				containerArguments = "pipeline_name=some-pipeline&job_name=some-job&step_name=some-step&attempt=2.4"
   812  				jobName = "some-job"
   813  				buildName = "3"
   814  				buildID = 13
   815  				stepType = "task"
   816  				stepName = "some-step"
   817  				attempt = "2.4"
   818  			})
   819  
   820  			Context("When the team is 'main'", func() {
   821  				BeforeEach(func() {
   822  					hijackTeamName = "main"
   823  				})
   824  				It("hijacks the job's next build", func() {
   825  					hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--attempt", "2.4")
   826  				})
   827  			})
   828  
   829  			Context("When the team is 'other'", func() {
   830  				BeforeEach(func() {
   831  					hijackTeamName = "other"
   832  
   833  					atcServer.AppendHandlers(
   834  						ghttp.CombineHandlers(
   835  							ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   836  							ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   837  					)
   838  				})
   839  				It("hijacks the job's next build", func() {
   840  					hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--attempt", "2.4", "--team", hijackTeamName)
   841  				})
   842  			})
   843  		})
   844  
   845  		Context("when called with a step type", func() {
   846  			BeforeEach(func() {
   847  				containerArguments = "pipeline_name=some-pipeline&job_name=some-job&step_name=some-step&type=put"
   848  				jobName = "some-job"
   849  				buildName = "3"
   850  				buildID = 13
   851  				stepType = "put"
   852  				stepName = "some-step"
   853  				attempt = ""
   854  			})
   855  
   856  			It("hijacks the job's next build", func() {
   857  				hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--step-type", "put")
   858  			})
   859  		})
   860  
   861  		Context("when called with a specific path and args", func() {
   862  			BeforeEach(func() {
   863  				path = "sh"
   864  				args = []string{"echo hello"}
   865  
   866  				containerArguments = "build_id=2&step_name=some-step"
   867  				stepType = "task"
   868  				stepName = "some-step"
   869  				buildID = 2
   870  			})
   871  
   872  			It("hijacks and runs the provided path with args", func() {
   873  				hijack("-b", "2", "-s", "some-step", "sh", "echo hello")
   874  			})
   875  		})
   876  
   877  		Context("when hijacking yields an error", func() {
   878  			BeforeEach(func() {
   879  				resourceName = "some-resource-name"
   880  				containerArguments = "type=check&resource_name=some-resource-name&pipeline_name=a-pipeline"
   881  				hijackHandlerError = []string{"something went wrong"}
   882  			})
   883  
   884  			It("prints it to stderr and exits 255", func() {
   885  				flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--check", "a-pipeline/some-resource-name")
   886  
   887  				stdin, err := flyCmd.StdinPipe()
   888  				Expect(err).NotTo(HaveOccurred())
   889  
   890  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   891  				Expect(err).NotTo(HaveOccurred())
   892  
   893  				Eventually(hijacked).Should(BeClosed())
   894  
   895  				_, err = fmt.Fprintf(stdin, "some stdin")
   896  				Expect(err).NotTo(HaveOccurred())
   897  
   898  				Eventually(sess.Err.Contents).Should(ContainSubstring(ansi.Color("something went wrong", "red+b") + "\n"))
   899  
   900  				err = stdin.Close()
   901  				Expect(err).NotTo(HaveOccurred())
   902  
   903  				<-sess.Exited
   904  				Expect(sess.ExitCode()).To(Equal(255))
   905  			})
   906  		})
   907  	})
   908  
   909  	Context("when hijacking a specific container", func() {
   910  		var (
   911  			hijackHandlerError []string
   912  			statusCode         int
   913  			id                 string
   914  			hijackTeamName     string
   915  		)
   916  
   917  		BeforeEach(func() {
   918  			hijackHandlerError = nil
   919  			statusCode = 0
   920  			id = ""
   921  			hijackTeamName = "main"
   922  
   923  		})
   924  
   925  		JustBeforeEach(func() {
   926  			atcServer.AppendHandlers(
   927  				ghttp.CombineHandlers(
   928  					ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName+"/containers/container-id"),
   929  					ghttp.RespondWithJSONEncoded(statusCode, atc.Container{
   930  						ID:   id,
   931  						User: user,
   932  					}),
   933  				),
   934  			)
   935  		})
   936  
   937  		Context("when container exists", func() {
   938  			BeforeEach(func() {
   939  				statusCode = 200
   940  				id = "container-id"
   941  			})
   942  
   943  			Context("when hijack returns no error", func() {
   944  				JustBeforeEach(func() {
   945  					didHijack := make(chan struct{})
   946  					hijacked = didHijack
   947  					atcServer.AppendHandlers(
   948  						hijackHandler("container-id", didHijack, hijackHandlerError, hijackTeamName),
   949  					)
   950  				})
   951  				Context("when the team is 'main'", func() {
   952  					BeforeEach(func() {
   953  						hijackTeamName = "main"
   954  					})
   955  					It("should hijack container with associated handle", func() {
   956  						hijack("--handle", "container-id")
   957  					})
   958  				})
   959  
   960  				Context("when the team is 'other'", func() {
   961  					BeforeEach(func() {
   962  						hijackTeamName = "other"
   963  
   964  						atcServer.AppendHandlers(
   965  							ghttp.CombineHandlers(
   966  								ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName),
   967  								ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})),
   968  						)
   969  					})
   970  					It("should hijack container with associated handle to 'other' team", func() {
   971  						hijack("--handle", "container-id", "--team", hijackTeamName)
   972  					})
   973  				})
   974  			})
   975  
   976  			Context("when hijack returns error", func() {
   977  				JustBeforeEach(func() {
   978  					atcServer.AppendHandlers(
   979  						ghttp.CombineHandlers(
   980  							ghttp.VerifyRequest("GET", fmt.Sprintf("/api/v1/teams/main/containers/%s/hijack", id)),
   981  							ghttp.RespondWithJSONEncoded(403, nil),
   982  						),
   983  					)
   984  				})
   985  
   986  				It("should print out response status and error", func() {
   987  					flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--handle", "container-id")
   988  					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
   989  					Expect(err).NotTo(HaveOccurred())
   990  
   991  					Eventually(sess).Should(gexec.Exit(1))
   992  
   993  					Expect(sess.Err).To(gbytes.Say("error: 403 Forbidden websocket: bad handshake"))
   994  				})
   995  			})
   996  		})
   997  
   998  		Context("when container does not exist", func() {
   999  			BeforeEach(func() {
  1000  				statusCode = 404
  1001  			})
  1002  
  1003  			JustBeforeEach(func() {
  1004  				didHijack := make(chan struct{})
  1005  				hijacked = didHijack
  1006  				atcServer.AppendHandlers(
  1007  					hijackHandler("container-id", didHijack, hijackHandlerError, hijackTeamName),
  1008  				)
  1009  			})
  1010  
  1011  			It("should return an appropriate error message", func() {
  1012  				flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--handle", "container-id")
  1013  				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1014  				Expect(err).NotTo(HaveOccurred())
  1015  
  1016  				Eventually(sess).Should(gexec.Exit(1))
  1017  
  1018  				Expect(sess.Err).To(gbytes.Say("no containers matched the given handle id!\n\nthey may have expired if your build hasn't recently finished.\n"))
  1019  			})
  1020  		})
  1021  	})
  1022  
  1023  	Context("when passing a URL that doesn't match the target", func() {
  1024  		It("errors out when wrong team is specified", func() {
  1025  			flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--url", atcServer.URL()+"/teams/wrongteam/pipelines/a-pipeline/resources/some-resource-name")
  1026  
  1027  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1028  			Expect(err).NotTo(HaveOccurred())
  1029  
  1030  			Eventually(sess.Err.Contents).Should(ContainSubstring("Team in URL doesn't match the current team of the target"))
  1031  
  1032  			<-sess.Exited
  1033  			Expect(sess.ExitCode()).ToNot(Equal(0))
  1034  		})
  1035  
  1036  		It("errors out when wrong URL is specified", func() {
  1037  			flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--url", "http://wrong.example.com/teams/"+teamName+"/pipelines/a-pipeline/resources/some-resource-name")
  1038  
  1039  			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
  1040  			Expect(err).NotTo(HaveOccurred())
  1041  
  1042  			Eventually(sess.Err.Contents).Should(ContainSubstring("URL doesn't match that of target"))
  1043  
  1044  			<-sess.Exited
  1045  			Expect(sess.ExitCode()).ToNot(Equal(0))
  1046  		})
  1047  	})
  1048  })