concourse/atc/exec/put_step_test.go

402 lines
13 KiB
Go

package exec_test
import (
"context"
"errors"
"github.com/concourse/concourse/atc"
"github.com/concourse/concourse/atc/creds/credsfakes"
"github.com/concourse/concourse/atc/db"
"github.com/concourse/concourse/atc/db/dbfakes"
"github.com/concourse/concourse/atc/exec"
"github.com/concourse/concourse/atc/exec/artifact"
"github.com/concourse/concourse/atc/exec/execfakes"
"github.com/concourse/concourse/atc/resource"
"github.com/concourse/concourse/atc/resource/resourcefakes"
"github.com/concourse/concourse/atc/worker"
"github.com/concourse/concourse/atc/worker/workerfakes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
)
var _ = Describe("PutStep", func() {
var (
ctx context.Context
cancel func()
fakeWorker *workerfakes.FakeWorker
fakePool *workerfakes.FakePool
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
fakeResourceFactory *resourcefakes.FakeResourceFactory
fakeResourceConfigFactory *dbfakes.FakeResourceConfigFactory
fakeSecretManager *credsfakes.FakeSecrets
fakeDelegate *execfakes.FakePutDelegate
putPlan *atc.PutPlan
interpolatedResourceTypes atc.VersionedResourceTypes
containerMetadata = db.ContainerMetadata{
WorkingDirectory: resource.ResourcesDir("put"),
Type: db.ContainerTypePut,
StepName: "some-step",
}
stepMetadata = exec.StepMetadata{
TeamID: 123,
TeamName: "some-team",
BuildID: 42,
BuildName: "some-build",
PipelineID: 4567,
PipelineName: "some-pipeline",
}
repo *artifact.Repository
state *execfakes.FakeRunState
putStep *exec.PutStep
stepErr error
stdoutBuf *gbytes.Buffer
stderrBuf *gbytes.Buffer
planID atc.PlanID
)
BeforeEach(func() {
ctx, cancel = context.WithCancel(context.Background())
planID = atc.PlanID("some-plan-id")
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
fakePool = new(workerfakes.FakePool)
fakeWorker = new(workerfakes.FakeWorker)
fakeResourceFactory = new(resourcefakes.FakeResourceFactory)
fakeResourceConfigFactory = new(dbfakes.FakeResourceConfigFactory)
fakeSecretManager = new(credsfakes.FakeSecrets)
fakeSecretManager.GetReturnsOnCall(0, "super-secret-source", nil, true, nil)
fakeSecretManager.GetReturnsOnCall(1, "source", nil, true, nil)
fakeDelegate = new(execfakes.FakePutDelegate)
stdoutBuf = gbytes.NewBuffer()
stderrBuf = gbytes.NewBuffer()
fakeDelegate.StdoutReturns(stdoutBuf)
fakeDelegate.StderrReturns(stderrBuf)
repo = artifact.NewRepository()
state = new(execfakes.FakeRunState)
state.ArtifactsReturns(repo)
uninterpolatedResourceTypes := atc.VersionedResourceTypes{
{
ResourceType: atc.ResourceType{
Name: "custom-resource",
Type: "custom-type",
Source: atc.Source{"some-custom": "((custom-param))"},
},
Version: atc.Version{"some-custom": "version"},
},
}
interpolatedResourceTypes = atc.VersionedResourceTypes{
{
ResourceType: atc.ResourceType{
Name: "custom-resource",
Type: "custom-type",
Source: atc.Source{"some-custom": "source"},
},
Version: atc.Version{"some-custom": "version"},
},
}
putPlan = &atc.PutPlan{
Name: "some-name",
Resource: "some-resource",
Type: "some-resource-type",
Source: atc.Source{"some": "((source-param))"},
Params: atc.Params{"some-param": "some-value"},
Tags: []string{"some", "tags"},
VersionedResourceTypes: uninterpolatedResourceTypes,
}
})
AfterEach(func() {
cancel()
})
JustBeforeEach(func() {
plan := atc.Plan{
ID: atc.PlanID(planID),
Put: putPlan,
}
putStep = exec.NewPutStep(
plan.ID,
*plan.Put,
stepMetadata,
containerMetadata,
fakeSecretManager,
fakeResourceFactory,
fakeResourceConfigFactory,
fakeStrategy,
fakePool,
fakeDelegate,
)
stepErr = putStep.Run(ctx, state)
})
Context("when repo contains sources", func() {
var (
fakeSource *workerfakes.FakeArtifactSource
fakeOtherSource *workerfakes.FakeArtifactSource
fakeMountedSource *workerfakes.FakeArtifactSource
)
BeforeEach(func() {
fakeSource = new(workerfakes.FakeArtifactSource)
fakeOtherSource = new(workerfakes.FakeArtifactSource)
fakeMountedSource = new(workerfakes.FakeArtifactSource)
repo.RegisterSource("some-source", fakeSource)
repo.RegisterSource("some-other-source", fakeOtherSource)
repo.RegisterSource("some-mounted-source", fakeMountedSource)
})
Context("when the tracker can initialize the resource", func() {
var (
fakeResource *resourcefakes.FakeResource
fakeResourceConfig *dbfakes.FakeResourceConfig
fakeVersionedSource *resourcefakes.FakeVersionedSource
)
BeforeEach(func() {
fakeResourceConfig = new(dbfakes.FakeResourceConfig)
fakeResourceConfig.IDReturns(1)
fakeResourceConfigFactory.FindOrCreateResourceConfigReturns(fakeResourceConfig, nil)
fakeVersionedSource = new(resourcefakes.FakeVersionedSource)
fakeVersionedSource.VersionReturns(atc.Version{"some": "version"})
fakeVersionedSource.MetadataReturns([]atc.MetadataField{{Name: "some", Value: "metadata"}})
fakeWorker.NameReturns("some-worker")
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
fakeResource = new(resourcefakes.FakeResource)
fakeResource.PutReturns(fakeVersionedSource, nil)
fakeResourceFactory.NewResourceForContainerReturns(fakeResource)
})
It("finds/chooses a worker and creates a container with the correct type, session, and sources with no inputs specified (meaning it takes all artifacts)", func() {
Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1))
_, _, actualOwner, actualContainerSpec, actualContainerMetadata, actualWorkerSpec, strategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
Expect(actualOwner).To(Equal(db.NewBuildStepContainerOwner(42, atc.PlanID(planID), 123)))
Expect(actualContainerSpec.ImageSpec).To(Equal(worker.ImageSpec{
ResourceType: "some-resource-type",
}))
Expect(actualContainerSpec.Tags).To(Equal([]string{"some", "tags"}))
Expect(actualContainerSpec.TeamID).To(Equal(123))
Expect(actualContainerSpec.Env).To(Equal(stepMetadata.Env()))
Expect(actualContainerSpec.Dir).To(Equal("/tmp/build/put"))
Expect(actualContainerSpec.Inputs).To(HaveLen(3))
Expect(actualContainerMetadata).To(Equal(containerMetadata))
Expect(actualWorkerSpec).To(Equal(worker.WorkerSpec{
TeamID: 123,
Tags: []string{"some", "tags"},
ResourceType: "some-resource-type",
ResourceTypes: interpolatedResourceTypes,
}))
Expect(strategy).To(Equal(fakeStrategy))
_, _, delegate, owner, containerSpec, actualResourceTypes := fakeWorker.FindOrCreateContainerArgsForCall(0)
Expect(owner).To(Equal(db.NewBuildStepContainerOwner(42, atc.PlanID(planID), 123)))
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
ResourceType: "some-resource-type",
}))
Expect(containerSpec.Tags).To(Equal([]string{"some", "tags"}))
Expect(containerSpec.TeamID).To(Equal(123))
Expect(containerSpec.Env).To(Equal(stepMetadata.Env()))
Expect(containerSpec.Dir).To(Equal("/tmp/build/put"))
Expect(containerSpec.Inputs).To(HaveLen(3))
Expect([]worker.ArtifactSource{
containerSpec.Inputs[0].Source(),
containerSpec.Inputs[1].Source(),
containerSpec.Inputs[2].Source(),
}).To(ConsistOf(
exec.PutResourceSource{fakeSource},
exec.PutResourceSource{fakeOtherSource},
exec.PutResourceSource{fakeMountedSource},
))
Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
Expect(delegate).To(Equal(fakeDelegate))
})
Context("when the inputs are specified", func() {
BeforeEach(func() {
putPlan.Inputs = &atc.InputsConfig{
Specified: []string{"some-source", "some-other-source"},
}
})
It("initializes the container with specified inputs", func() {
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
Expect(containerSpec.Inputs).To(HaveLen(2))
Expect([]worker.ArtifactSource{
containerSpec.Inputs[0].Source(),
containerSpec.Inputs[1].Source(),
}).To(ConsistOf(
exec.PutResourceSource{fakeSource},
exec.PutResourceSource{fakeOtherSource},
))
})
})
It("puts the resource with the given context", func() {
Expect(fakeResource.PutCallCount()).To(Equal(1))
putCtx, _, _, _ := fakeResource.PutArgsForCall(0)
Expect(putCtx).To(Equal(ctx))
})
It("puts the resource with the correct source and params", func() {
Expect(fakeResource.PutCallCount()).To(Equal(1))
_, _, putSource, putParams := fakeResource.PutArgsForCall(0)
Expect(putSource).To(Equal(atc.Source{"some": "super-secret-source"}))
Expect(putParams).To(Equal(atc.Params{"some-param": "some-value"}))
})
It("puts the resource with the io config forwarded", func() {
Expect(fakeResource.PutCallCount()).To(Equal(1))
_, ioConfig, _, _ := fakeResource.PutArgsForCall(0)
Expect(ioConfig.Stdout).To(Equal(stdoutBuf))
Expect(ioConfig.Stderr).To(Equal(stderrBuf))
})
It("runs the get resource action", func() {
Expect(fakeResource.PutCallCount()).To(Equal(1))
})
It("is successful", func() {
Expect(putStep.Succeeded()).To(BeTrue())
})
It("saves the build output", func() {
Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(1))
_, plan, actualSource, actualResourceTypes, info := fakeDelegate.SaveOutputArgsForCall(0)
Expect(plan.Name).To(Equal("some-name"))
Expect(plan.Type).To(Equal("some-resource-type"))
Expect(plan.Resource).To(Equal("some-resource"))
Expect(actualSource).To(Equal(atc.Source{"some": "super-secret-source"}))
Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
Expect(info.Metadata).To(Equal([]atc.MetadataField{{"some", "metadata"}}))
})
Context("when the resource is blank", func() {
BeforeEach(func() {
putPlan.Resource = ""
})
It("is successful", func() {
Expect(putStep.Succeeded()).To(BeTrue())
})
It("does not save the build output", func() {
Expect(fakeDelegate.SaveOutputCallCount()).To(Equal(0))
})
})
It("finishes via the delegate", func() {
Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
_, status, info := fakeDelegate.FinishedArgsForCall(0)
Expect(status).To(Equal(exec.ExitStatus(0)))
Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}}))
})
It("stores the version info as the step result", func() {
Expect(state.StoreResultCallCount()).To(Equal(1))
sID, sVal := state.StoreResultArgsForCall(0)
Expect(sID).To(Equal(planID))
Expect(sVal).To(Equal(exec.VersionInfo{
Version: atc.Version{"some": "version"},
Metadata: []atc.MetadataField{{Name: "some", Value: "metadata"}},
}))
})
Context("when performing the put exits unsuccessfully", func() {
BeforeEach(func() {
fakeResource.PutReturns(nil, resource.ErrResourceScriptFailed{
ExitStatus: 42,
})
})
It("finishes the step via the delegate", func() {
Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
_, status, info := fakeDelegate.FinishedArgsForCall(0)
Expect(status).To(Equal(exec.ExitStatus(42)))
Expect(info).To(BeZero())
})
It("returns nil", func() {
Expect(stepErr).ToNot(HaveOccurred())
})
It("is not successful", func() {
Expect(putStep.Succeeded()).To(BeFalse())
})
})
Context("when performing the put errors", func() {
disaster := errors.New("oh no")
BeforeEach(func() {
fakeResource.PutReturns(nil, disaster)
})
It("does not finish the step via the delegate", func() {
Expect(fakeDelegate.FinishedCallCount()).To(Equal(0))
})
It("returns the error", func() {
Expect(stepErr).To(Equal(disaster))
})
It("is not successful", func() {
Expect(putStep.Succeeded()).To(BeFalse())
})
})
})
Context("when find or choosing a worker fails", func() {
disaster := errors.New("nope")
BeforeEach(func() {
fakePool.FindOrChooseWorkerForContainerReturns(nil, disaster)
})
It("returns the failure", func() {
Expect(stepErr).To(Equal(disaster))
})
})
Context("when find or creating a container fails", func() {
disaster := errors.New("nope")
BeforeEach(func() {
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
fakeWorker.FindOrCreateContainerReturns(nil, disaster)
})
It("returns the failure", func() {
Expect(stepErr).To(Equal(disaster))
})
})
})
})