github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/containers_test.go (about)

     1  package api_test
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"net/url"
    13  	"strconv"
    14  	"time"
    15  
    16  	"code.cloudfoundry.org/garden"
    17  	gfakes "code.cloudfoundry.org/garden/gardenfakes"
    18  	"github.com/pf-qiu/concourse/v6/atc"
    19  	"github.com/pf-qiu/concourse/v6/atc/db"
    20  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    21  	. "github.com/pf-qiu/concourse/v6/atc/testhelpers"
    22  	"github.com/pf-qiu/concourse/v6/atc/worker/workerfakes"
    23  	"github.com/gorilla/websocket"
    24  	. "github.com/onsi/ginkgo"
    25  	. "github.com/onsi/gomega"
    26  )
    27  
    28  var _ = Describe("Containers API", func() {
    29  	var (
    30  		stepType         = db.ContainerTypeTask
    31  		stepName         = "some-step"
    32  		pipelineID       = 1111
    33  		jobID            = 2222
    34  		buildID          = 3333
    35  		workingDirectory = "/tmp/build/my-favorite-guid"
    36  		attempt          = "1.5"
    37  		user             = "snoopy"
    38  
    39  		req *http.Request
    40  
    41  		fakeContainer1 *dbfakes.FakeContainer
    42  		fakeContainer2 *dbfakes.FakeContainer
    43  	)
    44  
    45  	BeforeEach(func() {
    46  		fakeContainer1 = new(dbfakes.FakeContainer)
    47  		fakeContainer1.HandleReturns("some-handle")
    48  		fakeContainer1.StateReturns("container-state")
    49  		fakeContainer1.WorkerNameReturns("some-worker-name")
    50  		fakeContainer1.MetadataReturns(db.ContainerMetadata{
    51  			Type: stepType,
    52  
    53  			StepName: stepName,
    54  			Attempt:  attempt,
    55  
    56  			PipelineID: pipelineID,
    57  			JobID:      jobID,
    58  			BuildID:    buildID,
    59  
    60  			WorkingDirectory: workingDirectory,
    61  			User:             user,
    62  		})
    63  
    64  		fakeContainer2 = new(dbfakes.FakeContainer)
    65  		fakeContainer2.HandleReturns("some-other-handle")
    66  		fakeContainer2.WorkerNameReturns("some-other-worker-name")
    67  		fakeContainer2.MetadataReturns(db.ContainerMetadata{
    68  			Type: stepType,
    69  
    70  			StepName: stepName + "-other",
    71  			Attempt:  attempt + ".1",
    72  
    73  			PipelineID: pipelineID + 1,
    74  			JobID:      jobID + 1,
    75  			BuildID:    buildID + 1,
    76  
    77  			WorkingDirectory: workingDirectory + "/other",
    78  			User:             user + "-other",
    79  		})
    80  	})
    81  
    82  	Describe("GET /api/v1/teams/a-team/containers", func() {
    83  		BeforeEach(func() {
    84  			var err error
    85  			req, err = http.NewRequest("GET", server.URL+"/api/v1/teams/a-team/containers", nil)
    86  			Expect(err).NotTo(HaveOccurred())
    87  			req.Header.Set("Content-Type", "application/json")
    88  		})
    89  
    90  		Context("when not authenticated", func() {
    91  			BeforeEach(func() {
    92  				fakeAccess.IsAuthenticatedReturns(false)
    93  			})
    94  
    95  			It("returns 401 Unauthorized", func() {
    96  				response, err := client.Do(req)
    97  				Expect(err).NotTo(HaveOccurred())
    98  
    99  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   100  			})
   101  		})
   102  
   103  		Context("when authenticated", func() {
   104  			BeforeEach(func() {
   105  				fakeAccess.IsAuthenticatedReturns(true)
   106  				fakeAccess.IsAuthorizedReturns(true)
   107  			})
   108  
   109  			Context("with no params", func() {
   110  				Context("when no errors are returned", func() {
   111  					BeforeEach(func() {
   112  						dbTeam.ContainersReturns([]db.Container{fakeContainer1, fakeContainer2}, nil)
   113  					})
   114  
   115  					It("returns 200", func() {
   116  						response, err := client.Do(req)
   117  						Expect(err).NotTo(HaveOccurred())
   118  
   119  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   120  					})
   121  
   122  					It("returns Content-Type application/json", func() {
   123  						response, err := client.Do(req)
   124  						Expect(err).NotTo(HaveOccurred())
   125  
   126  						expectedHeaderEntries := map[string]string{
   127  							"Content-Type": "application/json",
   128  						}
   129  						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   130  					})
   131  
   132  					It("returns all containers", func() {
   133  						response, err := client.Do(req)
   134  						Expect(err).NotTo(HaveOccurred())
   135  
   136  						body, err := ioutil.ReadAll(response.Body)
   137  						Expect(err).NotTo(HaveOccurred())
   138  
   139  						Expect(body).To(MatchJSON(`
   140  							[
   141  								{
   142  									"id": "some-handle",
   143  									"worker_name": "some-worker-name",
   144  									"type": "task",
   145  									"step_name": "some-step",
   146  									"attempt": "1.5",
   147  									"pipeline_id": 1111,
   148  									"job_id": 2222,
   149  									"state": "container-state",
   150  									"build_id": 3333,
   151  									"working_directory": "/tmp/build/my-favorite-guid",
   152  									"user": "snoopy"
   153  								},
   154  								{
   155  									"id": "some-other-handle",
   156  									"worker_name": "some-other-worker-name",
   157  									"type": "task",
   158  									"step_name": "some-step-other",
   159  									"attempt": "1.5.1",
   160  									"pipeline_id": 1112,
   161  									"job_id": 2223,
   162  									"build_id": 3334,
   163  									"working_directory": "/tmp/build/my-favorite-guid/other",
   164  									"user": "snoopy-other"
   165  								}
   166  							]
   167  						`))
   168  					})
   169  				})
   170  
   171  				Context("when no containers are found", func() {
   172  					BeforeEach(func() {
   173  						dbTeam.ContainersReturns([]db.Container{}, nil)
   174  					})
   175  
   176  					It("returns 200", func() {
   177  						response, err := client.Do(req)
   178  						Expect(err).NotTo(HaveOccurred())
   179  
   180  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   181  					})
   182  
   183  					It("returns an empty array", func() {
   184  						response, err := client.Do(req)
   185  						Expect(err).NotTo(HaveOccurred())
   186  
   187  						body, err := ioutil.ReadAll(response.Body)
   188  						Expect(err).NotTo(HaveOccurred())
   189  
   190  						Expect(body).To(MatchJSON(`
   191  						  []
   192  						`))
   193  					})
   194  				})
   195  
   196  				Context("when there is an error", func() {
   197  					var (
   198  						expectedErr error
   199  					)
   200  
   201  					BeforeEach(func() {
   202  						expectedErr = errors.New("some error")
   203  						dbTeam.ContainersReturns(nil, expectedErr)
   204  					})
   205  
   206  					It("returns 500", func() {
   207  						response, err := client.Do(req)
   208  						Expect(err).NotTo(HaveOccurred())
   209  
   210  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   211  					})
   212  				})
   213  			})
   214  
   215  			Describe("querying with pipeline id", func() {
   216  				BeforeEach(func() {
   217  					req.URL.RawQuery = url.Values{
   218  						"pipeline_id": []string{strconv.Itoa(pipelineID)},
   219  					}.Encode()
   220  				})
   221  
   222  				It("queries with it in the metadata", func() {
   223  					_, err := client.Do(req)
   224  					Expect(err).NotTo(HaveOccurred())
   225  
   226  					Expect(dbTeam.FindContainersByMetadataCallCount()).To(Equal(1))
   227  
   228  					meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   229  					Expect(meta).To(Equal(db.ContainerMetadata{
   230  						PipelineID: pipelineID,
   231  					}))
   232  				})
   233  			})
   234  
   235  			Describe("querying with job id", func() {
   236  				BeforeEach(func() {
   237  					req.URL.RawQuery = url.Values{
   238  						"job_id": []string{strconv.Itoa(jobID)},
   239  					}.Encode()
   240  				})
   241  
   242  				It("queries with it in the metadata", func() {
   243  					_, err := client.Do(req)
   244  					Expect(err).NotTo(HaveOccurred())
   245  
   246  					Expect(dbTeam.FindContainersByMetadataCallCount()).To(Equal(1))
   247  
   248  					meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   249  					Expect(meta).To(Equal(db.ContainerMetadata{
   250  						JobID: jobID,
   251  					}))
   252  				})
   253  			})
   254  
   255  			Describe("querying with type", func() {
   256  				BeforeEach(func() {
   257  					req.URL.RawQuery = url.Values{
   258  						"type": []string{string(stepType)},
   259  					}.Encode()
   260  				})
   261  
   262  				It("queries with it in the metadata", func() {
   263  					_, err := client.Do(req)
   264  					Expect(err).NotTo(HaveOccurred())
   265  
   266  					meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   267  					Expect(meta).To(Equal(db.ContainerMetadata{
   268  						Type: stepType,
   269  					}))
   270  				})
   271  			})
   272  
   273  			Describe("querying with step name", func() {
   274  				BeforeEach(func() {
   275  					req.URL.RawQuery = url.Values{
   276  						"step_name": []string{stepName},
   277  					}.Encode()
   278  				})
   279  
   280  				It("queries with it in the metadata", func() {
   281  					_, err := client.Do(req)
   282  					Expect(err).NotTo(HaveOccurred())
   283  
   284  					meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   285  					Expect(meta).To(Equal(db.ContainerMetadata{
   286  						StepName: stepName,
   287  					}))
   288  				})
   289  			})
   290  
   291  			Describe("querying with build id", func() {
   292  				Context("when the buildID can be parsed as an int", func() {
   293  					BeforeEach(func() {
   294  						buildIDString := strconv.Itoa(buildID)
   295  
   296  						req.URL.RawQuery = url.Values{
   297  							"build_id": []string{buildIDString},
   298  						}.Encode()
   299  					})
   300  
   301  					It("queries with it in the metadata", func() {
   302  						_, err := client.Do(req)
   303  						Expect(err).NotTo(HaveOccurred())
   304  
   305  						meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   306  						Expect(meta).To(Equal(db.ContainerMetadata{
   307  							BuildID: buildID,
   308  						}))
   309  					})
   310  
   311  					Context("when the buildID fails to be parsed as an int", func() {
   312  						BeforeEach(func() {
   313  							req.URL.RawQuery = url.Values{
   314  								"build_id": []string{"not-an-int"},
   315  							}.Encode()
   316  						})
   317  
   318  						It("returns 400 Bad Request", func() {
   319  							response, _ := client.Do(req)
   320  							Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
   321  						})
   322  
   323  						It("does not lookup containers", func() {
   324  							_, _ = client.Do(req)
   325  							Expect(dbTeam.FindContainersByMetadataCallCount()).To(Equal(0))
   326  						})
   327  					})
   328  				})
   329  			})
   330  
   331  			Describe("querying with attempts", func() {
   332  				Context("when the attempts can be parsed as a slice of int", func() {
   333  					BeforeEach(func() {
   334  						req.URL.RawQuery = url.Values{
   335  							"attempt": []string{attempt},
   336  						}.Encode()
   337  					})
   338  
   339  					It("queries with it in the metadata", func() {
   340  						_, err := client.Do(req)
   341  						Expect(err).NotTo(HaveOccurred())
   342  
   343  						meta := dbTeam.FindContainersByMetadataArgsForCall(0)
   344  						Expect(meta).To(Equal(db.ContainerMetadata{
   345  							Attempt: attempt,
   346  						}))
   347  					})
   348  				})
   349  			})
   350  
   351  			Describe("querying with type 'check'", func() {
   352  				BeforeEach(func() {
   353  					rawInstanceVars, _ := json.Marshal(atc.InstanceVars{"branch": "master"})
   354  					req.URL.RawQuery = url.Values{
   355  						"type":          []string{"check"},
   356  						"resource_name": []string{"some-resource"},
   357  						"pipeline_name": []string{"some-pipeline"},
   358  						"instance_vars": []string{string(rawInstanceVars)},
   359  					}.Encode()
   360  				})
   361  
   362  				It("queries with check properties", func() {
   363  					_, err := client.Do(req)
   364  					Expect(err).NotTo(HaveOccurred())
   365  
   366  					_, pipelineRef, resourceName, secretManager, varSourcePool := dbTeam.FindCheckContainersArgsForCall(0)
   367  					Expect(pipelineRef).To(Equal(atc.PipelineRef{Name: "some-pipeline", InstanceVars: atc.InstanceVars{"branch": "master"}}))
   368  					Expect(resourceName).To(Equal("some-resource"))
   369  					Expect(secretManager).To(Equal(fakeSecretManager))
   370  					Expect(varSourcePool).To(Equal(fakeVarSourcePool))
   371  				})
   372  			})
   373  		})
   374  	})
   375  
   376  	Describe("GET /api/v1/containers/:id", func() {
   377  		var handle = "some-handle"
   378  
   379  		BeforeEach(func() {
   380  			dbTeam.FindContainerByHandleReturns(fakeContainer1, true, nil)
   381  
   382  			var err error
   383  			req, err = http.NewRequest("GET", server.URL+"/api/v1/teams/a-team/containers/"+handle, nil)
   384  			Expect(err).NotTo(HaveOccurred())
   385  			req.Header.Set("Content-Type", "application/json")
   386  		})
   387  
   388  		Context("when not authenticated", func() {
   389  			BeforeEach(func() {
   390  				fakeAccess.IsAuthenticatedReturns(false)
   391  			})
   392  
   393  			It("returns 401 Unauthorized", func() {
   394  				response, err := client.Do(req)
   395  				Expect(err).NotTo(HaveOccurred())
   396  
   397  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   398  			})
   399  		})
   400  
   401  		Context("when authenticated", func() {
   402  			BeforeEach(func() {
   403  				fakeAccess.IsAuthenticatedReturns(true)
   404  				fakeAccess.IsAuthorizedReturns(true)
   405  			})
   406  
   407  			Context("when the container is not found", func() {
   408  				BeforeEach(func() {
   409  					dbTeam.FindContainerByHandleReturns(nil, false, nil)
   410  				})
   411  
   412  				It("returns 404 Not Found", func() {
   413  					response, err := client.Do(req)
   414  					Expect(err).NotTo(HaveOccurred())
   415  
   416  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   417  				})
   418  			})
   419  
   420  			Context("when the container is found", func() {
   421  				BeforeEach(func() {
   422  					dbTeam.FindContainerByHandleReturns(fakeContainer1, true, nil)
   423  				})
   424  
   425  				Context("when the container is within the team", func() {
   426  					BeforeEach(func() {
   427  						dbTeam.IsCheckContainerReturns(false, nil)
   428  						dbTeam.IsContainerWithinTeamReturns(true, nil)
   429  					})
   430  
   431  					It("returns 200 OK", func() {
   432  						response, err := client.Do(req)
   433  						Expect(err).NotTo(HaveOccurred())
   434  
   435  						Expect(response.StatusCode).To(Equal(http.StatusOK))
   436  					})
   437  
   438  					It("returns Content-Type application/json", func() {
   439  						response, err := client.Do(req)
   440  						Expect(err).NotTo(HaveOccurred())
   441  
   442  						expectedHeaderEntries := map[string]string{
   443  							"Content-Type": "application/json",
   444  						}
   445  						Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
   446  					})
   447  
   448  					It("performs lookup by id", func() {
   449  						_, err := client.Do(req)
   450  						Expect(err).NotTo(HaveOccurred())
   451  
   452  						Expect(dbTeam.FindContainerByHandleCallCount()).To(Equal(1))
   453  						Expect(dbTeam.FindContainerByHandleArgsForCall(0)).To(Equal(handle))
   454  					})
   455  
   456  					It("returns the container", func() {
   457  						response, err := client.Do(req)
   458  						Expect(err).NotTo(HaveOccurred())
   459  
   460  						body, err := ioutil.ReadAll(response.Body)
   461  						Expect(err).NotTo(HaveOccurred())
   462  
   463  						Expect(body).To(MatchJSON(`
   464  	 					{
   465  	 						"id": "some-handle",
   466  							"state": "container-state",
   467  	 						"worker_name": "some-worker-name",
   468  	 						"type": "task",
   469  	 						"step_name": "some-step",
   470  	 						"attempt": "1.5",
   471  	 						"pipeline_id": 1111,
   472  	 						"job_id": 2222,
   473  	 						"build_id": 3333,
   474  	 						"working_directory": "/tmp/build/my-favorite-guid",
   475  	 						"user": "snoopy"
   476  	 					}
   477  	 				`))
   478  					})
   479  				})
   480  
   481  				Context("when the container is not within the team", func() {
   482  					BeforeEach(func() {
   483  						dbTeam.IsCheckContainerReturns(false, nil)
   484  						dbTeam.IsContainerWithinTeamReturns(false, nil)
   485  					})
   486  
   487  					It("returns 404 Not Found", func() {
   488  						response, err := client.Do(req)
   489  						Expect(err).NotTo(HaveOccurred())
   490  
   491  						Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   492  					})
   493  				})
   494  			})
   495  
   496  			Context("when there is an error", func() {
   497  				var (
   498  					expectedErr error
   499  				)
   500  
   501  				BeforeEach(func() {
   502  					expectedErr = errors.New("some error")
   503  					dbTeam.FindContainerByHandleReturns(nil, false, expectedErr)
   504  				})
   505  
   506  				It("returns 500", func() {
   507  					response, err := client.Do(req)
   508  					Expect(err).NotTo(HaveOccurred())
   509  
   510  					Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   511  				})
   512  			})
   513  		})
   514  	})
   515  
   516  	Describe("GET /api/v1/containers/:id/hijack", func() {
   517  		var (
   518  			handle = "some-handle"
   519  
   520  			requestPayload string
   521  
   522  			conn     *websocket.Conn
   523  			response *http.Response
   524  
   525  			expectBadHandshake bool
   526  		)
   527  
   528  		BeforeEach(func() {
   529  			expectBadHandshake = false
   530  			requestPayload = `{"path":"ls", "user": "snoopy"}`
   531  		})
   532  
   533  		JustBeforeEach(func() {
   534  			wsURL, err := url.Parse(server.URL)
   535  			Expect(err).NotTo(HaveOccurred())
   536  
   537  			wsURL.Scheme = "ws"
   538  			wsURL.Path = "/api/v1/teams/a-team/containers/" + handle + "/hijack"
   539  
   540  			dialer := websocket.Dialer{}
   541  			conn, response, err = dialer.Dial(wsURL.String(), nil)
   542  			if !expectBadHandshake {
   543  				Expect(err).NotTo(HaveOccurred())
   544  
   545  				writer, err := conn.NextWriter(websocket.TextMessage)
   546  				Expect(err).NotTo(HaveOccurred())
   547  
   548  				_, err = writer.Write([]byte(requestPayload))
   549  				Expect(err).NotTo(HaveOccurred())
   550  
   551  				err = writer.Close()
   552  				Expect(err).NotTo(HaveOccurred())
   553  			}
   554  		})
   555  
   556  		AfterEach(func() {
   557  			if !expectBadHandshake {
   558  				_ = conn.Close()
   559  			}
   560  		})
   561  
   562  		Context("when authenticated", func() {
   563  			BeforeEach(func() {
   564  				fakeAccess.IsAuthenticatedReturns(true)
   565  				fakeAccess.IsAuthorizedReturns(true)
   566  			})
   567  
   568  			Context("and the worker client returns a container", func() {
   569  				var (
   570  					fakeDBContainer *dbfakes.FakeCreatedContainer
   571  					fakeContainer   *workerfakes.FakeContainer
   572  				)
   573  
   574  				BeforeEach(func() {
   575  					fakeDBContainer = new(dbfakes.FakeCreatedContainer)
   576  					dbTeam.FindContainerByHandleReturns(fakeDBContainer, true, nil)
   577  					fakeDBContainer.HandleReturns("some-handle")
   578  
   579  					fakeContainer = new(workerfakes.FakeContainer)
   580  					fakeWorkerClient.FindContainerReturns(fakeContainer, true, nil)
   581  				})
   582  
   583  				Context("when the container is a check container", func() {
   584  					BeforeEach(func() {
   585  						dbTeam.IsCheckContainerReturns(true, nil)
   586  					})
   587  
   588  					Context("when the user is not admin", func() {
   589  						BeforeEach(func() {
   590  							expectBadHandshake = true
   591  
   592  							fakeAccess.IsAdminReturns(false)
   593  						})
   594  
   595  						It("returns Forbidden", func() {
   596  							Expect(response.StatusCode).To(Equal(http.StatusForbidden))
   597  						})
   598  					})
   599  
   600  					Context("when the user is an admin", func() {
   601  						BeforeEach(func() {
   602  							fakeAccess.IsAdminReturns(true)
   603  						})
   604  
   605  						Context("when the container is not within the team", func() {
   606  							BeforeEach(func() {
   607  								expectBadHandshake = true
   608  
   609  								dbTeam.IsContainerWithinTeamReturns(false, nil)
   610  							})
   611  
   612  							It("returns 404 not found", func() {
   613  								Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   614  							})
   615  						})
   616  
   617  						Context("when the container is within the team", func() {
   618  							var (
   619  								fakeProcess *gfakes.FakeProcess
   620  								processExit chan int
   621  							)
   622  
   623  							BeforeEach(func() {
   624  								dbTeam.IsContainerWithinTeamReturns(true, nil)
   625  
   626  								exit := make(chan int)
   627  								processExit = exit
   628  
   629  								fakeProcess = new(gfakes.FakeProcess)
   630  								fakeProcess.WaitStub = func() (int, error) {
   631  									return <-exit, nil
   632  								}
   633  
   634  								fakeContainer.RunReturns(fakeProcess, nil)
   635  							})
   636  
   637  							AfterEach(func() {
   638  								close(processExit)
   639  							})
   640  
   641  							It("should try to hijack the container", func() {
   642  								Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   643  							})
   644  						})
   645  					})
   646  				})
   647  
   648  				Context("when the container is a build step container", func() {
   649  					BeforeEach(func() {
   650  						dbTeam.IsCheckContainerReturns(false, nil)
   651  					})
   652  
   653  					Context("when the container is not within the team", func() {
   654  						BeforeEach(func() {
   655  							expectBadHandshake = true
   656  
   657  							dbTeam.IsContainerWithinTeamReturns(false, nil)
   658  						})
   659  
   660  						It("returns 404 not found", func() {
   661  							Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   662  						})
   663  					})
   664  
   665  					Context("when the container is within the team", func() {
   666  						BeforeEach(func() {
   667  							dbTeam.IsContainerWithinTeamReturns(true, nil)
   668  						})
   669  
   670  						Context("when the call to lookup the container returns an error", func() {
   671  							BeforeEach(func() {
   672  								expectBadHandshake = true
   673  
   674  								fakeWorkerClient.FindContainerReturns(nil, false, errors.New("nope"))
   675  							})
   676  
   677  							It("returns 500 internal error", func() {
   678  								Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
   679  							})
   680  						})
   681  
   682  						Context("when the container could not be found on the worker client", func() {
   683  							BeforeEach(func() {
   684  								expectBadHandshake = true
   685  
   686  								fakeWorkerClient.FindContainerReturns(nil, false, nil)
   687  							})
   688  
   689  							It("returns 404 Not Found", func() {
   690  								Expect(response.StatusCode).To(Equal(http.StatusNotFound))
   691  							})
   692  						})
   693  
   694  						Context("when the request payload is invalid", func() {
   695  							BeforeEach(func() {
   696  								requestPayload = "ß"
   697  							})
   698  
   699  							It("closes the connection with an error", func() {
   700  								_, _, err := conn.ReadMessage()
   701  
   702  								Expect(websocket.IsCloseError(err, 1003)).To(BeTrue()) // unsupported data
   703  								Expect(err).To(MatchError(ContainSubstring("malformed process spec")))
   704  							})
   705  						})
   706  
   707  						Context("when running the process fails", func() {
   708  							var containerRunError = errors.New("container-run-error")
   709  
   710  							BeforeEach(func() {
   711  								fakeContainer.RunReturns(nil, containerRunError)
   712  							})
   713  
   714  							It("receives the error in the output", func() {
   715  								Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   716  
   717  								expectedHijackOutput := atc.HijackOutput{
   718  									Error: containerRunError.Error(),
   719  								}
   720  
   721  								var hijackOutput atc.HijackOutput
   722  								err := conn.ReadJSON(&hijackOutput)
   723  								Expect(err).ToNot(HaveOccurred())
   724  								Expect(hijackOutput).To(Equal(expectedHijackOutput))
   725  							})
   726  						})
   727  
   728  						Context("when running the process succeeds", func() {
   729  							var (
   730  								fakeProcess *gfakes.FakeProcess
   731  								processExit chan int
   732  							)
   733  
   734  							BeforeEach(func() {
   735  								exit := make(chan int)
   736  								processExit = exit
   737  
   738  								fakeProcess = new(gfakes.FakeProcess)
   739  								fakeProcess.WaitStub = func() (int, error) {
   740  									return <-exit, nil
   741  								}
   742  
   743  								fakeContainer.RunReturns(fakeProcess, nil)
   744  							})
   745  
   746  							AfterEach(func() {
   747  								close(processExit)
   748  							})
   749  
   750  							It("did not check if the user is admin", func() {
   751  								Expect(fakeAccess.IsAdminCallCount()).To(Equal(0))
   752  							})
   753  
   754  							It("hijacks the build", func() {
   755  								Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   756  
   757  								_, lookedUpTeamID, lookedUpHandle := fakeWorkerClient.FindContainerArgsForCall(0)
   758  								Expect(lookedUpTeamID).To(Equal(734))
   759  								Expect(lookedUpHandle).To(Equal(handle))
   760  
   761  								_, spec, io := fakeContainer.RunArgsForCall(0)
   762  								Expect(spec).To(Equal(garden.ProcessSpec{
   763  									Path: "ls",
   764  									User: "snoopy",
   765  								}))
   766  
   767  								Expect(io.Stdin).NotTo(BeNil())
   768  								Expect(io.Stdout).NotTo(BeNil())
   769  								Expect(io.Stderr).NotTo(BeNil())
   770  							})
   771  
   772  							It("updates the last hijack value", func() {
   773  								Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   774  
   775  								Expect(fakeContainer.UpdateLastHijackCallCount()).To(Equal(1))
   776  							})
   777  
   778  							Context("when the hijack timer elapses", func() {
   779  								JustBeforeEach(func() {
   780  									fakeClock.WaitForWatcherAndIncrement(time.Second)
   781  								})
   782  
   783  								It("updates the last hijack value again", func() {
   784  									Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   785  
   786  									Eventually(fakeContainer.UpdateLastHijackCallCount).Should(Equal(2))
   787  								})
   788  							})
   789  
   790  							Context("when stdin is sent over the API", func() {
   791  								JustBeforeEach(func() {
   792  									err := conn.WriteJSON(atc.HijackInput{
   793  										Stdin: []byte("some stdin\n"),
   794  									})
   795  									Expect(err).NotTo(HaveOccurred())
   796  								})
   797  
   798  								It("forwards the payload to the process", func() {
   799  									Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   800  
   801  									_, _, io := fakeContainer.RunArgsForCall(0)
   802  									Expect(bufio.NewReader(io.Stdin).ReadBytes('\n')).To(Equal([]byte("some stdin\n")))
   803  
   804  									Expect(interceptTimeout.ResetCallCount()).To(Equal(1))
   805  								})
   806  							})
   807  
   808  							Context("when stdin is closed via the API", func() {
   809  								JustBeforeEach(func() {
   810  									err := conn.WriteJSON(atc.HijackInput{
   811  										Closed: true,
   812  									})
   813  									Expect(err).NotTo(HaveOccurred())
   814  								})
   815  
   816  								It("closes the process's stdin", func() {
   817  									Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   818  
   819  									_, _, ioConfig := fakeContainer.RunArgsForCall(0)
   820  									_, err := ioConfig.Stdin.Read(make([]byte, 10))
   821  									Expect(err).To(Equal(io.EOF))
   822  								})
   823  							})
   824  
   825  							Context("when the process prints to stdout", func() {
   826  								JustBeforeEach(func() {
   827  									Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   828  
   829  									_, _, io := fakeContainer.RunArgsForCall(0)
   830  
   831  									_, err := fmt.Fprintf(io.Stdout, "some stdout\n")
   832  									Expect(err).NotTo(HaveOccurred())
   833  								})
   834  
   835  								It("forwards it to the response", func() {
   836  									var hijackOutput atc.HijackOutput
   837  									err := conn.ReadJSON(&hijackOutput)
   838  									Expect(err).NotTo(HaveOccurred())
   839  
   840  									Expect(hijackOutput).To(Equal(atc.HijackOutput{
   841  										Stdout: []byte("some stdout\n"),
   842  									}))
   843  								})
   844  							})
   845  
   846  							Context("when the process prints to stderr", func() {
   847  								JustBeforeEach(func() {
   848  									Eventually(fakeContainer.RunCallCount).Should(Equal(1))
   849  
   850  									_, _, io := fakeContainer.RunArgsForCall(0)
   851  
   852  									_, err := fmt.Fprintf(io.Stderr, "some stderr\n")
   853  									Expect(err).NotTo(HaveOccurred())
   854  								})
   855  
   856  								It("forwards it to the response", func() {
   857  									var hijackOutput atc.HijackOutput
   858  									err := conn.ReadJSON(&hijackOutput)
   859  									Expect(err).NotTo(HaveOccurred())
   860  
   861  									Expect(hijackOutput).To(Equal(atc.HijackOutput{
   862  										Stderr: []byte("some stderr\n"),
   863  									}))
   864  								})
   865  							})
   866  
   867  							Context("when the process exits", func() {
   868  								JustBeforeEach(func() {
   869  									Eventually(processExit).Should(BeSent(123))
   870  								})
   871  
   872  								It("forwards its exit status to the response", func() {
   873  									var hijackOutput atc.HijackOutput
   874  									err := conn.ReadJSON(&hijackOutput)
   875  									Expect(err).NotTo(HaveOccurred())
   876  
   877  									exitStatus := 123
   878  									Expect(hijackOutput).To(Equal(atc.HijackOutput{
   879  										ExitStatus: &exitStatus,
   880  									}))
   881  								})
   882  
   883  								It("closes the process' stdin pipe", func() {
   884  									_, _, io := fakeContainer.RunArgsForCall(0)
   885  
   886  									c := make(chan bool, 1)
   887  
   888  									go func() {
   889  										var b []byte
   890  										_, err := io.Stdin.Read(b)
   891  										if err != nil {
   892  											c <- true
   893  										}
   894  									}()
   895  
   896  									Eventually(c, 2*time.Second).Should(Receive())
   897  								})
   898  							})
   899  
   900  							Context("when new tty settings are sent over the API", func() {
   901  								JustBeforeEach(func() {
   902  									err := conn.WriteJSON(atc.HijackInput{
   903  										TTYSpec: &atc.HijackTTYSpec{
   904  											WindowSize: atc.HijackWindowSize{
   905  												Columns: 123,
   906  												Rows:    456,
   907  											},
   908  										},
   909  									})
   910  									Expect(err).NotTo(HaveOccurred())
   911  								})
   912  
   913  								It("forwards it to the process", func() {
   914  									Eventually(fakeProcess.SetTTYCallCount).Should(Equal(1))
   915  
   916  									Expect(fakeProcess.SetTTYArgsForCall(0)).To(Equal(garden.TTYSpec{
   917  										WindowSize: &garden.WindowSize{
   918  											Columns: 123,
   919  											Rows:    456,
   920  										},
   921  									}))
   922  								})
   923  
   924  								Context("and setting the TTY on the process fails", func() {
   925  									BeforeEach(func() {
   926  										fakeProcess.SetTTYReturns(errors.New("oh no!"))
   927  									})
   928  
   929  									It("forwards the error to the response", func() {
   930  										var hijackOutput atc.HijackOutput
   931  										err := conn.ReadJSON(&hijackOutput)
   932  										Expect(err).NotTo(HaveOccurred())
   933  
   934  										Expect(hijackOutput).To(Equal(atc.HijackOutput{
   935  											Error: "oh no!",
   936  										}))
   937  									})
   938  								})
   939  							})
   940  
   941  							Context("when waiting on the process fails", func() {
   942  								BeforeEach(func() {
   943  									fakeProcess.WaitReturns(0, errors.New("oh no!"))
   944  								})
   945  
   946  								It("forwards the error to the response", func() {
   947  									var hijackOutput atc.HijackOutput
   948  									err := conn.ReadJSON(&hijackOutput)
   949  									Expect(err).NotTo(HaveOccurred())
   950  
   951  									Expect(hijackOutput).To(Equal(atc.HijackOutput{
   952  										Error: "oh no!",
   953  									}))
   954  								})
   955  							})
   956  
   957  							Context("when intercept timeout channel sends a value", func() {
   958  								var (
   959  									interceptTimeoutChannel chan time.Time
   960  								)
   961  
   962  								BeforeEach(func() {
   963  									interceptTimeoutChannel = make(chan time.Time)
   964  									interceptTimeout.ChannelReturns(interceptTimeoutChannel)
   965  								})
   966  
   967  								It("exits with timeout error", func() {
   968  									interceptTimeout.ErrorReturns(errors.New("too slow"))
   969  									interceptTimeoutChannel <- time.Time{}
   970  
   971  									var hijackOutput atc.HijackOutput
   972  									err := conn.ReadJSON(&hijackOutput)
   973  									Expect(err).NotTo(HaveOccurred())
   974  
   975  									Expect(hijackOutput.Error).To(Equal("too slow"))
   976  								})
   977  							})
   978  						})
   979  					})
   980  				})
   981  			})
   982  		})
   983  
   984  		Context("when not authenticated", func() {
   985  			BeforeEach(func() {
   986  				expectBadHandshake = true
   987  
   988  				fakeAccess.IsAuthenticatedReturns(false)
   989  			})
   990  
   991  			It("returns 401 Unauthorized", func() {
   992  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
   993  			})
   994  		})
   995  	})
   996  
   997  	Describe("GET /api/v1/containers/destroying", func() {
   998  		BeforeEach(func() {
   999  			var err error
  1000  			req, err = http.NewRequest("GET", server.URL+"/api/v1/containers/destroying", nil)
  1001  			Expect(err).NotTo(HaveOccurred())
  1002  			req.Header.Set("Content-Type", "application/json")
  1003  
  1004  			fakeAccess.IsAuthenticatedReturns(true)
  1005  		})
  1006  
  1007  		Context("when not authenticated", func() {
  1008  			BeforeEach(func() {
  1009  				fakeAccess.IsAuthenticatedReturns(false)
  1010  			})
  1011  
  1012  			It("returns 401 Unauthorized", func() {
  1013  				response, err := client.Do(req)
  1014  				Expect(err).NotTo(HaveOccurred())
  1015  
  1016  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
  1017  			})
  1018  
  1019  			It("does not attempt to find the worker", func() {
  1020  				Expect(dbWorkerFactory.GetWorkerCallCount()).To(BeZero())
  1021  			})
  1022  		})
  1023  
  1024  		Context("when authenticated as system", func() {
  1025  			BeforeEach(func() {
  1026  				fakeAccess.IsSystemReturns(true)
  1027  			})
  1028  
  1029  			Context("with no params", func() {
  1030  				It("returns 400 Bad Request", func() {
  1031  					response, err := client.Do(req)
  1032  					Expect(err).NotTo(HaveOccurred())
  1033  
  1034  					Expect(fakeContainerRepository.FindDestroyingContainersCallCount()).To(Equal(0))
  1035  					Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
  1036  				})
  1037  			})
  1038  
  1039  			Context("querying with worker name", func() {
  1040  				BeforeEach(func() {
  1041  					req.URL.RawQuery = url.Values{
  1042  						"worker_name": []string{"some-worker-name"},
  1043  					}.Encode()
  1044  				})
  1045  
  1046  				Context("when there is an error", func() {
  1047  					BeforeEach(func() {
  1048  						fakeContainerRepository.FindDestroyingContainersReturns(nil, errors.New("some error"))
  1049  					})
  1050  
  1051  					It("returns 500", func() {
  1052  						response, err := client.Do(req)
  1053  						Expect(err).NotTo(HaveOccurred())
  1054  
  1055  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
  1056  					})
  1057  				})
  1058  
  1059  				Context("when no containers are found", func() {
  1060  					BeforeEach(func() {
  1061  						fakeContainerRepository.FindDestroyingContainersReturns([]string{}, nil)
  1062  					})
  1063  
  1064  					It("returns 200", func() {
  1065  						response, err := client.Do(req)
  1066  						Expect(err).NotTo(HaveOccurred())
  1067  
  1068  						Expect(response.StatusCode).To(Equal(http.StatusOK))
  1069  					})
  1070  
  1071  					It("returns an empty array", func() {
  1072  						response, err := client.Do(req)
  1073  						Expect(err).NotTo(HaveOccurred())
  1074  
  1075  						body, err := ioutil.ReadAll(response.Body)
  1076  						Expect(err).NotTo(HaveOccurred())
  1077  
  1078  						Expect(body).To(MatchJSON(`
  1079  							[]
  1080  						`))
  1081  					})
  1082  
  1083  					Context("when containers are found", func() {
  1084  						BeforeEach(func() {
  1085  							fakeContainerRepository.FindDestroyingContainersReturns([]string{
  1086  								"handle1",
  1087  								"handle2",
  1088  							}, nil)
  1089  						})
  1090  						It("returns container handles array", func() {
  1091  							response, err := client.Do(req)
  1092  							Expect(err).NotTo(HaveOccurred())
  1093  
  1094  							body, err := ioutil.ReadAll(response.Body)
  1095  							Expect(err).NotTo(HaveOccurred())
  1096  
  1097  							Expect(body).To(MatchJSON(`
  1098  								["handle1", "handle2"]
  1099  							`))
  1100  						})
  1101  					})
  1102  				})
  1103  
  1104  				It("queries with it in the worker name", func() {
  1105  					_, err := client.Do(req)
  1106  					Expect(err).NotTo(HaveOccurred())
  1107  
  1108  					Expect(fakeContainerRepository.FindDestroyingContainersCallCount()).To(Equal(1))
  1109  
  1110  					workerName := fakeContainerRepository.FindDestroyingContainersArgsForCall(0)
  1111  					Expect(workerName).To(Equal("some-worker-name"))
  1112  				})
  1113  			})
  1114  		})
  1115  	})
  1116  
  1117  	Describe("PUT /api/v1/containers/report", func() {
  1118  		var response *http.Response
  1119  		var body io.Reader
  1120  		var err error
  1121  
  1122  		BeforeEach(func() {
  1123  			body = bytes.NewBufferString(`
  1124  				[
  1125  					"handle1",
  1126  					"handle2"
  1127  				]
  1128  			`)
  1129  		})
  1130  
  1131  		JustBeforeEach(func() {
  1132  			req, err = http.NewRequest("PUT", server.URL+"/api/v1/containers/report", body)
  1133  			Expect(err).NotTo(HaveOccurred())
  1134  			req.Header.Set("Content-Type", "application/json")
  1135  		})
  1136  
  1137  		Context("when not authenticated", func() {
  1138  			BeforeEach(func() {
  1139  				fakeAccess.IsAuthenticatedReturns(false)
  1140  			})
  1141  
  1142  			It("returns 401 Unauthorized", func() {
  1143  				response, err = client.Do(req)
  1144  				Expect(err).NotTo(HaveOccurred())
  1145  				Expect(response.StatusCode).To(Equal(http.StatusUnauthorized))
  1146  			})
  1147  		})
  1148  
  1149  		Context("when authenticated as system", func() {
  1150  			BeforeEach(func() {
  1151  				fakeAccess.IsAuthenticatedReturns(true)
  1152  				fakeAccess.IsSystemReturns(true)
  1153  			})
  1154  
  1155  			Context("with no params", func() {
  1156  				It("returns 404", func() {
  1157  					response, err = client.Do(req)
  1158  					Expect(err).NotTo(HaveOccurred())
  1159  					Expect(fakeDestroyer.DestroyContainersCallCount()).To(Equal(0))
  1160  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
  1161  				})
  1162  
  1163  				It("returns Content-Type application/json", func() {
  1164  					response, err = client.Do(req)
  1165  					Expect(err).NotTo(HaveOccurred())
  1166  					Expect(response.StatusCode).To(Equal(http.StatusNotFound))
  1167  					expectedHeaderEntries := map[string]string{
  1168  						"Content-Type": "application/json",
  1169  					}
  1170  					Expect(response).Should(IncludeHeaderEntries(expectedHeaderEntries))
  1171  				})
  1172  			})
  1173  
  1174  			Context("querying with worker name", func() {
  1175  				JustBeforeEach(func() {
  1176  					req.URL.RawQuery = url.Values{
  1177  						"worker_name": []string{"some-worker-name"},
  1178  					}.Encode()
  1179  				})
  1180  
  1181  				Context("with invalid json", func() {
  1182  					BeforeEach(func() {
  1183  						body = bytes.NewBufferString(`{}`)
  1184  					})
  1185  
  1186  					It("returns 400", func() {
  1187  						response, err = client.Do(req)
  1188  						Expect(err).NotTo(HaveOccurred())
  1189  						Expect(response.StatusCode).To(Equal(http.StatusBadRequest))
  1190  					})
  1191  				})
  1192  
  1193  				Context("when there is an error", func() {
  1194  					BeforeEach(func() {
  1195  						fakeDestroyer.DestroyContainersReturns(errors.New("some error"))
  1196  					})
  1197  
  1198  					It("returns 500", func() {
  1199  						response, err = client.Do(req)
  1200  						Expect(err).NotTo(HaveOccurred())
  1201  						Expect(response.StatusCode).To(Equal(http.StatusInternalServerError))
  1202  					})
  1203  				})
  1204  
  1205  				Context("when containers are destroyed", func() {
  1206  					BeforeEach(func() {
  1207  						fakeDestroyer.DestroyContainersReturns(nil)
  1208  					})
  1209  
  1210  					It("returns 204", func() {
  1211  						response, err = client.Do(req)
  1212  						Expect(err).NotTo(HaveOccurred())
  1213  						Expect(response.StatusCode).To(Equal(http.StatusNoContent))
  1214  					})
  1215  				})
  1216  
  1217  				It("queries with it in the worker name", func() {
  1218  					_, err = client.Do(req)
  1219  					Expect(err).NotTo(HaveOccurred())
  1220  					Expect(fakeDestroyer.DestroyContainersCallCount()).To(Equal(1))
  1221  
  1222  					workerName, handles := fakeDestroyer.DestroyContainersArgsForCall(0)
  1223  					Expect(workerName).To(Equal("some-worker-name"))
  1224  					Expect(handles).To(Equal([]string{"handle1", "handle2"}))
  1225  				})
  1226  
  1227  				It("marks containers as missing", func() {
  1228  					_, err = client.Do(req)
  1229  					Expect(err).NotTo(HaveOccurred())
  1230  					Expect(fakeContainerRepository.UpdateContainersMissingSinceCallCount()).To(Equal(1))
  1231  
  1232  					workerName, handles := fakeContainerRepository.UpdateContainersMissingSinceArgsForCall(0)
  1233  					Expect(workerName).To(Equal("some-worker-name"))
  1234  					Expect(handles).To(Equal([]string{"handle1", "handle2"}))
  1235  				})
  1236  			})
  1237  		})
  1238  	})
  1239  })