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 })