github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/fly/integration/hijack_test.go (about) 1 package integration_test 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "os/exec" 8 "strings" 9 10 "github.com/pf-qiu/concourse/v6/atc" 11 "github.com/gorilla/websocket" 12 "github.com/mgutz/ansi" 13 . "github.com/onsi/ginkgo" 14 . "github.com/onsi/gomega" 15 "github.com/onsi/gomega/gbytes" 16 "github.com/onsi/gomega/gexec" 17 "github.com/onsi/gomega/ghttp" 18 ) 19 20 var _ = Describe("Hijacking", func() { 21 var hijacked <-chan struct{} 22 var workingDirectory string 23 var user string 24 var path string 25 var args []string 26 27 BeforeEach(func() { 28 hijacked = nil 29 workingDirectory = "" 30 user = "root" 31 path = "bash" 32 args = nil 33 }) 34 35 upgrader := websocket.Upgrader{} 36 37 hijackHandler := func(id string, didHijack chan<- struct{}, errorMessages []string, teamName string) http.HandlerFunc { 38 return ghttp.CombineHandlers( 39 ghttp.VerifyRequest("GET", fmt.Sprintf("/api/v1/teams/"+teamName+"/containers/%s/hijack", id)), 40 func(w http.ResponseWriter, r *http.Request) { 41 defer GinkgoRecover() 42 43 conn, err := upgrader.Upgrade(w, r, nil) 44 Expect(err).NotTo(HaveOccurred()) 45 46 defer conn.Close() 47 48 close(didHijack) 49 50 var processSpec atc.HijackProcessSpec 51 err = conn.ReadJSON(&processSpec) 52 Expect(err).NotTo(HaveOccurred()) 53 54 Expect(processSpec.User).To(Equal(user)) 55 Expect(processSpec.Dir).To(Equal(workingDirectory)) 56 Expect(processSpec.Path).To(Equal(path)) 57 Expect(processSpec.Args).To(Equal(args)) 58 59 var payload atc.HijackInput 60 err = conn.ReadJSON(&payload) 61 Expect(err).NotTo(HaveOccurred()) 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 err = conn.ReadJSON(&closePayload) 89 Expect(err).NotTo(HaveOccurred()) 90 Expect(closePayload).To(Equal(atc.HijackInput{ 91 Closed: true, 92 })) 93 94 exitStatus := 123 95 err = conn.WriteJSON(atc.HijackOutput{ 96 ExitStatus: &exitStatus, 97 }) 98 Expect(err).NotTo(HaveOccurred()) 99 }, 100 ) 101 } 102 103 fly := func(command string, args ...string) { 104 commandWithArgs := append([]string{command}, args...) 105 106 flyCmd := exec.Command(flyPath, append([]string{"-t", targetName}, commandWithArgs...)...) 107 108 stdin, err := flyCmd.StdinPipe() 109 Expect(err).NotTo(HaveOccurred()) 110 111 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 112 Expect(err).NotTo(HaveOccurred()) 113 114 Eventually(hijacked).Should(BeClosed()) 115 116 _, err = fmt.Fprintf(stdin, "some stdin") 117 Expect(err).NotTo(HaveOccurred()) 118 119 Eventually(sess.Out).Should(gbytes.Say("some stdout")) 120 Eventually(sess.Err).Should(gbytes.Say("some stderr")) 121 122 err = stdin.Close() 123 Expect(err).NotTo(HaveOccurred()) 124 125 <-sess.Exited 126 Expect(sess.ExitCode()).To(Equal(123)) 127 } 128 129 hijack := func(args ...string) { 130 fly("hijack", args...) 131 } 132 133 Context("with only a step name specified", func() { 134 BeforeEach(func() { 135 didHijack := make(chan struct{}) 136 hijacked = didHijack 137 138 atcServer.AppendHandlers( 139 ghttp.CombineHandlers( 140 ghttp.VerifyRequest("GET", "/api/v1/builds"), 141 ghttp.RespondWithJSONEncoded(200, []atc.Build{ 142 {ID: 4, Name: "1", Status: "started", JobName: "some-job"}, 143 {ID: 3, Name: "3", Status: "started"}, 144 {ID: 2, Name: "2", Status: "started"}, 145 {ID: 1, Name: "1", Status: "finished"}, 146 }), 147 ), 148 ghttp.CombineHandlers( 149 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"), 150 ghttp.RespondWithJSONEncoded(200, []atc.Container{ 151 {ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", User: user}, 152 }), 153 ), 154 hijackHandler("container-id-1", didHijack, nil, "main"), 155 ) 156 }) 157 158 It("hijacks the most recent one-off build", func() { 159 hijack("-s", "some-step") 160 }) 161 162 It("hijacks the most recent one-off build with a more politically correct command", func() { 163 fly("intercept", "-s", "some-step") 164 }) 165 }) 166 167 Context("when the container specifies a working directory", func() { 168 BeforeEach(func() { 169 didHijack := make(chan struct{}) 170 hijacked = didHijack 171 workingDirectory = "/tmp/build/my-favorite-guid" 172 173 atcServer.AppendHandlers( 174 ghttp.CombineHandlers( 175 ghttp.VerifyRequest("GET", "/api/v1/builds"), 176 ghttp.RespondWithJSONEncoded(200, []atc.Build{ 177 {ID: 3, Name: "3", Status: "started"}, 178 }), 179 ), 180 ghttp.CombineHandlers( 181 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"), 182 ghttp.RespondWithJSONEncoded(200, []atc.Container{ 183 {ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", WorkingDirectory: workingDirectory, User: user}, 184 }), 185 ), 186 hijackHandler("container-id-1", didHijack, nil, "main"), 187 ) 188 }) 189 190 It("hijacks the most recent one-off build in the specified working directory", func() { 191 hijack("-s", "some-step") 192 }) 193 }) 194 195 Context("when the container specifies a user", func() { 196 BeforeEach(func() { 197 didHijack := make(chan struct{}) 198 hijacked = didHijack 199 user = "amelia" 200 201 atcServer.AppendHandlers( 202 ghttp.CombineHandlers( 203 ghttp.VerifyRequest("GET", "/api/v1/builds"), 204 ghttp.RespondWithJSONEncoded(200, []atc.Build{ 205 {ID: 3, Name: "3", Status: "started"}, 206 }), 207 ), 208 ghttp.CombineHandlers( 209 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=3&step_name=some-step"), 210 ghttp.RespondWithJSONEncoded(200, []atc.Container{ 211 {ID: "container-id-1", State: atc.ContainerStateCreated, BuildID: 3, Type: "task", StepName: "some-step", User: "amelia"}, 212 }), 213 ), 214 hijackHandler("container-id-1", didHijack, nil, "main"), 215 ) 216 }) 217 218 It("hijacks the most recent one-off build as the specified user", func() { 219 hijack("-s", "some-step") 220 }) 221 }) 222 223 Context("when no containers are found", func() { 224 BeforeEach(func() { 225 didHijack := make(chan struct{}) 226 hijacked = didHijack 227 228 atcServer.AppendHandlers( 229 ghttp.CombineHandlers( 230 ghttp.VerifyRequest("GET", "/api/v1/builds"), 231 ghttp.RespondWithJSONEncoded(200, []atc.Build{ 232 {ID: 1, Name: "1", Status: "finished"}, 233 }), 234 ), 235 ghttp.CombineHandlers( 236 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=1&step_name=some-step"), 237 ghttp.RespondWithJSONEncoded(200, []atc.Container{}), 238 ), 239 hijackHandler("container-id-1", didHijack, nil, "main"), 240 ) 241 }) 242 243 It("return a friendly error message", func() { 244 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-s", "some-step") 245 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 246 Expect(err).NotTo(HaveOccurred()) 247 248 Eventually(sess).Should(gexec.Exit(1)) 249 250 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")) 251 }) 252 253 Context("when a url is passed", func() { 254 It("return a friendly error message", func() { 255 flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s", atcServer.URL(), teamName)) 256 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 257 Expect(err).NotTo(HaveOccurred()) 258 259 Eventually(sess).Should(gexec.Exit(1)) 260 261 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")) 262 }) 263 264 It("returns an error when target from url is not found", func() { 265 flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s", "http://faketarget.com", teamName)) 266 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 267 Expect(err).NotTo(HaveOccurred()) 268 269 Eventually(sess).Should(gexec.Exit(1)) 270 271 Expect(sess.Err).To(gbytes.Say("no target matching url")) 272 }) 273 274 It("returns an error when team name from url is not found", func() { 275 flyCmd := exec.Command(flyPath, "hijack", "-s", "some-step", "-u", fmt.Sprintf("%s/teams/%s/builds/0", atcServer.URL(), "faketeam")) 276 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 277 Expect(err).NotTo(HaveOccurred()) 278 279 Eventually(sess).Should(gexec.Exit(1)) 280 281 Expect(sess.Err).To(gbytes.Say("no target matching url")) 282 }) 283 }) 284 }) 285 286 Context("when no containers are found", func() { 287 BeforeEach(func() { 288 didHijack := make(chan struct{}) 289 hijacked = didHijack 290 atcServer.AppendHandlers( 291 ghttp.CombineHandlers( 292 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "build_id=0"), 293 ghttp.RespondWithJSONEncoded(200, []atc.Container{}), 294 ), 295 ) 296 }) 297 298 It("logs an error message and response status/body", func() { 299 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "-b", "0") 300 301 stdin, err := flyCmd.StdinPipe() 302 Expect(err).NotTo(HaveOccurred()) 303 304 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 305 Expect(err).NotTo(HaveOccurred()) 306 307 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")) 308 309 err = stdin.Close() 310 Expect(err).NotTo(HaveOccurred()) 311 312 <-sess.Exited 313 Expect(sess.ExitCode()).To(Equal(1)) 314 }) 315 }) 316 317 Context("when multiple step containers are found", func() { 318 var ( 319 containerList []atc.Container 320 didHijack chan struct{} 321 expectedQueryParams []string 322 ) 323 324 BeforeEach(func() { 325 didHijack = make(chan struct{}) 326 hijacked = didHijack 327 containerList = []atc.Container{ 328 { 329 ID: "container-id-1", 330 WorkerName: "worker-name-1", 331 PipelineName: "pipeline-name-1", 332 JobName: "some-job", 333 BuildName: "2", 334 BuildID: 12, 335 Type: "get", 336 StepName: "some-input", 337 Attempt: "1.1.1", 338 User: user, 339 State: atc.ContainerStateCreated, 340 }, 341 { 342 ID: "container-id-2", 343 WorkerName: "worker-name-2", 344 PipelineName: "pipeline-name-1", 345 JobName: "some-job", 346 BuildName: "2", 347 BuildID: 13, 348 Type: "put", 349 StepName: "some-output", 350 Attempt: "1.1.2", 351 User: user, 352 State: atc.ContainerStateCreated, 353 }, 354 { 355 ID: "container-id-3", 356 WorkerName: "worker-name-2", 357 PipelineName: "pipeline-name-2", 358 JobName: "some-job", 359 BuildName: "2", 360 BuildID: 13, 361 StepName: "some-output", 362 Type: "task", 363 Attempt: "1", 364 User: user, 365 State: atc.ContainerStateCreated, 366 }, 367 { 368 ID: "container-id-4", 369 WorkerName: "worker-name-2", 370 PipelineName: "pipeline-name-2", 371 ResourceName: "banana", 372 User: user, 373 Type: "check", 374 State: atc.ContainerStateCreated, 375 }, 376 } 377 expectedQueryParams = append([]string{}, "pipeline_name=pipeline-name-1", "job_name=some-job") 378 }) 379 380 JustBeforeEach(func() { 381 atcServer.AppendHandlers( 382 ghttp.CombineHandlers( 383 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", strings.Join(expectedQueryParams, "&")), 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 = []string{} 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", strings.Join(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 = append(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 It("hijacks the given check container by URL", func() { 660 hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/a-pipeline/resources/some-resource-name") 661 }) 662 663 Context("and with pipeline instance is specified", func() { 664 BeforeEach(func() { 665 containerArguments = append(containerArguments, "instance_vars=%7B%22branch%22%3A%22master%22%7D") 666 }) 667 668 It("can accept the check resources name and a pipeline", func() { 669 hijack("--check", "a-pipeline/branch:master/some-resource-name") 670 }) 671 672 It("hijacks the given check container by URL", func() { 673 hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/a-pipeline/resources/some-resource-name"+"?instance_vars=%7B%22branch%22%3A%22master%22%7D") 674 }) 675 }) 676 }) 677 }) 678 679 Context("when the team is 'other'", func() { 680 BeforeEach(func() { 681 hijackTeamName = "other" 682 683 atcServer.AppendHandlers( 684 ghttp.CombineHandlers( 685 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 686 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 687 ) 688 }) 689 690 Context("and with pipeline specified", func() { 691 It("can accept the check resources name and a pipeline", func() { 692 hijack("--check", "a-pipeline/some-resource-name", "--team", hijackTeamName) 693 }) 694 }) 695 696 Context("and with url specified", func() { 697 It("hijacks the given check container by URL", func() { 698 hijack("--url", atcServer.URL()+"/teams/other/pipelines/a-pipeline/resources/some-resource-name", "--team", "other") 699 }) 700 }) 701 }) 702 }) 703 704 Context("when called with a specific build id", func() { 705 BeforeEach(func() { 706 containerArguments = append(containerArguments, "build_id=2", "step_name=some-step") 707 stepType = "task" 708 stepName = "some-step" 709 buildID = 2 710 }) 711 712 Context("when the team is 'main'", func() { 713 BeforeEach(func() { 714 hijackTeamName = "main" 715 }) 716 It("hijacks the most recent one-off build", func() { 717 hijack("-b", "2", "-s", "some-step") 718 }) 719 }) 720 721 Context("when the team is 'other'", func() { 722 BeforeEach(func() { 723 hijackTeamName = "other" 724 725 atcServer.AppendHandlers( 726 ghttp.CombineHandlers( 727 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 728 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 729 ) 730 }) 731 732 It("hijacks the most recent one-off build", func() { 733 hijack("-b", "2", "-s", "some-step", "--team", hijackTeamName) 734 }) 735 }) 736 }) 737 738 Context("when called with a specific job", func() { 739 BeforeEach(func() { 740 containerArguments = append(containerArguments, "pipeline_name=some-pipeline", "job_name=some-job", "step_name=some-step") 741 jobName = "some-job" 742 buildName = "3" 743 buildID = 13 744 stepType = "task" 745 stepName = "some-step" 746 }) 747 748 Context("hijacks the job's next build", func() { 749 Context("When the team is 'main'", func() { 750 BeforeEach(func() { 751 hijackTeamName = "main" 752 }) 753 754 It("hijacks the job's next build with '<pipeline>/<job>'", func() { 755 hijack("--job", "some-pipeline/some-job", "--step", "some-step") 756 }) 757 758 It("hijacks the job's next build when URL is specified", func() { 759 hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/some-pipeline/jobs/some-job", "--step", "some-step") 760 }) 761 762 Context("when pipeline instance is specified", func() { 763 BeforeEach(func() { 764 containerArguments = append(containerArguments, "instance_vars=%7B%22branch%22%3A%22master%22%7D") 765 }) 766 767 It("hijacks the job's next build with '<pipeline>/<instance_vars>/<job>'", func() { 768 hijack("--job", "some-pipeline/branch:master/some-job", "--step", "some-step") 769 }) 770 771 It("hijacks the job's next build when URL is specified", func() { 772 hijack("--url", atcServer.URL()+"/teams/"+teamName+"/pipelines/some-pipeline/jobs/some-job"+"?instance_vars=%7B%22branch%22%3A%22master%22%7D", "--step", "some-step") 773 }) 774 }) 775 776 }) 777 778 Context("When the team is 'other'", func() { 779 BeforeEach(func() { 780 hijackTeamName = "other" 781 782 atcServer.AppendHandlers( 783 ghttp.CombineHandlers( 784 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 785 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 786 ) 787 }) 788 It("hijacks the job's next build with 'pipelineName/jobName'", func() { 789 hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--team", hijackTeamName) 790 }) 791 792 It("hijacks the job's next build when URL is specified", func() { 793 hijack("--url", atcServer.URL()+"/teams/other/pipelines/some-pipeline/jobs/some-job", "--step", "some-step", "--team", hijackTeamName) 794 }) 795 }) 796 797 }) 798 799 Context("with a specific build of the job", func() { 800 BeforeEach(func() { 801 containerArguments = append(containerArguments, "build_name=3") 802 }) 803 804 Context("When the team is 'main'", func() { 805 BeforeEach(func() { 806 hijackTeamName = "main" 807 }) 808 It("hijacks the given build", func() { 809 hijack("--job", "some-pipeline/some-job", "--build", "3", "--step", "some-step") 810 }) 811 It("hijacks the given build with URL", func() { 812 hijack("--url", atcServer.URL()+"/teams/main/pipelines/some-pipeline/jobs/some-job/builds/3", "--step", "some-step") 813 }) 814 }) 815 816 Context("When the team is 'other'", func() { 817 BeforeEach(func() { 818 hijackTeamName = "other" 819 820 atcServer.AppendHandlers( 821 ghttp.CombineHandlers( 822 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 823 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 824 ) 825 }) 826 It("hijacks the given build", func() { 827 hijack("--job", "some-pipeline/some-job", "--build", "3", "--step", "some-step", "--team", hijackTeamName) 828 }) 829 It("hijacks the given build with URL", func() { 830 hijack("--url", atcServer.URL()+"/teams/other/pipelines/some-pipeline/jobs/some-job/builds/3", "--step", "some-step", "--team", hijackTeamName) 831 }) 832 }) 833 834 }) 835 }) 836 837 Context("when called with a specific attempt number", func() { 838 BeforeEach(func() { 839 containerArguments = append(containerArguments, "pipeline_name=some-pipeline", "job_name=some-job", "step_name=some-step", "attempt=2.4") 840 jobName = "some-job" 841 buildName = "3" 842 buildID = 13 843 stepType = "task" 844 stepName = "some-step" 845 attempt = "2.4" 846 }) 847 848 Context("When the team is 'main'", func() { 849 BeforeEach(func() { 850 hijackTeamName = "main" 851 }) 852 It("hijacks the job's next build", func() { 853 hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--attempt", "2.4") 854 }) 855 }) 856 857 Context("When the team is 'other'", func() { 858 BeforeEach(func() { 859 hijackTeamName = "other" 860 861 atcServer.AppendHandlers( 862 ghttp.CombineHandlers( 863 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 864 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 865 ) 866 }) 867 It("hijacks the job's next build", func() { 868 hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--attempt", "2.4", "--team", hijackTeamName) 869 }) 870 }) 871 }) 872 873 Context("when called with a step type", func() { 874 BeforeEach(func() { 875 containerArguments = append(containerArguments, "pipeline_name=some-pipeline", "job_name=some-job", "step_name=some-step", "type=put") 876 jobName = "some-job" 877 buildName = "3" 878 buildID = 13 879 stepType = "put" 880 stepName = "some-step" 881 attempt = "" 882 }) 883 884 It("hijacks the job's next build", func() { 885 hijack("--job", "some-pipeline/some-job", "--step", "some-step", "--step-type", "put") 886 }) 887 }) 888 889 Context("when called with a specific path and args", func() { 890 BeforeEach(func() { 891 path = "sh" 892 args = []string{"echo hello"} 893 894 containerArguments = append(containerArguments, "build_id=2", "step_name=some-step") 895 stepType = "task" 896 stepName = "some-step" 897 buildID = 2 898 }) 899 900 It("hijacks and runs the provided path with args", func() { 901 hijack("-b", "2", "-s", "some-step", "sh", "echo hello") 902 }) 903 }) 904 905 Context("when hijacking yields an error", func() { 906 BeforeEach(func() { 907 resourceName = "some-resource-name" 908 containerArguments = append(containerArguments, "type=check", "resource_name=some-resource-name", "pipeline_name=a-pipeline") 909 hijackHandlerError = []string{"something went wrong"} 910 }) 911 912 It("prints it to stderr and exits 255", func() { 913 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--check", "a-pipeline/some-resource-name") 914 915 stdin, err := flyCmd.StdinPipe() 916 Expect(err).NotTo(HaveOccurred()) 917 918 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 919 Expect(err).NotTo(HaveOccurred()) 920 921 Eventually(hijacked).Should(BeClosed()) 922 923 _, err = fmt.Fprintf(stdin, "some stdin") 924 Expect(err).NotTo(HaveOccurred()) 925 926 Eventually(sess.Err.Contents).Should(ContainSubstring(ansi.Color("something went wrong", "red+b") + "\n")) 927 928 err = stdin.Close() 929 Expect(err).NotTo(HaveOccurred()) 930 931 <-sess.Exited 932 Expect(sess.ExitCode()).To(Equal(255)) 933 }) 934 }) 935 }) 936 937 Context("when hijacking a specific container", func() { 938 var ( 939 hijackHandlerError []string 940 statusCode int 941 id string 942 hijackTeamName string 943 ) 944 945 BeforeEach(func() { 946 hijackHandlerError = nil 947 statusCode = 0 948 id = "" 949 hijackTeamName = "main" 950 951 }) 952 953 JustBeforeEach(func() { 954 atcServer.AppendHandlers( 955 ghttp.CombineHandlers( 956 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName+"/containers/container-id"), 957 ghttp.RespondWithJSONEncoded(statusCode, atc.Container{ 958 ID: id, 959 User: user, 960 }), 961 ), 962 ) 963 }) 964 965 Context("when container exists", func() { 966 BeforeEach(func() { 967 statusCode = 200 968 id = "container-id" 969 }) 970 971 Context("when hijack returns no error", func() { 972 JustBeforeEach(func() { 973 didHijack := make(chan struct{}) 974 hijacked = didHijack 975 atcServer.AppendHandlers( 976 hijackHandler("container-id", didHijack, hijackHandlerError, hijackTeamName), 977 ) 978 }) 979 Context("when the team is 'main'", func() { 980 BeforeEach(func() { 981 hijackTeamName = "main" 982 }) 983 It("should hijack container with associated handle", func() { 984 hijack("--handle", "container-id") 985 }) 986 }) 987 988 Context("when the team is 'other'", func() { 989 BeforeEach(func() { 990 hijackTeamName = "other" 991 992 atcServer.AppendHandlers( 993 ghttp.CombineHandlers( 994 ghttp.VerifyRequest("GET", "/api/v1/teams/"+hijackTeamName), 995 ghttp.RespondWithJSONEncoded(http.StatusOK, atc.Team{Name: hijackTeamName})), 996 ) 997 }) 998 It("should hijack container with associated handle to 'other' team", func() { 999 hijack("--handle", "container-id", "--team", hijackTeamName) 1000 }) 1001 }) 1002 }) 1003 1004 Context("when hijack returns error", func() { 1005 JustBeforeEach(func() { 1006 atcServer.AppendHandlers( 1007 ghttp.CombineHandlers( 1008 ghttp.VerifyRequest("GET", fmt.Sprintf("/api/v1/teams/main/containers/%s/hijack", id)), 1009 ghttp.RespondWithJSONEncoded(403, nil), 1010 ), 1011 ) 1012 }) 1013 1014 It("should print out response status and error", func() { 1015 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--handle", "container-id") 1016 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1017 Expect(err).NotTo(HaveOccurred()) 1018 1019 Eventually(sess).Should(gexec.Exit(1)) 1020 1021 Expect(sess.Err).To(gbytes.Say("error: 403 Forbidden websocket: bad handshake")) 1022 }) 1023 }) 1024 }) 1025 1026 Context("when container does not exist", func() { 1027 BeforeEach(func() { 1028 statusCode = 404 1029 }) 1030 1031 JustBeforeEach(func() { 1032 didHijack := make(chan struct{}) 1033 hijacked = didHijack 1034 atcServer.AppendHandlers( 1035 hijackHandler("container-id", didHijack, hijackHandlerError, hijackTeamName), 1036 ) 1037 }) 1038 1039 It("should return an appropriate error message", func() { 1040 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--handle", "container-id") 1041 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1042 Expect(err).NotTo(HaveOccurred()) 1043 1044 Eventually(sess).Should(gexec.Exit(1)) 1045 1046 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")) 1047 }) 1048 }) 1049 }) 1050 1051 Context("when passing a URL that doesn't match the target", func() { 1052 It("errors out when wrong team is specified", func() { 1053 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--url", atcServer.URL()+"/teams/wrongteam/pipelines/a-pipeline/resources/some-resource-name") 1054 1055 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1056 Expect(err).NotTo(HaveOccurred()) 1057 1058 Eventually(sess.Err.Contents).Should(ContainSubstring("Team in URL doesn't match the current team of the target")) 1059 1060 <-sess.Exited 1061 Expect(sess.ExitCode()).ToNot(Equal(0)) 1062 }) 1063 1064 It("errors out when wrong URL is specified", func() { 1065 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--url", "http://wrong.example.com/teams/"+teamName+"/pipelines/a-pipeline/resources/some-resource-name") 1066 1067 sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter) 1068 Expect(err).NotTo(HaveOccurred()) 1069 1070 Eventually(sess.Err.Contents).Should(ContainSubstring("URL doesn't match that of target")) 1071 1072 <-sess.Exited 1073 Expect(sess.ExitCode()).ToNot(Equal(0)) 1074 }) 1075 }) 1076 1077 Context("when hijacking yields an executable not found error", func() { 1078 var hijacked2 <-chan struct{} 1079 JustBeforeEach(func() { 1080 didHijack := make(chan struct{}) 1081 hijacked = didHijack 1082 didHijack2 := make(chan struct{}) 1083 hijacked2 = didHijack2 1084 atcServer.AppendHandlers( 1085 ghttp.CombineHandlers( 1086 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers", "type=check&resource_name=some-resource-name&pipeline_name=a-pipeline"), 1087 ghttp.RespondWithJSONEncoded(200, []atc.Container{ 1088 {ID: "container-id-1", State: atc.ContainerStateCreated, WorkerName: "some-worker", PipelineName: "a-pipeline", JobName: "", BuildName: "", BuildID: 0, Type: "", StepName: "", ResourceName: "some-resource-name", Attempt: "", User: user}, 1089 }), 1090 ), 1091 ghttp.CombineHandlers( 1092 ghttp.VerifyRequest("GET", "/api/v1/teams/main/containers/container-id-1/hijack"), 1093 func(w http.ResponseWriter, r *http.Request) { 1094 defer GinkgoRecover() 1095 1096 conn, err := upgrader.Upgrade(w, r, nil) 1097 Expect(err).NotTo(HaveOccurred()) 1098 1099 defer conn.Close() 1100 1101 close(didHijack) 1102 1103 var processSpec atc.HijackProcessSpec 1104 err = conn.ReadJSON(&processSpec) 1105 Expect(err).NotTo(HaveOccurred()) 1106 1107 Expect(processSpec.User).To(Equal(user)) 1108 Expect(processSpec.Dir).To(Equal(workingDirectory)) 1109 Expect(processSpec.Path).To(Equal("bash")) 1110 Expect(processSpec.Args).To(Equal(args)) 1111 1112 err = conn.WriteJSON(atc.HijackOutput{ 1113 ExecutableNotFound: true, 1114 }) 1115 Expect(err).NotTo(HaveOccurred()) 1116 1117 err = conn.WriteJSON(atc.HijackOutput{ 1118 Error: "executable not found", 1119 }) 1120 Expect(err).NotTo(HaveOccurred()) 1121 }, 1122 ), 1123 hijackHandler("container-id-1", didHijack2, nil, "main"), 1124 ) 1125 }) 1126 1127 Context("when a path was not specified", func() { 1128 BeforeEach(func() { 1129 path = "sh" 1130 }) 1131 It("tries \"bash\" then \"sh\"", func() { 1132 os.Stdout.WriteString("\n") 1133 flyCmd := exec.Command(flyPath, "-t", targetName, "hijack", "--check", "a-pipeline/some-resource-name") 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(hijacked).Should(BeClosed()) 1142 1143 Eventually(sess.Err.Contents).Should(ContainSubstring(ansi.Color("executable not found", "red+b") + "\n")) 1144 Eventually(sess.Err.Contents).Should(ContainSubstring("Couldn't find \"bash\" on container, retrying with \"sh\"")) 1145 1146 Eventually(hijacked2).Should(BeClosed()) 1147 1148 _, err = fmt.Fprintf(stdin, "some stdin") 1149 Expect(err).NotTo(HaveOccurred()) 1150 1151 Eventually(sess.Out).Should(gbytes.Say("some stdout")) 1152 Eventually(sess.Err).Should(gbytes.Say("some stderr")) 1153 1154 err = stdin.Close() 1155 Expect(err).NotTo(HaveOccurred()) 1156 1157 <-sess.Exited 1158 Expect(sess.ExitCode()).To(Equal(123)) 1159 }) 1160 }) 1161 }) 1162 })