github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/task_step_test.go (about) 1 package exec_test 2 3 import ( 4 "context" 5 "errors" 6 7 "code.cloudfoundry.org/lager" 8 "github.com/pf-qiu/concourse/v6/atc" 9 "github.com/pf-qiu/concourse/v6/atc/db" 10 "github.com/pf-qiu/concourse/v6/atc/db/lock/lockfakes" 11 "github.com/pf-qiu/concourse/v6/atc/exec" 12 "github.com/pf-qiu/concourse/v6/atc/exec/build" 13 "github.com/pf-qiu/concourse/v6/atc/exec/execfakes" 14 "github.com/pf-qiu/concourse/v6/atc/runtime" 15 "github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes" 16 "github.com/pf-qiu/concourse/v6/atc/worker" 17 "github.com/pf-qiu/concourse/v6/atc/worker/workerfakes" 18 "github.com/pf-qiu/concourse/v6/tracing" 19 "github.com/pf-qiu/concourse/v6/vars" 20 "github.com/onsi/gomega/gbytes" 21 "go.opentelemetry.io/otel/api/trace" 22 "go.opentelemetry.io/otel/api/trace/tracetest" 23 24 . "github.com/onsi/ginkgo" 25 . "github.com/onsi/gomega" 26 ) 27 28 var _ = Describe("TaskStep", func() { 29 var ( 30 ctx context.Context 31 cancel func() 32 33 stdoutBuf *gbytes.Buffer 34 stderrBuf *gbytes.Buffer 35 36 fakeClient *workerfakes.FakeClient 37 fakeStrategy *workerfakes.FakeContainerPlacementStrategy 38 39 fakeLockFactory *lockfakes.FakeLockFactory 40 41 spanCtx context.Context 42 fakeDelegate *execfakes.FakeTaskDelegate 43 44 fakeDelegateFactory *execfakes.FakeTaskDelegateFactory 45 46 taskPlan *atc.TaskPlan 47 48 repo *build.Repository 49 state *execfakes.FakeRunState 50 childState *execfakes.FakeRunState 51 52 taskStep exec.Step 53 stepOk bool 54 stepErr error 55 56 containerMetadata = db.ContainerMetadata{ 57 WorkingDirectory: "some-artifact-root", 58 Type: db.ContainerTypeTask, 59 StepName: "some-step", 60 } 61 62 stepMetadata = exec.StepMetadata{ 63 TeamID: 123, 64 BuildID: 1234, 65 JobID: 12345, 66 } 67 68 planID = atc.PlanID("42") 69 ) 70 71 BeforeEach(func() { 72 ctx, cancel = context.WithCancel(context.Background()) 73 74 stdoutBuf = gbytes.NewBuffer() 75 stderrBuf = gbytes.NewBuffer() 76 77 fakeClient = new(workerfakes.FakeClient) 78 fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy) 79 80 fakeLockFactory = new(lockfakes.FakeLockFactory) 81 82 fakeDelegate = new(execfakes.FakeTaskDelegate) 83 fakeDelegate.StdoutReturns(stdoutBuf) 84 fakeDelegate.StderrReturns(stderrBuf) 85 86 spanCtx = context.Background() 87 fakeDelegate.StartSpanReturns(spanCtx, trace.NoopSpan{}) 88 89 fakeDelegateFactory = new(execfakes.FakeTaskDelegateFactory) 90 fakeDelegateFactory.TaskDelegateReturns(fakeDelegate) 91 92 repo = build.NewRepository() 93 state = new(execfakes.FakeRunState) 94 state.ArtifactRepositoryReturns(repo) 95 96 childState = new(execfakes.FakeRunState) 97 childState.ArtifactRepositoryReturns(repo.NewLocalScope()) 98 state.NewLocalScopeReturns(childState) 99 100 state.GetStub = vars.StaticVariables{"source-param": "super-secret-source"}.Get 101 102 taskPlan = &atc.TaskPlan{ 103 Name: "some-task", 104 Privileged: false, 105 VersionedResourceTypes: atc.VersionedResourceTypes{ 106 { 107 ResourceType: atc.ResourceType{ 108 Name: "custom-resource", 109 Type: "custom-type", 110 Source: atc.Source{"some-custom": "((source-param))"}, 111 Params: atc.Params{"some-custom": "param"}, 112 }, 113 Version: atc.Version{"some-custom": "version"}, 114 }, 115 }, 116 } 117 }) 118 119 JustBeforeEach(func() { 120 plan := atc.Plan{ 121 ID: planID, 122 Task: taskPlan, 123 } 124 125 // stuff stored on task step still 126 taskStep = exec.NewTaskStep( 127 plan.ID, 128 *plan.Task, 129 atc.ContainerLimits{}, 130 stepMetadata, 131 containerMetadata, 132 fakeStrategy, 133 fakeClient, 134 fakeDelegateFactory, 135 fakeLockFactory, 136 ) 137 138 stepOk, stepErr = taskStep.Run(ctx, state) 139 }) 140 141 Context("when the plan has a config", func() { 142 BeforeEach(func() { 143 cpu := atc.CPULimit(1024) 144 memory := atc.MemoryLimit(1024) 145 146 taskPlan.Config = &atc.TaskConfig{ 147 Platform: "some-platform", 148 Limits: &atc.ContainerLimits{ 149 CPU: &cpu, 150 Memory: &memory, 151 }, 152 Params: atc.TaskEnv{ 153 "SECURE": "secret-task-param", 154 }, 155 Run: atc.TaskRunConfig{ 156 Path: "ls", 157 Args: []string{"some", "args"}, 158 }, 159 } 160 }) 161 162 Context("before running the task container", func() { 163 BeforeEach(func() { 164 fakeDelegate.InitializingStub = func(lager.Logger) { 165 defer GinkgoRecover() 166 Expect(fakeClient.RunTaskStepCallCount()).To(BeZero()) 167 } 168 }) 169 170 It("invokes the delegate's Initializing callback", func() { 171 Expect(fakeDelegate.InitializingCallCount()).To(Equal(1)) 172 }) 173 }) 174 175 It("creates a containerSpec with the correct parameters", func() { 176 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 177 178 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 179 180 Expect(containerSpec.Dir).To(Equal("some-artifact-root")) 181 Expect(containerSpec.User).To(BeEmpty()) 182 }) 183 184 It("creates the task process spec with the correct parameters", func() { 185 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 186 187 _, _, _, _, _, _, _, taskProcessSpec, _, _ := fakeClient.RunTaskStepArgsForCall(0) 188 Expect(taskProcessSpec.StdoutWriter).To(Equal(stdoutBuf)) 189 Expect(taskProcessSpec.StderrWriter).To(Equal(stderrBuf)) 190 Expect(taskProcessSpec.Path).To(Equal("ls")) 191 Expect(taskProcessSpec.Args).To(Equal([]string{"some", "args"})) 192 }) 193 194 It("sets the config on the TaskDelegate", func() { 195 Expect(fakeDelegate.SetTaskConfigCallCount()).To(Equal(1)) 196 actualTaskConfig := fakeDelegate.SetTaskConfigArgsForCall(0) 197 Expect(actualTaskConfig).To(Equal(*taskPlan.Config)) 198 }) 199 200 Context("when privileged", func() { 201 BeforeEach(func() { 202 taskPlan.Privileged = true 203 }) 204 205 It("marks the container's image spec as privileged", func() { 206 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 207 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 208 Expect(containerSpec.ImageSpec.Privileged).To(BeTrue()) 209 }) 210 }) 211 212 Context("when tags are configured", func() { 213 BeforeEach(func() { 214 taskPlan.Tags = atc.Tags{"plan", "tags"} 215 }) 216 217 It("creates a worker spec with the tags", func() { 218 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 219 220 _, _, _, _, workerSpec, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 221 Expect(workerSpec.Tags).To(Equal([]string{"plan", "tags"})) 222 }) 223 }) 224 225 Context("when rootfs uri is set instead of image resource", func() { 226 BeforeEach(func() { 227 taskPlan.Config.RootfsURI = "some-image" 228 }) 229 230 It("correctly sets up the image spec", func() { 231 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 232 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 233 234 Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 235 ImageURL: "some-image", 236 Privileged: false, 237 })) 238 }) 239 }) 240 241 Context("when tracing is enabled", func() { 242 var buildSpan trace.Span 243 244 BeforeEach(func() { 245 tracing.ConfigureTraceProvider(tracetest.NewProvider()) 246 247 spanCtx, buildSpan = tracing.StartSpan(ctx, "build", nil) 248 fakeDelegate.StartSpanReturns(spanCtx, buildSpan) 249 }) 250 251 AfterEach(func() { 252 tracing.Configured = false 253 }) 254 255 It("propagates span context to the worker client", func() { 256 runCtx, _, _, _, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 257 Expect(runCtx).To(Equal(spanCtx)) 258 }) 259 260 It("populates the TRACEPARENT env var", func() { 261 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 262 Expect(containerSpec.Env).To(ContainElement(MatchRegexp(`TRACEPARENT=.+`))) 263 }) 264 }) 265 266 Context("when the configuration specifies paths for inputs", func() { 267 var inputArtifact *runtimefakes.FakeArtifact 268 var otherInputArtifact *runtimefakes.FakeArtifact 269 270 BeforeEach(func() { 271 inputArtifact = new(runtimefakes.FakeArtifact) 272 otherInputArtifact = new(runtimefakes.FakeArtifact) 273 274 taskPlan.Config = &atc.TaskConfig{ 275 Platform: "some-platform", 276 RootfsURI: "some-image", 277 Params: map[string]string{"SOME": "params"}, 278 Run: atc.TaskRunConfig{ 279 Path: "ls", 280 Args: []string{"some", "args"}, 281 }, 282 Inputs: []atc.TaskInputConfig{ 283 {Name: "some-input", Path: "some-input-configured-path"}, 284 {Name: "some-other-input"}, 285 }, 286 } 287 }) 288 289 Context("when all inputs are present", func() { 290 BeforeEach(func() { 291 repo.RegisterArtifact("some-input", inputArtifact) 292 repo.RegisterArtifact("some-other-input", otherInputArtifact) 293 }) 294 295 It("configures the inputs for the containerSpec correctly", func() { 296 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 297 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 298 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(2)) 299 Expect(actualContainerSpec.ArtifactByPath["some-artifact-root/some-input-configured-path"]).To(Equal(inputArtifact)) 300 Expect(actualContainerSpec.ArtifactByPath["some-artifact-root/some-other-input"]).To(Equal(otherInputArtifact)) 301 }) 302 }) 303 304 Context("when any of the inputs are missing", func() { 305 BeforeEach(func() { 306 repo.RegisterArtifact("some-input", inputArtifact) 307 }) 308 309 It("returns a MissingInputsError", func() { 310 Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{})) 311 Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("some-other-input")) 312 }) 313 }) 314 }) 315 316 Context("when input is remapped", func() { 317 var remappedInputArtifact *runtimefakes.FakeArtifact 318 319 BeforeEach(func() { 320 remappedInputArtifact = new(runtimefakes.FakeArtifact) 321 taskPlan.InputMapping = map[string]string{"remapped-input": "remapped-input-src"} 322 taskPlan.Config = &atc.TaskConfig{ 323 Platform: "some-platform", 324 Run: atc.TaskRunConfig{ 325 Path: "ls", 326 Args: []string{"some", "args"}, 327 }, 328 Inputs: []atc.TaskInputConfig{ 329 {Name: "remapped-input"}, 330 }, 331 } 332 }) 333 334 Context("when all inputs are present in the in artifact repository", func() { 335 BeforeEach(func() { 336 repo.RegisterArtifact("remapped-input-src", remappedInputArtifact) 337 }) 338 339 It("uses remapped input", func() { 340 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 341 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 342 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(1)) 343 Expect(actualContainerSpec.ArtifactByPath["some-artifact-root/remapped-input"]).To(Equal(remappedInputArtifact)) 344 Expect(stepErr).ToNot(HaveOccurred()) 345 }) 346 }) 347 348 Context("when any of the inputs are missing", func() { 349 It("returns a MissingInputsError", func() { 350 Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{})) 351 Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("remapped-input-src")) 352 }) 353 }) 354 }) 355 356 Context("when some inputs are optional", func() { 357 var ( 358 optionalInputArtifact, optionalInput2Artifact, requiredInputArtifact *runtimefakes.FakeArtifact 359 ) 360 361 BeforeEach(func() { 362 optionalInputArtifact = new(runtimefakes.FakeArtifact) 363 optionalInput2Artifact = new(runtimefakes.FakeArtifact) 364 requiredInputArtifact = new(runtimefakes.FakeArtifact) 365 taskPlan.Config = &atc.TaskConfig{ 366 Platform: "some-platform", 367 Run: atc.TaskRunConfig{ 368 Path: "ls", 369 }, 370 Inputs: []atc.TaskInputConfig{ 371 {Name: "optional-input", Optional: true}, 372 {Name: "optional-input-2", Optional: true}, 373 {Name: "required-input"}, 374 }, 375 } 376 }) 377 378 Context("when an optional input is missing", func() { 379 BeforeEach(func() { 380 repo.RegisterArtifact("required-input", requiredInputArtifact) 381 repo.RegisterArtifact("optional-input-2", optionalInput2Artifact) 382 }) 383 384 It("runs successfully without the optional input", func() { 385 Expect(stepErr).ToNot(HaveOccurred()) 386 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 387 _, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 388 Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(2)) 389 Expect(actualContainerSpec.ArtifactByPath["some-artifact-root/required-input"]).To(Equal(optionalInputArtifact)) 390 Expect(actualContainerSpec.ArtifactByPath["some-artifact-root/optional-input-2"]).To(Equal(optionalInput2Artifact)) 391 }) 392 }) 393 394 Context("when a required input is missing", func() { 395 BeforeEach(func() { 396 repo.RegisterArtifact("optional-input", optionalInputArtifact) 397 repo.RegisterArtifact("optional-input-2", optionalInput2Artifact) 398 }) 399 400 It("returns a MissingInputsError", func() { 401 Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{})) 402 Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("required-input")) 403 }) 404 }) 405 }) 406 407 Context("when the configuration specifies paths for caches", func() { 408 var ( 409 fakeVolume1 *workerfakes.FakeVolume 410 fakeVolume2 *workerfakes.FakeVolume 411 ) 412 413 BeforeEach(func() { 414 taskPlan.Config = &atc.TaskConfig{ 415 Platform: "some-platform", 416 RootfsURI: "some-image", 417 Run: atc.TaskRunConfig{ 418 Path: "ls", 419 }, 420 Caches: []atc.TaskCacheConfig{ 421 {Path: "some-path-1"}, 422 {Path: "some-path-2"}, 423 }, 424 } 425 426 fakeVolume1 = new(workerfakes.FakeVolume) 427 fakeVolume2 = new(workerfakes.FakeVolume) 428 taskResult := worker.TaskResult{ 429 ExitStatus: 0, 430 VolumeMounts: []worker.VolumeMount{ 431 { 432 Volume: fakeVolume1, 433 MountPath: "some-artifact-root/some-path-1", 434 }, 435 { 436 Volume: fakeVolume2, 437 MountPath: "some-artifact-root/some-path-2", 438 }, 439 }, 440 } 441 fakeClient.RunTaskStepReturns(taskResult, nil) 442 }) 443 444 It("creates the containerSpec with the caches in the inputs", func() { 445 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 446 Expect(containerSpec.ArtifactByPath).To(HaveLen(2)) 447 Expect(containerSpec.ArtifactByPath["some-artifact-root/some-path-1"]).ToNot(BeNil()) 448 Expect(containerSpec.ArtifactByPath["some-artifact-root/some-path-2"]).ToNot(BeNil()) 449 }) 450 451 Context("when task belongs to a job", func() { 452 BeforeEach(func() { 453 stepMetadata.JobID = 12 454 }) 455 456 It("registers cache volumes as task caches", func() { 457 Expect(stepErr).ToNot(HaveOccurred()) 458 459 Expect(fakeVolume1.InitializeTaskCacheCallCount()).To(Equal(1)) 460 _, jID, stepName, cachePath, p := fakeVolume1.InitializeTaskCacheArgsForCall(0) 461 Expect(jID).To(Equal(stepMetadata.JobID)) 462 Expect(stepName).To(Equal("some-task")) 463 Expect(cachePath).To(Equal("some-path-1")) 464 Expect(p).To(Equal(bool(taskPlan.Privileged))) 465 466 Expect(fakeVolume2.InitializeTaskCacheCallCount()).To(Equal(1)) 467 _, jID, stepName, cachePath, p = fakeVolume2.InitializeTaskCacheArgsForCall(0) 468 Expect(jID).To(Equal(stepMetadata.JobID)) 469 Expect(stepName).To(Equal("some-task")) 470 Expect(cachePath).To(Equal("some-path-2")) 471 Expect(p).To(Equal(bool(taskPlan.Privileged))) 472 }) 473 }) 474 475 Context("when task does not belong to job (one-off build)", func() { 476 BeforeEach(func() { 477 stepMetadata.JobID = 0 478 }) 479 480 It("does not initialize caches", func() { 481 Expect(stepErr).ToNot(HaveOccurred()) 482 Expect(fakeVolume1.InitializeTaskCacheCallCount()).To(Equal(0)) 483 Expect(fakeVolume2.InitializeTaskCacheCallCount()).To(Equal(0)) 484 }) 485 }) 486 }) 487 488 Context("when the configuration specifies paths for outputs", func() { 489 BeforeEach(func() { 490 taskPlan.Config = &atc.TaskConfig{ 491 Platform: "some-platform", 492 RootfsURI: "some-image", 493 Params: map[string]string{"SOME": "params"}, 494 Run: atc.TaskRunConfig{ 495 Path: "ls", 496 Args: []string{"some", "args"}, 497 }, 498 Outputs: []atc.TaskOutputConfig{ 499 {Name: "some-output", Path: "some-output-configured-path"}, 500 {Name: "some-other-output"}, 501 {Name: "some-trailing-slash-output", Path: "some-output-configured-path-with-trailing-slash/"}, 502 }, 503 } 504 }) 505 506 It("configures them appropriately in the container spec", func() { 507 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 508 Expect(containerSpec.Outputs).To(Equal(worker.OutputPaths{ 509 "some-output": "some-artifact-root/some-output-configured-path/", 510 "some-other-output": "some-artifact-root/some-other-output/", 511 "some-trailing-slash-output": "some-artifact-root/some-output-configured-path-with-trailing-slash/", 512 })) 513 }) 514 }) 515 516 Context("when missing the platform", func() { 517 518 BeforeEach(func() { 519 taskPlan.Config.Platform = "" 520 }) 521 522 It("returns the error", func() { 523 Expect(stepErr).To(HaveOccurred()) 524 }) 525 526 It("is not successful", func() { 527 Expect(stepOk).To(BeFalse()) 528 }) 529 }) 530 531 Context("when missing the path to the executable", func() { 532 533 BeforeEach(func() { 534 taskPlan.Config.Run.Path = "" 535 }) 536 537 It("returns the error", func() { 538 Expect(stepErr).To(HaveOccurred()) 539 }) 540 541 It("is not successful", func() { 542 Expect(stepOk).To(BeFalse()) 543 }) 544 }) 545 546 Context("when an image artifact name is specified", func() { 547 BeforeEach(func() { 548 taskPlan.ImageArtifactName = "some-image-artifact" 549 }) 550 551 Context("when the image artifact is registered in the artifact repo", func() { 552 var imageArtifact *runtimefakes.FakeArtifact 553 554 BeforeEach(func() { 555 imageArtifact = new(runtimefakes.FakeArtifact) 556 repo.RegisterArtifact("some-image-artifact", imageArtifact) 557 }) 558 559 It("configures it in the containerSpec's ImageSpec", func() { 560 _, _, _, containerSpec, workerSpec, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 561 Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 562 ImageArtifact: imageArtifact, 563 })) 564 565 Expect(workerSpec.ResourceType).To(Equal("")) 566 }) 567 568 Describe("when task config specifies image and/or image resource as well as image artifact", func() { 569 Context("when streaming the metadata from the worker succeeds", func() { 570 571 JustBeforeEach(func() { 572 Expect(stepErr).ToNot(HaveOccurred()) 573 }) 574 575 Context("when the task config also specifies image", func() { 576 BeforeEach(func() { 577 taskPlan.Config = &atc.TaskConfig{ 578 Platform: "some-platform", 579 RootfsURI: "some-image", 580 Params: map[string]string{"SOME": "params"}, 581 Run: atc.TaskRunConfig{ 582 Path: "ls", 583 Args: []string{"some", "args"}, 584 }, 585 } 586 }) 587 588 It("still uses the image artifact", func() { 589 _, _, _, containerSpec, workerSpec, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 590 Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 591 ImageArtifact: imageArtifact, 592 })) 593 594 Expect(workerSpec.ResourceType).To(Equal("")) 595 }) 596 }) 597 598 Context("when the task config also specifies image_resource", func() { 599 BeforeEach(func() { 600 taskPlan.Config = &atc.TaskConfig{ 601 Platform: "some-platform", 602 ImageResource: &atc.ImageResource{ 603 Type: "docker", 604 Source: atc.Source{"some": "super-secret-source"}, 605 Params: atc.Params{"some": "params"}, 606 Version: atc.Version{"some": "version"}, 607 }, 608 Params: map[string]string{"SOME": "params"}, 609 Run: atc.TaskRunConfig{ 610 Path: "ls", 611 Args: []string{"some", "args"}, 612 }, 613 } 614 }) 615 616 It("still uses the image artifact", func() { 617 _, _, _, containerSpec, workerSpec, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 618 Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 619 ImageArtifact: imageArtifact, 620 })) 621 622 Expect(workerSpec.ResourceType).To(Equal("")) 623 }) 624 }) 625 626 Context("when the task config also specifies image and image_resource", func() { 627 BeforeEach(func() { 628 taskPlan.Config = &atc.TaskConfig{ 629 Platform: "some-platform", 630 RootfsURI: "some-image", 631 ImageResource: &atc.ImageResource{ 632 Type: "docker", 633 Source: atc.Source{"some": "super-secret-source"}, 634 Params: atc.Params{"some": "params"}, 635 Version: atc.Version{"some": "version"}, 636 }, 637 Params: map[string]string{"SOME": "params"}, 638 Run: atc.TaskRunConfig{ 639 Path: "ls", 640 Args: []string{"some", "args"}, 641 }, 642 } 643 }) 644 645 It("still uses the image artifact", func() { 646 _, _, _, containerSpec, workerSpec, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 647 Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{ 648 ImageArtifact: imageArtifact, 649 })) 650 Expect(workerSpec.ResourceType).To(Equal("")) 651 }) 652 }) 653 }) 654 }) 655 }) 656 657 Context("when the image artifact is NOT registered in the artifact repo", func() { 658 It("returns a MissingTaskImageSourceError", func() { 659 Expect(stepErr).To(Equal(exec.MissingTaskImageSourceError{"some-image-artifact"})) 660 }) 661 662 It("is not successful", func() { 663 Expect(stepOk).To(BeFalse()) 664 }) 665 }) 666 }) 667 668 Context("when the image_resource is specified (even if RootfsURI is configured)", func() { 669 var fakeImageSpec worker.ImageSpec 670 671 BeforeEach(func() { 672 taskPlan.Config = &atc.TaskConfig{ 673 Platform: "some-platform", 674 RootfsURI: "some-image", 675 ImageResource: &atc.ImageResource{ 676 Type: "docker", 677 Source: atc.Source{"some": "super-secret-source"}, 678 Params: atc.Params{"some": "params"}, 679 }, 680 Params: map[string]string{"SOME": "params"}, 681 Run: atc.TaskRunConfig{ 682 Path: "ls", 683 Args: []string{"some", "args"}, 684 }, 685 } 686 687 fakeImageSpec = worker.ImageSpec{ 688 ImageArtifact: new(runtimefakes.FakeArtifact), 689 } 690 691 fakeDelegate.FetchImageReturns(fakeImageSpec, nil) 692 }) 693 694 It("succeeds", func() { 695 Expect(stepErr).ToNot(HaveOccurred()) 696 Expect(stepOk).To(BeTrue()) 697 }) 698 699 It("fetches the image", func() { 700 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 701 _, imageResource, types, privileged := fakeDelegate.FetchImageArgsForCall(0) 702 Expect(imageResource).To(Equal(atc.ImageResource{ 703 Type: "docker", 704 Source: atc.Source{"some": "super-secret-source"}, 705 Params: atc.Params{"some": "params"}, 706 })) 707 Expect(types).To(Equal(taskPlan.VersionedResourceTypes)) 708 Expect(privileged).To(BeFalse()) 709 }) 710 711 It("creates the specs with the image artifact", func() { 712 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 713 Expect(containerSpec.ImageSpec).To(Equal(fakeImageSpec)) 714 }) 715 716 Context("when tags are specified on the task plan", func() { 717 BeforeEach(func() { 718 taskPlan.Tags = atc.Tags{"plan", "tags"} 719 }) 720 721 It("fetches the image with the same tags", func() { 722 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 723 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 724 Expect(imageResource.Tags).To(Equal(atc.Tags{"plan", "tags"})) 725 }) 726 }) 727 728 Context("when tags are specified on the image resource", func() { 729 BeforeEach(func() { 730 taskPlan.Config.ImageResource.Tags = atc.Tags{"image", "tags"} 731 }) 732 733 It("fetches the image with the same tags", func() { 734 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 735 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 736 Expect(imageResource.Tags).To(Equal(atc.Tags{"image", "tags"})) 737 }) 738 739 Context("when tags are ALSO specified on the task plan", func() { 740 BeforeEach(func() { 741 taskPlan.Tags = atc.Tags{"plan", "tags"} 742 }) 743 744 It("fetches the image using only the image tags", func() { 745 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 746 _, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0) 747 Expect(imageResource.Tags).To(Equal(atc.Tags{"image", "tags"})) 748 }) 749 }) 750 }) 751 752 Context("when privileged", func() { 753 BeforeEach(func() { 754 taskPlan.Privileged = true 755 }) 756 757 It("fetches a privileged image", func() { 758 Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1)) 759 _, _, _, privileged := fakeDelegate.FetchImageArgsForCall(0) 760 Expect(privileged).To(BeTrue()) 761 }) 762 }) 763 }) 764 765 Context("when a run dir is specified", func() { 766 var dir string 767 BeforeEach(func() { 768 dir = "/some/dir" 769 taskPlan.Config.Run.Dir = dir 770 }) 771 772 It("specifies it in the process spec", func() { 773 _, _, _, _, _, _, _, processSpec, _, _ := fakeClient.RunTaskStepArgsForCall(0) 774 Expect(processSpec.Dir).To(Equal(dir)) 775 }) 776 }) 777 778 Context("when a run user is specified", func() { 779 BeforeEach(func() { 780 taskPlan.Config.Run.User = "some-user" 781 }) 782 783 It("adds the user to the container spec", func() { 784 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 785 Expect(containerSpec.User).To(Equal("some-user")) 786 }) 787 788 It("doesn't bother adding the user to the run spec", func() { 789 _, _, _, _, _, _, _, processSpec, _, _ := fakeClient.RunTaskStepArgsForCall(0) 790 Expect(processSpec.User).To(BeEmpty()) 791 }) 792 }) 793 794 Context("when running the task succeeds", func() { 795 var taskStepStatus int 796 BeforeEach(func() { 797 taskPlan.Config = &atc.TaskConfig{ 798 Platform: "some-platform", 799 RootfsURI: "some-image", 800 Params: map[string]string{"SOME": "params"}, 801 Run: atc.TaskRunConfig{ 802 Path: "ls", 803 Args: []string{"some", "args"}, 804 }, 805 Outputs: []atc.TaskOutputConfig{ 806 {Name: "some-output", Path: "some-output-configured-path"}, 807 {Name: "some-other-output"}, 808 {Name: "some-trailing-slash-output", Path: "some-output-configured-path-with-trailing-slash/"}, 809 }, 810 } 811 }) 812 813 It("returns successfully", func() { 814 Expect(stepErr).ToNot(HaveOccurred()) 815 }) 816 817 Context("when the task exits with zero status", func() { 818 BeforeEach(func() { 819 taskStepStatus = 0 820 taskResult := worker.TaskResult{ 821 ExitStatus: taskStepStatus, 822 VolumeMounts: []worker.VolumeMount{}, 823 } 824 fakeClient.RunTaskStepReturns(taskResult, nil) 825 }) 826 It("finishes the task via the delegate", func() { 827 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 828 _, status := fakeDelegate.FinishedArgsForCall(0) 829 Expect(status).To(Equal(exec.ExitStatus(taskStepStatus))) 830 }) 831 832 It("returns successfully", func() { 833 Expect(stepErr).ToNot(HaveOccurred()) 834 }) 835 836 Describe("the registered artifacts", func() { 837 var ( 838 artifact1 runtime.Artifact 839 artifact2 runtime.Artifact 840 artifact3 runtime.Artifact 841 842 fakeMountPath1 string = "some-artifact-root/some-output-configured-path/" 843 fakeMountPath2 string = "some-artifact-root/some-other-output/" 844 fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/" 845 846 fakeVolume1 *workerfakes.FakeVolume 847 fakeVolume2 *workerfakes.FakeVolume 848 fakeVolume3 *workerfakes.FakeVolume 849 ) 850 851 BeforeEach(func() { 852 853 fakeVolume1 = new(workerfakes.FakeVolume) 854 fakeVolume1.HandleReturns("some-handle-1") 855 fakeVolume2 = new(workerfakes.FakeVolume) 856 fakeVolume2.HandleReturns("some-handle-2") 857 fakeVolume3 = new(workerfakes.FakeVolume) 858 fakeVolume3.HandleReturns("some-handle-3") 859 860 fakeTaskResult := worker.TaskResult{ 861 ExitStatus: 0, 862 VolumeMounts: []worker.VolumeMount{ 863 { 864 Volume: fakeVolume1, 865 MountPath: fakeMountPath1, 866 }, 867 { 868 Volume: fakeVolume2, 869 MountPath: fakeMountPath2, 870 }, 871 { 872 Volume: fakeVolume3, 873 MountPath: fakeMountPath3, 874 }, 875 }, 876 } 877 fakeClient.RunTaskStepReturns(fakeTaskResult, nil) 878 }) 879 880 JustBeforeEach(func() { 881 Expect(stepErr).ToNot(HaveOccurred()) 882 883 var found bool 884 artifact1, found = repo.ArtifactFor("some-output") 885 Expect(found).To(BeTrue()) 886 887 artifact2, found = repo.ArtifactFor("some-other-output") 888 Expect(found).To(BeTrue()) 889 890 artifact3, found = repo.ArtifactFor("some-trailing-slash-output") 891 Expect(found).To(BeTrue()) 892 }) 893 894 It("does not register the task as a artifact", func() { 895 artifactMap := repo.AsMap() 896 Expect(artifactMap).To(ConsistOf(artifact1, artifact2, artifact3)) 897 }) 898 899 It("passes existing output volumes to the resource", func() { 900 _, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunTaskStepArgsForCall(0) 901 Expect(containerSpec.Outputs).To(Equal(worker.OutputPaths{ 902 "some-output": "some-artifact-root/some-output-configured-path/", 903 "some-other-output": "some-artifact-root/some-other-output/", 904 "some-trailing-slash-output": "some-artifact-root/some-output-configured-path-with-trailing-slash/", 905 })) 906 }) 907 }) 908 }) 909 910 Context("when the task exits with nonzero status", func() { 911 BeforeEach(func() { 912 taskStepStatus = 5 913 taskResult := worker.TaskResult{ExitStatus: taskStepStatus, VolumeMounts: []worker.VolumeMount{}} 914 fakeClient.RunTaskStepReturns(taskResult, nil) 915 }) 916 It("finishes the task via the delegate", func() { 917 Expect(fakeDelegate.FinishedCallCount()).To(Equal(1)) 918 _, status := fakeDelegate.FinishedArgsForCall(0) 919 Expect(status).To(Equal(exec.ExitStatus(taskStepStatus))) 920 }) 921 922 It("returns successfully", func() { 923 Expect(stepErr).ToNot(HaveOccurred()) 924 }) 925 }) 926 }) 927 928 Context("when running the task fails", func() { 929 disaster := errors.New("task run failed") 930 931 BeforeEach(func() { 932 taskResult := worker.TaskResult{ExitStatus: -1, VolumeMounts: []worker.VolumeMount{}} 933 fakeClient.RunTaskStepReturns(taskResult, disaster) 934 }) 935 936 It("returns the error", func() { 937 Expect(stepErr).To(Equal(disaster)) 938 }) 939 940 It("is not successful", func() { 941 Expect(stepOk).To(BeFalse()) 942 }) 943 }) 944 945 Context("when the task step is interrupted", func() { 946 BeforeEach(func() { 947 fakeClient.RunTaskStepReturns( 948 worker.TaskResult{ 949 ExitStatus: -1, 950 VolumeMounts: []worker.VolumeMount{}, 951 }, context.Canceled) 952 cancel() 953 }) 954 955 It("returns the context.Canceled error", func() { 956 Expect(stepErr).To(Equal(context.Canceled)) 957 }) 958 959 It("is not successful", func() { 960 Expect(stepOk).To(BeFalse()) 961 }) 962 963 It("waits for RunTaskStep to return", func() { 964 Expect(fakeClient.RunTaskStepCallCount()).To(Equal(1)) 965 }) 966 967 It("doesn't register a artifact", func() { 968 artifactMap := repo.AsMap() 969 Expect(artifactMap).To(BeEmpty()) 970 }) 971 }) 972 973 Context("when RunTaskStep returns volume mounts", func() { 974 var ( 975 fakeMountPath1 string = "some-artifact-root/some-output-configured-path/" 976 fakeMountPath2 string = "some-artifact-root/some-other-output/" 977 fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/" 978 979 fakeVolume1 *workerfakes.FakeVolume 980 fakeVolume2 *workerfakes.FakeVolume 981 fakeVolume3 *workerfakes.FakeVolume 982 983 runTaskStepError error 984 taskResult worker.TaskResult 985 ) 986 987 BeforeEach(func() { 988 taskPlan.Config = &atc.TaskConfig{ 989 Platform: "some-platform", 990 RootfsURI: "some-image", 991 Params: map[string]string{"SOME": "params"}, 992 Run: atc.TaskRunConfig{ 993 Path: "ls", 994 Args: []string{"some", "args"}, 995 }, 996 Outputs: []atc.TaskOutputConfig{ 997 {Name: "some-output", Path: "some-output-configured-path"}, 998 {Name: "some-other-output"}, 999 {Name: "some-trailing-slash-output", Path: "some-output-configured-path-with-trailing-slash/"}, 1000 }, 1001 } 1002 1003 fakeVolume1 = new(workerfakes.FakeVolume) 1004 fakeVolume1.HandleReturns("some-handle-1") 1005 fakeVolume2 = new(workerfakes.FakeVolume) 1006 fakeVolume2.HandleReturns("some-handle-2") 1007 fakeVolume3 = new(workerfakes.FakeVolume) 1008 fakeVolume3.HandleReturns("some-handle-3") 1009 1010 taskResult = worker.TaskResult{ 1011 ExitStatus: 0, 1012 VolumeMounts: []worker.VolumeMount{ 1013 { 1014 Volume: fakeVolume1, 1015 MountPath: fakeMountPath1, 1016 }, 1017 { 1018 Volume: fakeVolume2, 1019 MountPath: fakeMountPath2, 1020 }, 1021 { 1022 Volume: fakeVolume3, 1023 MountPath: fakeMountPath3, 1024 }, 1025 }, 1026 } 1027 }) 1028 1029 var outputsAreRegistered = func() { 1030 It("registers the outputs as artifacts", func() { 1031 artifact1, found := repo.ArtifactFor("some-output") 1032 Expect(found).To(BeTrue()) 1033 1034 artifact2, found := repo.ArtifactFor("some-other-output") 1035 Expect(found).To(BeTrue()) 1036 1037 artifact3, found := repo.ArtifactFor("some-trailing-slash-output") 1038 Expect(found).To(BeTrue()) 1039 1040 artifactMap := repo.AsMap() 1041 Expect(artifactMap).To(ConsistOf(artifact1, artifact2, artifact3)) 1042 }) 1043 1044 } 1045 1046 Context("when RunTaskStep succeeds", func() { 1047 BeforeEach(func() { 1048 runTaskStepError = nil 1049 fakeClient.RunTaskStepReturns(taskResult, runTaskStepError) 1050 }) 1051 outputsAreRegistered() 1052 }) 1053 1054 Context("when RunTaskStep returns a context Canceled error", func() { 1055 BeforeEach(func() { 1056 runTaskStepError = context.Canceled 1057 fakeClient.RunTaskStepReturns(taskResult, runTaskStepError) 1058 }) 1059 outputsAreRegistered() 1060 }) 1061 Context("when RunTaskStep returns a context DeadlineExceeded error", func() { 1062 BeforeEach(func() { 1063 runTaskStepError = context.DeadlineExceeded 1064 fakeClient.RunTaskStepReturns(taskResult, runTaskStepError) 1065 }) 1066 outputsAreRegistered() 1067 }) 1068 1069 Context("when RunTaskStep returns a unexpected error", func() { 1070 BeforeEach(func() { 1071 runTaskStepError = errors.New("some unexpected error") 1072 fakeClient.RunTaskStepReturns(taskResult, runTaskStepError) 1073 }) 1074 It("re-registers the outputs as artifacts", func() { 1075 artifactMap := repo.AsMap() 1076 Expect(artifactMap).To(BeEmpty()) 1077 }) 1078 1079 }) 1080 }) 1081 1082 Context("when output is remapped", func() { 1083 var ( 1084 fakeMountPath string = "some-artifact-root/generic-remapped-output/" 1085 ) 1086 1087 BeforeEach(func() { 1088 taskPlan.OutputMapping = map[string]string{"generic-remapped-output": "specific-remapped-output"} 1089 taskPlan.Config = &atc.TaskConfig{ 1090 Platform: "some-platform", 1091 Run: atc.TaskRunConfig{ 1092 Path: "ls", 1093 }, 1094 Outputs: []atc.TaskOutputConfig{ 1095 {Name: "generic-remapped-output"}, 1096 }, 1097 } 1098 1099 fakeVolume := new(workerfakes.FakeVolume) 1100 fakeVolume.HandleReturns("some-handle") 1101 1102 taskResult := worker.TaskResult{ 1103 ExitStatus: 0, 1104 VolumeMounts: []worker.VolumeMount{ 1105 { 1106 Volume: fakeVolume, 1107 MountPath: fakeMountPath, 1108 }, 1109 }, 1110 } 1111 fakeClient.RunTaskStepReturns(taskResult, nil) 1112 }) 1113 1114 JustBeforeEach(func() { 1115 Expect(stepErr).ToNot(HaveOccurred()) 1116 }) 1117 1118 It("registers the outputs as artifacts with specific name", func() { 1119 artifact, found := repo.ArtifactFor("specific-remapped-output") 1120 Expect(found).To(BeTrue()) 1121 1122 artifactMap := repo.AsMap() 1123 Expect(artifactMap).To(ConsistOf(artifact)) 1124 }) 1125 }) 1126 }) 1127 })