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