491 lines
15 KiB
Go
491 lines
15 KiB
Go
package exec_test
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
|
|
"code.cloudfoundry.org/lager/lagertest"
|
|
"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("GetStep", func() {
|
|
var (
|
|
ctx context.Context
|
|
cancel func()
|
|
testLogger *lagertest.TestLogger
|
|
|
|
fakeWorker *workerfakes.FakeWorker
|
|
fakePool *workerfakes.FakePool
|
|
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
|
|
fakeResourceFetcher *resourcefakes.FakeFetcher
|
|
fakeResourceCacheFactory *dbfakes.FakeResourceCacheFactory
|
|
fakeSecretManager *credsfakes.FakeSecrets
|
|
fakeDelegate *execfakes.FakeGetDelegate
|
|
getPlan *atc.GetPlan
|
|
|
|
fakeVersionedSource *resourcefakes.FakeVersionedSource
|
|
interpolatedResourceTypes atc.VersionedResourceTypes
|
|
|
|
artifactRepository *artifact.Repository
|
|
state *execfakes.FakeRunState
|
|
|
|
getStep exec.Step
|
|
stepErr error
|
|
|
|
containerMetadata = db.ContainerMetadata{
|
|
WorkingDirectory: resource.ResourcesDir("get"),
|
|
PipelineID: 4567,
|
|
Type: db.ContainerTypeGet,
|
|
StepName: "some-step",
|
|
}
|
|
|
|
stepMetadata = exec.StepMetadata{
|
|
TeamID: 123,
|
|
TeamName: "some-team",
|
|
BuildID: 42,
|
|
BuildName: "some-build",
|
|
PipelineID: 4567,
|
|
PipelineName: "some-pipeline",
|
|
}
|
|
|
|
planID = 56
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
testLogger = lagertest.NewTestLogger("get-action-test")
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakeResourceFetcher = new(resourcefakes.FakeFetcher)
|
|
fakePool = new(workerfakes.FakePool)
|
|
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
|
|
fakeResourceCacheFactory = new(dbfakes.FakeResourceCacheFactory)
|
|
|
|
fakeSecretManager = new(credsfakes.FakeSecrets)
|
|
fakeSecretManager.GetReturns("super-secret-source", nil, true, nil)
|
|
|
|
artifactRepository = artifact.NewRepository()
|
|
state = new(execfakes.FakeRunState)
|
|
state.ArtifactsReturns(artifactRepository)
|
|
|
|
fakeVersionedSource = new(resourcefakes.FakeVersionedSource)
|
|
fakeResourceFetcher.FetchReturns(fakeVersionedSource, nil)
|
|
|
|
fakeDelegate = new(execfakes.FakeGetDelegate)
|
|
|
|
uninterpolatedResourceTypes := atc.VersionedResourceTypes{
|
|
{
|
|
ResourceType: atc.ResourceType{
|
|
Name: "custom-resource",
|
|
Type: "custom-type",
|
|
Source: atc.Source{"some-custom": "((source-param))"},
|
|
},
|
|
Version: atc.Version{"some-custom": "version"},
|
|
},
|
|
}
|
|
|
|
interpolatedResourceTypes = atc.VersionedResourceTypes{
|
|
{
|
|
ResourceType: atc.ResourceType{
|
|
Name: "custom-resource",
|
|
Type: "custom-type",
|
|
Source: atc.Source{"some-custom": "super-secret-source"},
|
|
},
|
|
Version: atc.Version{"some-custom": "version"},
|
|
},
|
|
}
|
|
|
|
getPlan = &atc.GetPlan{
|
|
Name: "some-name",
|
|
Type: "some-resource-type",
|
|
Source: atc.Source{"some": "((source-param))"},
|
|
Params: atc.Params{"some-param": "some-value"},
|
|
Tags: []string{"some", "tags"},
|
|
Version: &atc.Version{"some-version": "some-value"},
|
|
VersionedResourceTypes: uninterpolatedResourceTypes,
|
|
}
|
|
})
|
|
|
|
AfterEach(func() {
|
|
cancel()
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
plan := atc.Plan{
|
|
ID: atc.PlanID(planID),
|
|
Get: getPlan,
|
|
}
|
|
|
|
getStep = exec.NewGetStep(
|
|
plan.ID,
|
|
*plan.Get,
|
|
stepMetadata,
|
|
containerMetadata,
|
|
fakeSecretManager,
|
|
fakeResourceFetcher,
|
|
fakeResourceCacheFactory,
|
|
fakeStrategy,
|
|
fakePool,
|
|
fakeDelegate,
|
|
)
|
|
|
|
stepErr = getStep.Run(ctx, state)
|
|
})
|
|
|
|
It("finds or chooses a worker", func() {
|
|
Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1))
|
|
_, _, actualOwner, actualContainerSpec, actualContainerMetadata, actualWorkerSpec, strategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(actualOwner).To(Equal(db.NewBuildStepContainerOwner(stepMetadata.BuildID, atc.PlanID(planID), stepMetadata.TeamID)))
|
|
Expect(actualContainerSpec).To(Equal(worker.ContainerSpec{
|
|
ImageSpec: worker.ImageSpec{
|
|
ResourceType: "some-resource-type",
|
|
},
|
|
TeamID: stepMetadata.TeamID,
|
|
Env: stepMetadata.Env(),
|
|
}))
|
|
Expect(actualContainerMetadata).To(Equal(containerMetadata))
|
|
Expect(actualWorkerSpec).To(Equal(worker.WorkerSpec{
|
|
ResourceType: "some-resource-type",
|
|
Tags: atc.Tags{"some", "tags"},
|
|
TeamID: stepMetadata.TeamID,
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
}))
|
|
Expect(strategy).To(Equal(fakeStrategy))
|
|
})
|
|
|
|
Context("when find or choosing worker succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
})
|
|
|
|
It("initializes the resource with the correct type and session id, making sure that it is not ephemeral", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
Expect(fakeResourceFetcher.FetchCallCount()).To(Equal(1))
|
|
fctx, _, sid, actualWorker, actualContainerSpec, actualResourceTypes, resourceInstance, delegate := fakeResourceFetcher.FetchArgsForCall(0)
|
|
Expect(fctx).To(Equal(ctx))
|
|
Expect(sid).To(Equal(resource.Session{
|
|
Metadata: db.ContainerMetadata{
|
|
PipelineID: 4567,
|
|
Type: db.ContainerTypeGet,
|
|
StepName: "some-step",
|
|
WorkingDirectory: "/tmp/build/get",
|
|
},
|
|
}))
|
|
Expect(actualWorker.Name()).To(Equal("some-worker"))
|
|
Expect(actualContainerSpec).To(Equal(worker.ContainerSpec{
|
|
ImageSpec: worker.ImageSpec{
|
|
ResourceType: "some-resource-type",
|
|
},
|
|
TeamID: stepMetadata.TeamID,
|
|
Env: stepMetadata.Env(),
|
|
}))
|
|
Expect(resourceInstance).To(Equal(resource.NewResourceInstance(
|
|
"some-resource-type",
|
|
atc.Version{"some-version": "some-value"},
|
|
atc.Source{"some": "super-secret-source"},
|
|
atc.Params{"some-param": "some-value"},
|
|
interpolatedResourceTypes,
|
|
nil,
|
|
db.NewBuildStepContainerOwner(stepMetadata.BuildID, atc.PlanID(planID), stepMetadata.TeamID),
|
|
)))
|
|
Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
|
|
Expect(delegate).To(Equal(fakeDelegate))
|
|
expectedLockName := fmt.Sprintf("%x",
|
|
sha256.Sum256([]byte(
|
|
`{"type":"some-resource-type","version":{"some-version":"some-value"},"source":{"some":"super-secret-source"},"params":{"some-param":"some-value"},"worker_name":"fake-worker"}`,
|
|
)),
|
|
)
|
|
|
|
Expect(resourceInstance.LockName("fake-worker")).To(Equal(expectedLockName))
|
|
})
|
|
|
|
Context("when fetching resource succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeVersionedSource.VersionReturns(atc.Version{"some": "version"})
|
|
fakeVersionedSource.MetadataReturns([]atc.MetadataField{{Name: "some", Value: "metadata"}})
|
|
})
|
|
|
|
It("returns nil", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("is successful", func() {
|
|
Expect(getStep.Succeeded()).To(BeTrue())
|
|
})
|
|
|
|
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(0)))
|
|
Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
|
|
Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}}))
|
|
})
|
|
|
|
Context("when the plan has a resource", func() {
|
|
BeforeEach(func() {
|
|
getPlan.Resource = "some-pipeline-resource"
|
|
})
|
|
|
|
It("saves a version for the resource", func() {
|
|
Expect(fakeDelegate.UpdateVersionCallCount()).To(Equal(1))
|
|
_, plan, info := fakeDelegate.UpdateVersionArgsForCall(0)
|
|
Expect(plan.Resource).To(Equal("some-pipeline-resource"))
|
|
Expect(info.Version).To(Equal(atc.Version{"some": "version"}))
|
|
Expect(info.Metadata).To(Equal([]atc.MetadataField{{Name: "some", Value: "metadata"}}))
|
|
})
|
|
})
|
|
|
|
Context("when getting an anonymous resource", func() {
|
|
|
|
BeforeEach(func() {
|
|
getPlan.Resource = ""
|
|
})
|
|
|
|
It("does not save the version", func() {
|
|
Expect(fakeDelegate.UpdateVersionCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
|
|
Describe("the source registered with the repository", func() {
|
|
var artifactSource worker.ArtifactSource
|
|
|
|
JustBeforeEach(func() {
|
|
var found bool
|
|
artifactSource, found = artifactRepository.SourceFor("some-name")
|
|
Expect(found).To(BeTrue())
|
|
})
|
|
|
|
Describe("streaming to a destination", func() {
|
|
var fakeDestination *workerfakes.FakeArtifactDestination
|
|
|
|
BeforeEach(func() {
|
|
fakeDestination = new(workerfakes.FakeArtifactDestination)
|
|
})
|
|
|
|
Context("when the resource can stream out", func() {
|
|
var (
|
|
streamedOut io.ReadCloser
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
streamedOut = gbytes.NewBuffer()
|
|
fakeVersionedSource.StreamOutReturns(streamedOut, nil)
|
|
})
|
|
|
|
It("streams the resource to the destination", func() {
|
|
err := artifactSource.StreamTo(testLogger, fakeDestination)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(fakeVersionedSource.StreamOutCallCount()).To(Equal(1))
|
|
Expect(fakeVersionedSource.StreamOutArgsForCall(0)).To(Equal("."))
|
|
|
|
Expect(fakeDestination.StreamInCallCount()).To(Equal(1))
|
|
dest, src := fakeDestination.StreamInArgsForCall(0)
|
|
Expect(dest).To(Equal("."))
|
|
Expect(src).To(Equal(streamedOut))
|
|
})
|
|
|
|
Context("when streaming out of the versioned source fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeVersionedSource.StreamOutReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(artifactSource.StreamTo(testLogger, fakeDestination)).To(Equal(disaster))
|
|
})
|
|
})
|
|
|
|
Context("when streaming in to the destination fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeDestination.StreamInReturns(disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(artifactSource.StreamTo(testLogger, fakeDestination)).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the resource cannot stream out", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeVersionedSource.StreamOutReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(artifactSource.StreamTo(testLogger, fakeDestination)).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("streaming a file out", func() {
|
|
Context("when the resource can stream out", func() {
|
|
var (
|
|
fileContent = "file-content"
|
|
|
|
tgzBuffer *gbytes.Buffer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
tgzBuffer = gbytes.NewBuffer()
|
|
fakeVersionedSource.StreamOutReturns(tgzBuffer, nil)
|
|
})
|
|
|
|
Context("when the file exists", func() {
|
|
BeforeEach(func() {
|
|
gzWriter := gzip.NewWriter(tgzBuffer)
|
|
defer gzWriter.Close()
|
|
|
|
tarWriter := tar.NewWriter(gzWriter)
|
|
defer tarWriter.Close()
|
|
|
|
err := tarWriter.WriteHeader(&tar.Header{
|
|
Name: "some-file",
|
|
Mode: 0644,
|
|
Size: int64(len(fileContent)),
|
|
})
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
_, err = tarWriter.Write([]byte(fileContent))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("streams out the given path", func() {
|
|
reader, err := artifactSource.StreamFile(testLogger, "some-path")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(ioutil.ReadAll(reader)).To(Equal([]byte(fileContent)))
|
|
|
|
Expect(fakeVersionedSource.StreamOutArgsForCall(0)).To(Equal("some-path"))
|
|
})
|
|
|
|
Describe("closing the stream", func() {
|
|
It("closes the stream from the versioned source", func() {
|
|
reader, err := artifactSource.StreamFile(testLogger, "some-path")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(tgzBuffer.Closed()).To(BeFalse())
|
|
|
|
err = reader.Close()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(tgzBuffer.Closed()).To(BeTrue())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("but the stream is empty", func() {
|
|
It("returns ErrFileNotFound", func() {
|
|
_, err := artifactSource.StreamFile(testLogger, "some-path")
|
|
Expect(err).To(MatchError(exec.FileNotFoundError{Path: "some-path"}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the resource cannot stream out", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeVersionedSource.StreamOutReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
_, err := artifactSource.StreamFile(testLogger, "some-path")
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when fetching the resource exits unsuccessfully", func() {
|
|
BeforeEach(func() {
|
|
fakeResourceFetcher.FetchReturns(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(getStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when fetching the resource errors", func() {
|
|
disaster := errors.New("oh no")
|
|
|
|
BeforeEach(func() {
|
|
fakeResourceFetcher.FetchReturns(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(getStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when finding or choosing the worker exits unsuccessfully", func() {
|
|
disaster := errors.New("oh no")
|
|
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerForContainerReturns(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(getStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|