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