github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/exec/put_step_test.go (about)

     1  package exec_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  
     7  	"github.com/pf-qiu/concourse/v6/tracing"
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  	"github.com/onsi/gomega/gbytes"
    11  	"go.opentelemetry.io/otel/api/trace"
    12  	"go.opentelemetry.io/otel/api/trace/tracetest"
    13  
    14  	"github.com/pf-qiu/concourse/v6/atc"
    15  	"github.com/pf-qiu/concourse/v6/atc/db"
    16  	"github.com/pf-qiu/concourse/v6/atc/db/dbfakes"
    17  	"github.com/pf-qiu/concourse/v6/atc/exec"
    18  	"github.com/pf-qiu/concourse/v6/atc/exec/build"
    19  	"github.com/pf-qiu/concourse/v6/atc/exec/execfakes"
    20  	"github.com/pf-qiu/concourse/v6/atc/resource"
    21  	"github.com/pf-qiu/concourse/v6/atc/resource/resourcefakes"
    22  	"github.com/pf-qiu/concourse/v6/atc/runtime"
    23  	"github.com/pf-qiu/concourse/v6/atc/runtime/runtimefakes"
    24  	"github.com/pf-qiu/concourse/v6/atc/worker"
    25  	"github.com/pf-qiu/concourse/v6/atc/worker/workerfakes"
    26  	"github.com/pf-qiu/concourse/v6/vars"
    27  )
    28  
    29  var _ = Describe("PutStep", func() {
    30  	var (
    31  		ctx    context.Context
    32  		cancel func()
    33  
    34  		fakeWorker                *workerfakes.FakeWorker
    35  		fakeClient                *workerfakes.FakeClient
    36  		fakeStrategy              *workerfakes.FakeContainerPlacementStrategy
    37  		fakeResourceFactory       *resourcefakes.FakeResourceFactory
    38  		fakeResource              *resourcefakes.FakeResource
    39  		fakeResourceConfigFactory *dbfakes.FakeResourceConfigFactory
    40  		fakeDelegate              *execfakes.FakePutDelegate
    41  		fakeDelegateFactory       *execfakes.FakePutDelegateFactory
    42  
    43  		spanCtx context.Context
    44  
    45  		putPlan *atc.PutPlan
    46  
    47  		fakeArtifact        *runtimefakes.FakeArtifact
    48  		fakeOtherArtifact   *runtimefakes.FakeArtifact
    49  		fakeMountedArtifact *runtimefakes.FakeArtifact
    50  
    51  		interpolatedResourceTypes atc.VersionedResourceTypes
    52  
    53  		containerMetadata = db.ContainerMetadata{
    54  			WorkingDirectory: resource.ResourcesDir("put"),
    55  			Type:             db.ContainerTypePut,
    56  			StepName:         "some-step",
    57  		}
    58  
    59  		stepMetadata = exec.StepMetadata{
    60  			TeamID:       123,
    61  			TeamName:     "some-team",
    62  			BuildID:      42,
    63  			BuildName:    "some-build",
    64  			PipelineID:   4567,
    65  			PipelineName: "some-pipeline",
    66  		}
    67  
    68  		repo  *build.Repository
    69  		state *execfakes.FakeRunState
    70  
    71  		putStep exec.Step
    72  		stepOk  bool
    73  		stepErr error
    74  
    75  		stdoutBuf *gbytes.Buffer
    76  		stderrBuf *gbytes.Buffer
    77  
    78  		planID atc.PlanID
    79  
    80  		versionResult  runtime.VersionResult
    81  		clientErr      error
    82  		someExitStatus int
    83  	)
    84  
    85  	BeforeEach(func() {
    86  		ctx, cancel = context.WithCancel(context.Background())
    87  
    88  		planID = atc.PlanID("some-plan-id")
    89  
    90  		fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
    91  		fakeClient = new(workerfakes.FakeClient)
    92  		fakeWorker = new(workerfakes.FakeWorker)
    93  		fakeResourceFactory = new(resourcefakes.FakeResourceFactory)
    94  		fakeResourceConfigFactory = new(dbfakes.FakeResourceConfigFactory)
    95  
    96  		fakeDelegate = new(execfakes.FakePutDelegate)
    97  		stdoutBuf = gbytes.NewBuffer()
    98  		stderrBuf = gbytes.NewBuffer()
    99  		fakeDelegate.StdoutReturns(stdoutBuf)
   100  		fakeDelegate.StderrReturns(stderrBuf)
   101  
   102  		fakeDelegateFactory = new(execfakes.FakePutDelegateFactory)
   103  		fakeDelegateFactory.PutDelegateReturns(fakeDelegate)
   104  
   105  		spanCtx = context.Background()
   106  		fakeDelegate.StartSpanReturns(spanCtx, trace.NoopSpan{})
   107  
   108  		versionResult = runtime.VersionResult{
   109  			Version:  atc.Version{"some": "version"},
   110  			Metadata: []atc.MetadataField{{Name: "some", Value: "metadata"}},
   111  		}
   112  
   113  		fakeResource = new(resourcefakes.FakeResource)
   114  		fakeResource.PutReturns(versionResult, nil)
   115  
   116  		repo = build.NewRepository()
   117  		state = new(execfakes.FakeRunState)
   118  		state.ArtifactRepositoryReturns(repo)
   119  
   120  		state.GetStub = vars.StaticVariables{
   121  			"source-var": "super-secret-source",
   122  			"params-var": "super-secret-params",
   123  		}.Get
   124  
   125  		uninterpolatedResourceTypes := atc.VersionedResourceTypes{
   126  			{
   127  				ResourceType: atc.ResourceType{
   128  					Name:   "some-custom-type",
   129  					Type:   "another-custom-type",
   130  					Source: atc.Source{"some-custom": "((source-var))"},
   131  					Params: atc.Params{"some-custom": "((params-var))"},
   132  				},
   133  				Version: atc.Version{"some-custom": "version"},
   134  			},
   135  			{
   136  				ResourceType: atc.ResourceType{
   137  					Name:       "another-custom-type",
   138  					Type:       "registry-image",
   139  					Source:     atc.Source{"another-custom": "((source-var))"},
   140  					Privileged: true,
   141  				},
   142  				Version: atc.Version{"another-custom": "version"},
   143  			},
   144  		}
   145  
   146  		interpolatedResourceTypes = atc.VersionedResourceTypes{
   147  			{
   148  				ResourceType: atc.ResourceType{
   149  					Name:   "some-custom-type",
   150  					Type:   "another-custom-type",
   151  					Source: atc.Source{"some-custom": "super-secret-source"},
   152  
   153  					// params don't need to be interpolated because it's used for
   154  					// fetching, not constructing the resource config
   155  					Params: atc.Params{"some-custom": "((params-var))"},
   156  				},
   157  				Version: atc.Version{"some-custom": "version"},
   158  			},
   159  			{
   160  				ResourceType: atc.ResourceType{
   161  					Name:       "another-custom-type",
   162  					Type:       "registry-image",
   163  					Source:     atc.Source{"another-custom": "super-secret-source"},
   164  					Privileged: true,
   165  				},
   166  				Version: atc.Version{"another-custom": "version"},
   167  			},
   168  		}
   169  
   170  		putPlan = &atc.PutPlan{
   171  			Name:                   "some-name",
   172  			Resource:               "some-resource",
   173  			Type:                   "some-resource-type",
   174  			Source:                 atc.Source{"some": "((source-var))"},
   175  			Params:                 atc.Params{"some": "((params-var))"},
   176  			VersionedResourceTypes: uninterpolatedResourceTypes,
   177  		}
   178  
   179  		fakeArtifact = new(runtimefakes.FakeArtifact)
   180  		fakeOtherArtifact = new(runtimefakes.FakeArtifact)
   181  		fakeMountedArtifact = new(runtimefakes.FakeArtifact)
   182  
   183  		repo.RegisterArtifact("some-source", fakeArtifact)
   184  		repo.RegisterArtifact("some-other-source", fakeOtherArtifact)
   185  		repo.RegisterArtifact("some-mounted-source", fakeMountedArtifact)
   186  
   187  		fakeResourceFactory.NewResourceReturns(fakeResource)
   188  
   189  		someExitStatus = 0
   190  		clientErr = nil
   191  	})
   192  
   193  	AfterEach(func() {
   194  		cancel()
   195  	})
   196  
   197  	JustBeforeEach(func() {
   198  		plan := atc.Plan{
   199  			ID:  atc.PlanID(planID),
   200  			Put: putPlan,
   201  		}
   202  
   203  		fakeClient.RunPutStepReturns(
   204  			worker.PutResult{ExitStatus: someExitStatus, VersionResult: versionResult},
   205  			clientErr,
   206  		)
   207  
   208  		putStep = exec.NewPutStep(
   209  			plan.ID,
   210  			*plan.Put,
   211  			stepMetadata,
   212  			containerMetadata,
   213  			fakeResourceFactory,
   214  			fakeResourceConfigFactory,
   215  			fakeStrategy,
   216  			fakeClient,
   217  			fakeDelegateFactory,
   218  		)
   219  
   220  		stepOk, stepErr = putStep.Run(ctx, state)
   221  	})
   222  
   223  	Context("inputs", func() {
   224  		Context("when inputs are specified with 'all' keyword", func() {
   225  			BeforeEach(func() {
   226  				putPlan.Inputs = &atc.InputsConfig{
   227  					All: true,
   228  				}
   229  			})
   230  
   231  			It("calls RunPutStep with all inputs", func() {
   232  				_, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   233  				Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3))
   234  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact))
   235  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact))
   236  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   237  			})
   238  		})
   239  
   240  		Context("when inputs are left blank", func() {
   241  			It("calls RunPutStep with all inputs", func() {
   242  				_, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   243  				Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3))
   244  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact))
   245  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact))
   246  				Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   247  			})
   248  		})
   249  
   250  		Context("when only some inputs are specified ", func() {
   251  			BeforeEach(func() {
   252  				putPlan.Inputs = &atc.InputsConfig{
   253  					Specified: []string{"some-source", "some-other-source"},
   254  				}
   255  			})
   256  
   257  			It("calls RunPutStep with specified inputs", func() {
   258  				_, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   259  				Expect(containerSpec.ArtifactByPath).To(HaveLen(2))
   260  				Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact))
   261  				Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   262  			})
   263  		})
   264  
   265  		Context("when the inputs are detected", func() {
   266  			BeforeEach(func() {
   267  				putPlan.Inputs = &atc.InputsConfig{
   268  					Detect: true,
   269  				}
   270  			})
   271  
   272  			Context("when the params are only strings", func() {
   273  				BeforeEach(func() {
   274  					putPlan.Params = atc.Params{
   275  						"some-param":    "some-source/source",
   276  						"another-param": "does-not-exist",
   277  						"number-param":  123,
   278  					}
   279  				})
   280  
   281  				It("calls RunPutStep with detected inputs", func() {
   282  					_, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   283  					Expect(containerSpec.ArtifactByPath).To(HaveLen(1))
   284  					Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   285  				})
   286  			})
   287  
   288  			Context("when the params have maps and slices", func() {
   289  				BeforeEach(func() {
   290  					putPlan.Params = atc.Params{
   291  						"some-slice": []interface{}{
   292  							[]interface{}{"some-source/source", "does-not-exist", 123},
   293  							[]interface{}{"does not exist-2"},
   294  						},
   295  						"some-map": map[string]interface{}{
   296  							"key": "some-other-source/source",
   297  						},
   298  					}
   299  				})
   300  
   301  				It("calls RunPutStep with detected inputs", func() {
   302  					_, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   303  					Expect(containerSpec.ArtifactByPath).To(HaveLen(2))
   304  					Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact))
   305  					Expect(containerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   306  				})
   307  			})
   308  		})
   309  	})
   310  
   311  	It("calls workerClient -> RunPutStep with the appropriate arguments", func() {
   312  		Expect(fakeClient.RunPutStepCallCount()).To(Equal(1))
   313  		actualContext, _, actualOwner, actualContainerSpec, actualWorkerSpec, actualStrategy, actualContainerMetadata, actualProcessSpec, actualEventDelegate, actualResource := fakeClient.RunPutStepArgsForCall(0)
   314  
   315  		Expect(actualContext).To(Equal(spanCtx))
   316  		Expect(actualOwner).To(Equal(db.NewBuildStepContainerOwner(42, atc.PlanID(planID), 123)))
   317  		Expect(actualContainerSpec.ImageSpec).To(Equal(worker.ImageSpec{
   318  			ResourceType: "some-resource-type",
   319  		}))
   320  		Expect(actualContainerSpec.TeamID).To(Equal(123))
   321  		Expect(actualContainerSpec.Env).To(Equal(stepMetadata.Env()))
   322  		Expect(actualContainerSpec.Dir).To(Equal("/tmp/build/put"))
   323  
   324  		Expect(actualContainerSpec.ArtifactByPath).To(HaveLen(3))
   325  		Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-other-source"]).To(Equal(fakeOtherArtifact))
   326  		Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-mounted-source"]).To(Equal(fakeMountedArtifact))
   327  		Expect(actualContainerSpec.ArtifactByPath["/tmp/build/put/some-source"]).To(Equal(fakeArtifact))
   328  
   329  		Expect(actualWorkerSpec).To(Equal(worker.WorkerSpec{
   330  			TeamID:       123,
   331  			ResourceType: "some-resource-type",
   332  		}))
   333  		Expect(actualStrategy).To(Equal(fakeStrategy))
   334  
   335  		Expect(actualContainerMetadata).To(Equal(containerMetadata))
   336  
   337  		Expect(actualProcessSpec).To(Equal(
   338  			runtime.ProcessSpec{
   339  				Path:         "/opt/resource/out",
   340  				Args:         []string{resource.ResourcesDir("put")},
   341  				StdoutWriter: stdoutBuf,
   342  				StderrWriter: stderrBuf,
   343  			}))
   344  		Expect(actualEventDelegate).To(Equal(fakeDelegate))
   345  		Expect(actualResource).To(Equal(fakeResource))
   346  	})
   347  
   348  	Context("when using a custom resource type", func() {
   349  		var fakeImageSpec worker.ImageSpec
   350  
   351  		BeforeEach(func() {
   352  			putPlan.Type = "some-custom-type"
   353  
   354  			fakeImageSpec = worker.ImageSpec{
   355  				ImageArtifact: new(runtimefakes.FakeArtifact),
   356  			}
   357  
   358  			fakeDelegate.FetchImageReturns(fakeImageSpec, nil)
   359  		})
   360  
   361  		It("fetches the resource type image and uses it for the container", func() {
   362  			Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   363  
   364  			_, imageResource, types, privileged := fakeDelegate.FetchImageArgsForCall(0)
   365  
   366  			By("fetching the type image")
   367  			Expect(imageResource).To(Equal(atc.ImageResource{
   368  				Name:    "some-custom-type",
   369  				Type:    "another-custom-type",
   370  				Source:  atc.Source{"some-custom": "((source-var))"},
   371  				Params:  atc.Params{"some-custom": "((params-var))"},
   372  				Version: atc.Version{"some-custom": "version"},
   373  			}))
   374  
   375  			By("excluding the type from the FetchImage call")
   376  			Expect(types).To(Equal(atc.VersionedResourceTypes{
   377  				{
   378  					ResourceType: atc.ResourceType{
   379  						Name:       "another-custom-type",
   380  						Type:       "registry-image",
   381  						Source:     atc.Source{"another-custom": "((source-var))"},
   382  						Privileged: true,
   383  					},
   384  					Version: atc.Version{"another-custom": "version"},
   385  				},
   386  			}))
   387  
   388  			By("not being privileged")
   389  			Expect(privileged).To(BeFalse())
   390  		})
   391  
   392  		It("sets the bottom-most type in the worker spec", func() {
   393  			_, _, _, _, workerSpec, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   394  			Expect(workerSpec).To(Equal(worker.WorkerSpec{
   395  				TeamID:       stepMetadata.TeamID,
   396  				ResourceType: "registry-image",
   397  			}))
   398  		})
   399  
   400  		It("sets the image spec in the container spec", func() {
   401  			_, _, _, containerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   402  			Expect(containerSpec.ImageSpec).To(Equal(fakeImageSpec))
   403  		})
   404  
   405  		Context("when the resource type is privileged", func() {
   406  			BeforeEach(func() {
   407  				putPlan.Type = "another-custom-type"
   408  			})
   409  
   410  			It("fetches the image with privileged", func() {
   411  				Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   412  				_, _, _, privileged := fakeDelegate.FetchImageArgsForCall(0)
   413  				Expect(privileged).To(BeTrue())
   414  			})
   415  		})
   416  
   417  		Context("when the plan configures tags", func() {
   418  			BeforeEach(func() {
   419  				putPlan.Tags = atc.Tags{"plan", "tags"}
   420  			})
   421  
   422  			It("fetches using the tags", func() {
   423  				Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   424  				_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   425  				Expect(imageResource.Tags).To(Equal(atc.Tags{"plan", "tags"}))
   426  			})
   427  		})
   428  
   429  		Context("when the resource type configures tags", func() {
   430  			BeforeEach(func() {
   431  				taggedType, found := putPlan.VersionedResourceTypes.Lookup("some-custom-type")
   432  				Expect(found).To(BeTrue())
   433  
   434  				taggedType.Tags = atc.Tags{"type", "tags"}
   435  
   436  				newTypes := putPlan.VersionedResourceTypes.Without("some-custom-type")
   437  				newTypes = append(newTypes, taggedType)
   438  
   439  				putPlan.VersionedResourceTypes = newTypes
   440  			})
   441  
   442  			It("fetches using the type tags", func() {
   443  				Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   444  				_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   445  				Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"}))
   446  			})
   447  
   448  			Context("when the plan ALSO configures tags", func() {
   449  				BeforeEach(func() {
   450  					putPlan.Tags = atc.Tags{"plan", "tags"}
   451  				})
   452  
   453  				It("fetches using only the type tags", func() {
   454  					Expect(fakeDelegate.FetchImageCallCount()).To(Equal(1))
   455  					_, imageResource, _, _ := fakeDelegate.FetchImageArgsForCall(0)
   456  					Expect(imageResource.Tags).To(Equal(atc.Tags{"type", "tags"}))
   457  				})
   458  			})
   459  		})
   460  	})
   461  
   462  	Context("when the plan specifies tags", func() {
   463  		BeforeEach(func() {
   464  			putPlan.Tags = atc.Tags{"some", "tags"}
   465  		})
   466  
   467  		It("sets them in the WorkerSpec", func() {
   468  			_, _, _, _, workerSpec, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   469  			Expect(workerSpec.Tags).To(Equal([]string{"some", "tags"}))
   470  		})
   471  	})
   472  
   473  	Context("when tracing is enabled", func() {
   474  		var buildSpan trace.Span
   475  
   476  		BeforeEach(func() {
   477  			tracing.ConfigureTraceProvider(tracetest.NewProvider())
   478  
   479  			spanCtx, buildSpan = tracing.StartSpan(ctx, "build", nil)
   480  			fakeDelegate.StartSpanReturns(spanCtx, buildSpan)
   481  		})
   482  
   483  		AfterEach(func() {
   484  			tracing.Configured = false
   485  		})
   486  
   487  		It("propagates span context to the worker client", func() {
   488  			actualCtx, _, _, _, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   489  			Expect(actualCtx).To(Equal(spanCtx))
   490  		})
   491  
   492  		It("populates the TRACEPARENT env var", func() {
   493  			_, _, _, actualContainerSpec, _, _, _, _, _, _ := fakeClient.RunPutStepArgsForCall(0)
   494  			Expect(actualContainerSpec.Env).To(ContainElement(MatchRegexp(`TRACEPARENT=.+`)))
   495  		})
   496  	})
   497  
   498  	Context("when creds tracker can initialize the resource", func() {
   499  		var (
   500  			fakeResourceConfig *dbfakes.FakeResourceConfig
   501  		)
   502  
   503  		BeforeEach(func() {
   504  			fakeResourceConfig = new(dbfakes.FakeResourceConfig)
   505  			fakeResourceConfig.IDReturns(1)
   506  
   507  			fakeResourceConfigFactory.FindOrCreateResourceConfigReturns(fakeResourceConfig, nil)
   508  
   509  			fakeWorker.NameReturns("some-worker")
   510  		})
   511  
   512  		It("creates a resource with the correct source and params", func() {
   513  			actualSource, actualParams, _ := fakeResourceFactory.NewResourceArgsForCall(0)
   514  			Expect(actualSource).To(Equal(atc.Source{"some": "super-secret-source"}))
   515  			Expect(actualParams).To(Equal(atc.Params{"some": "super-secret-params"}))
   516  
   517  			_, _, _, _, _, _, _, _, _, actualResource := fakeClient.RunPutStepArgsForCall(0)
   518  			Expect(actualResource).To(Equal(fakeResource))
   519  		})
   520  
   521  	})
   522  
   523  	It("saves the build output", func() {
   524  		Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(1))
   525  
   526  		_, plan, actualSource, actualResourceTypes, info := fakeDelegate.SaveOutputArgsForCall(0)
   527  		Expect(plan.Name).To(Equal("some-name"))
   528  		Expect(plan.Type).To(Equal("some-resource-type"))
   529  		Expect(plan.Resource).To(Equal("some-resource"))
   530  		Expect(actualSource).To(Equal(atc.Source{"some": "super-secret-source"}))
   531  		Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
   532  		Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
   533  		Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}}))
   534  	})
   535  
   536  	Context("when the step.Plan.Resource is blank", func() {
   537  		BeforeEach(func() {
   538  			putPlan.Resource = ""
   539  		})
   540  
   541  		It("is successful", func() {
   542  			Expect(stepOk).To(BeTrue())
   543  		})
   544  
   545  		It("does not save the build output", func() {
   546  			Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(0))
   547  		})
   548  	})
   549  
   550  	Context("when RunPutStep succeeds", func() {
   551  		It("finishes via the delegate", func() {
   552  			Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
   553  			_, status, info := fakeDelegate.FinishedArgsForCall(0)
   554  			Expect(status).To(Equal(exec.ExitStatus(0)))
   555  			Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
   556  			Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}}))
   557  		})
   558  
   559  		It("stores the version result as the step result", func() {
   560  			Expect(state.StoreResultCallCount()).To(Equal(1))
   561  			sID, sVal := state.StoreResultArgsForCall(0)
   562  			Expect(sID).To(Equal(planID))
   563  			Expect(sVal).To(Equal(versionResult))
   564  		})
   565  
   566  		It("is successful", func() {
   567  			Expect(stepOk).To(BeTrue())
   568  		})
   569  	})
   570  
   571  	Context("when RunPutStep exits unsuccessfully", func() {
   572  		BeforeEach(func() {
   573  			versionResult = runtime.VersionResult{}
   574  			someExitStatus = 42
   575  		})
   576  
   577  		It("finishes the step via the delegate", func() {
   578  			Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
   579  			_, status, info := fakeDelegate.FinishedArgsForCall(0)
   580  			Expect(status).To(Equal(exec.ExitStatus(42)))
   581  			Expect(info).To(BeZero())
   582  		})
   583  
   584  		It("returns nil", func() {
   585  			Expect(stepErr).ToNot(HaveOccurred())
   586  		})
   587  
   588  		It("is not successful", func() {
   589  			Expect(stepOk).To(BeFalse())
   590  		})
   591  	})
   592  
   593  	Context("when RunPutStep exits with an error", func() {
   594  		disaster := errors.New("oh no")
   595  
   596  		BeforeEach(func() {
   597  			versionResult = runtime.VersionResult{}
   598  			clientErr = disaster
   599  		})
   600  
   601  		It("does not finish the step via the delegate", func() {
   602  			Expect(fakeDelegate.FinishedCallCount()).To(Equal(0))
   603  		})
   604  
   605  		It("returns the error", func() {
   606  			Expect(stepErr).To(Equal(disaster))
   607  		})
   608  
   609  		It("is not successful", func() {
   610  			Expect(stepOk).To(BeFalse())
   611  		})
   612  	})
   613  })