1681 lines
53 KiB
Go
1681 lines
53 KiB
Go
package exec_test
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"code.cloudfoundry.org/garden"
|
|
"code.cloudfoundry.org/garden/gardenfakes"
|
|
"code.cloudfoundry.org/lager"
|
|
"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/exec"
|
|
"github.com/concourse/concourse/atc/exec/artifact"
|
|
"github.com/concourse/concourse/atc/exec/execfakes"
|
|
"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("TaskStep", func() {
|
|
var (
|
|
ctx context.Context
|
|
cancel func()
|
|
logger *lagertest.TestLogger
|
|
|
|
stdoutBuf *gbytes.Buffer
|
|
stderrBuf *gbytes.Buffer
|
|
|
|
fakePool *workerfakes.FakePool
|
|
fakeWorker *workerfakes.FakeWorker
|
|
fakeStrategy *workerfakes.FakeContainerPlacementStrategy
|
|
|
|
fakeSecretManager *credsfakes.FakeSecrets
|
|
fakeDelegate *execfakes.FakeTaskDelegate
|
|
taskPlan *atc.TaskPlan
|
|
|
|
interpolatedResourceTypes atc.VersionedResourceTypes
|
|
|
|
repo *artifact.Repository
|
|
state *execfakes.FakeRunState
|
|
|
|
taskStep exec.Step
|
|
stepErr error
|
|
|
|
containerMetadata = db.ContainerMetadata{
|
|
WorkingDirectory: "some-artifact-root",
|
|
Type: db.ContainerTypeTask,
|
|
StepName: "some-step",
|
|
}
|
|
|
|
stepMetadata = exec.StepMetadata{
|
|
TeamID: 123,
|
|
BuildID: 1234,
|
|
JobID: 12345,
|
|
}
|
|
|
|
planID = atc.PlanID(42)
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
logger = lagertest.NewTestLogger("task-action-test")
|
|
|
|
stdoutBuf = gbytes.NewBuffer()
|
|
stderrBuf = gbytes.NewBuffer()
|
|
|
|
fakeWorker = new(workerfakes.FakeWorker)
|
|
fakePool = new(workerfakes.FakePool)
|
|
fakeStrategy = new(workerfakes.FakeContainerPlacementStrategy)
|
|
|
|
fakeSecretManager = new(credsfakes.FakeSecrets)
|
|
fakeSecretManager.GetReturns("super-secret-source", nil, true, nil)
|
|
|
|
fakeDelegate = new(execfakes.FakeTaskDelegate)
|
|
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": "((source-param))"},
|
|
Params: atc.Params{"some-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": "super-secret-source"},
|
|
Params: atc.Params{"some-custom": "param"},
|
|
},
|
|
Version: atc.Version{"some-custom": "version"},
|
|
},
|
|
}
|
|
|
|
taskPlan = &atc.TaskPlan{
|
|
Name: "some-task",
|
|
Privileged: false,
|
|
Tags: []string{"step", "tags"},
|
|
VersionedResourceTypes: uninterpolatedResourceTypes,
|
|
}
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
plan := atc.Plan{
|
|
ID: atc.PlanID(planID),
|
|
Task: taskPlan,
|
|
}
|
|
|
|
taskStep = exec.NewTaskStep(
|
|
plan.ID,
|
|
*plan.Task,
|
|
atc.ContainerLimits{},
|
|
stepMetadata,
|
|
containerMetadata,
|
|
fakeSecretManager,
|
|
fakeStrategy,
|
|
fakePool,
|
|
fakeDelegate,
|
|
)
|
|
|
|
stepErr = taskStep.Run(ctx, state)
|
|
})
|
|
|
|
Context("when the plan has a config", func() {
|
|
|
|
BeforeEach(func() {
|
|
cpu := uint64(1024)
|
|
memory := uint64(1024)
|
|
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
ImageResource: &atc.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "secret-source-param"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Limits: atc.ContainerLimits{
|
|
CPU: &cpu,
|
|
Memory: &memory,
|
|
},
|
|
Params: map[string]string{
|
|
"SECURE": "secret-task-param",
|
|
},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
Context("when the worker is either found or chosen", func() {
|
|
BeforeEach(func() {
|
|
fakeWorker.NameReturns("some-worker")
|
|
fakePool.FindOrChooseWorkerForContainerReturns(fakeWorker, nil)
|
|
|
|
fakeContainer := new(workerfakes.FakeContainer)
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
})
|
|
|
|
It("finds or chooses a worker", func() {
|
|
Expect(fakePool.FindOrChooseWorkerForContainerCallCount()).To(Equal(1))
|
|
_, _, owner, containerSpec, createdMetadata, workerSpec, strategy := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(owner).To(Equal(db.NewBuildStepContainerOwner(stepMetadata.BuildID, planID, stepMetadata.TeamID)))
|
|
Expect(createdMetadata).To(Equal(db.ContainerMetadata{
|
|
WorkingDirectory: "some-artifact-root",
|
|
Type: db.ContainerTypeTask,
|
|
StepName: "some-step",
|
|
}))
|
|
|
|
cpu := uint64(1024)
|
|
memory := uint64(1024)
|
|
Expect(containerSpec).To(Equal(worker.ContainerSpec{
|
|
Platform: "some-platform",
|
|
Tags: []string{"step", "tags"},
|
|
TeamID: stepMetadata.TeamID,
|
|
ImageSpec: worker.ImageSpec{
|
|
ImageResource: &worker.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "secret-source-param"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Privileged: false,
|
|
},
|
|
Limits: worker.ContainerLimits{
|
|
CPU: &cpu,
|
|
Memory: &memory,
|
|
},
|
|
Dir: "some-artifact-root",
|
|
Env: []string{"SECURE=secret-task-param"},
|
|
Inputs: []worker.InputSource{},
|
|
Outputs: worker.OutputPaths{},
|
|
}))
|
|
|
|
Expect(workerSpec).To(Equal(worker.WorkerSpec{
|
|
Platform: "some-platform",
|
|
Tags: []string{"step", "tags"},
|
|
TeamID: stepMetadata.TeamID,
|
|
ResourceType: "docker",
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
}))
|
|
Expect(strategy).To(Equal(fakeStrategy))
|
|
})
|
|
|
|
Context("when the task's container is either found or created", func() {
|
|
var (
|
|
fakeContainer *workerfakes.FakeContainer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer = new(workerfakes.FakeContainer)
|
|
fakeContainer.HandleReturns("some-handle")
|
|
fakeWorker.FindOrCreateContainerReturns(fakeContainer, nil)
|
|
})
|
|
|
|
Describe("before creating a container", func() {
|
|
BeforeEach(func() {
|
|
fakeDelegate.InitializingStub = func(lager.Logger, atc.TaskConfig) {
|
|
defer GinkgoRecover()
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(BeZero())
|
|
}
|
|
})
|
|
|
|
It("invoked the delegate's Initializing callback", func() {
|
|
Expect(fakeDelegate.InitializingCallCount()).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
It("finds or creates a container", func() {
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, cancel, delegate, owner, containerSpec, actualResourceTypes := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(cancel).ToNot(BeNil())
|
|
Expect(owner).To(Equal(db.NewBuildStepContainerOwner(stepMetadata.BuildID, planID, stepMetadata.TeamID)))
|
|
Expect(delegate).To(Equal(fakeDelegate))
|
|
|
|
cpu := uint64(1024)
|
|
memory := uint64(1024)
|
|
Expect(containerSpec).To(Equal(worker.ContainerSpec{
|
|
Platform: "some-platform",
|
|
Tags: []string{"step", "tags"},
|
|
TeamID: stepMetadata.TeamID,
|
|
ImageSpec: worker.ImageSpec{
|
|
ImageResource: &worker.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "secret-source-param"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Privileged: false,
|
|
},
|
|
Limits: worker.ContainerLimits{
|
|
CPU: &cpu,
|
|
Memory: &memory,
|
|
},
|
|
Dir: "some-artifact-root",
|
|
Env: []string{"SECURE=secret-task-param"},
|
|
Inputs: []worker.InputSource{},
|
|
Outputs: worker.OutputPaths{},
|
|
}))
|
|
Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
|
|
})
|
|
|
|
Context("when rootfs uri is set instead of image resource", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("finds or creates a container", func() {
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, cancel, delegate, owner, containerSpec, actualResourceTypes := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(cancel).ToNot(BeNil())
|
|
Expect(owner).To(Equal(db.NewBuildStepContainerOwner(stepMetadata.BuildID, planID, stepMetadata.TeamID)))
|
|
Expect(delegate).To(Equal(fakeDelegate))
|
|
|
|
Expect(containerSpec).To(Equal(worker.ContainerSpec{
|
|
Platform: "some-platform",
|
|
Tags: []string{"step", "tags"},
|
|
TeamID: stepMetadata.TeamID,
|
|
ImageSpec: worker.ImageSpec{
|
|
ImageURL: "some-image",
|
|
Privileged: false,
|
|
},
|
|
Dir: "some-artifact-root",
|
|
Env: []string{"SOME=params"},
|
|
Inputs: []worker.InputSource{},
|
|
Outputs: worker.OutputPaths{},
|
|
}))
|
|
|
|
Expect(actualResourceTypes).To(Equal(interpolatedResourceTypes))
|
|
})
|
|
})
|
|
|
|
Context("when an exit status is already saved off", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertyStub = func(name string) (string, error) {
|
|
defer GinkgoRecover()
|
|
|
|
switch name {
|
|
case "concourse:exit-status":
|
|
return "123", nil
|
|
default:
|
|
return "", errors.New("unstubbed property: " + name)
|
|
}
|
|
}
|
|
})
|
|
|
|
It("returns no error", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("does not attach to any process", func() {
|
|
Expect(fakeContainer.AttachCallCount()).To(BeZero())
|
|
})
|
|
|
|
It("is not successful as the exit status is nonzero", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
|
|
Context("when outputs are configured and present on the container", func() {
|
|
var (
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeNewlyCreatedVolume1 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume2 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume3 *workerfakes.FakeVolume
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
Outputs: []atc.TaskOutputConfig{
|
|
{Name: "some-output", Path: "some-output-configured-path"},
|
|
{Name: "some-other-output"},
|
|
{Name: "some-trailing-slash-output", Path: "some-output-configured-path-with-trailing-slash/"},
|
|
},
|
|
}
|
|
|
|
fakeNewlyCreatedVolume1 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume1.HandleReturns("some-handle-1")
|
|
fakeNewlyCreatedVolume2 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume2.HandleReturns("some-handle-2")
|
|
fakeNewlyCreatedVolume3 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
It("re-registers the outputs as sources", func() {
|
|
artifactSource1, found := repo.SourceFor("some-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource2, found := repo.SourceFor("some-other-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource3, found := repo.SourceFor("some-trailing-slash-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(ConsistOf(artifactSource1, artifactSource2, artifactSource3))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when a process is still running", func() {
|
|
var fakeProcess *gardenfakes.FakeProcess
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertyReturns("", errors.New("no exit status property"))
|
|
|
|
fakeProcess = new(gardenfakes.FakeProcess)
|
|
fakeContainer.AttachReturns(fakeProcess, nil)
|
|
})
|
|
|
|
Context("when the container does not have task process name as its property", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertyStub = func(propertyName string) (string, error) {
|
|
if propertyName == "concourse:exit-status" {
|
|
return "", errors.New("no exit status property")
|
|
}
|
|
if propertyName == "concourse:task-process" {
|
|
return "", errors.New("property does not exist")
|
|
}
|
|
|
|
panic("unknown property")
|
|
}
|
|
})
|
|
|
|
It("attaches to saved process name", func() {
|
|
Expect(fakeContainer.AttachCallCount()).To(Equal(1))
|
|
|
|
pid, _ := fakeContainer.AttachArgsForCall(0)
|
|
Expect(pid).To(Equal("task"))
|
|
})
|
|
})
|
|
|
|
It("directs the process's stdout/stderr to the io config", func() {
|
|
Expect(fakeContainer.AttachCallCount()).To(Equal(1))
|
|
|
|
_, pio := fakeContainer.AttachArgsForCall(0)
|
|
Expect(pio.Stdout).To(Equal(stdoutBuf))
|
|
Expect(pio.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
})
|
|
|
|
Context("when the process is not already running or exited", func() {
|
|
var fakeProcess *gardenfakes.FakeProcess
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.PropertyReturns("", errors.New("no exit status property"))
|
|
fakeContainer.AttachReturns(nil, errors.New("no garden error type for this :("))
|
|
|
|
fakeProcess = new(gardenfakes.FakeProcess)
|
|
fakeContainer.RunReturns(fakeProcess, nil)
|
|
})
|
|
|
|
Describe("before running a process", func() {
|
|
BeforeEach(func() {
|
|
fakeDelegate.StartingStub = func(lager.Logger, atc.TaskConfig) {
|
|
defer GinkgoRecover()
|
|
Expect(fakeContainer.RunCallCount()).To(BeZero())
|
|
}
|
|
})
|
|
|
|
It("invoked the delegate's Starting callback", func() {
|
|
Expect(fakeDelegate.StartingCallCount()).To(Equal(1))
|
|
})
|
|
})
|
|
|
|
It("runs a process with the config's path and args, in the specified (default) build directory", func() {
|
|
Expect(fakeContainer.RunCallCount()).To(Equal(1))
|
|
|
|
containerSpec, _ := fakeContainer.RunArgsForCall(0)
|
|
Expect(containerSpec.ID).To(Equal("task"))
|
|
Expect(containerSpec.Path).To(Equal("ls"))
|
|
Expect(containerSpec.Args).To(Equal([]string{"some", "args"}))
|
|
Expect(containerSpec.Dir).To(Equal("some-artifact-root"))
|
|
Expect(containerSpec.User).To(BeEmpty())
|
|
Expect(containerSpec.TTY).To(Equal(&garden.TTYSpec{WindowSize: &garden.WindowSize{Columns: 500, Rows: 500}}))
|
|
})
|
|
|
|
It("directs the process's stdout/stderr to the io config", func() {
|
|
Expect(fakeContainer.RunCallCount()).To(Equal(1))
|
|
|
|
_, io := fakeContainer.RunArgsForCall(0)
|
|
Expect(io.Stdout).To(Equal(stdoutBuf))
|
|
Expect(io.Stderr).To(Equal(stderrBuf))
|
|
})
|
|
|
|
Context("when privileged", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Privileged = true
|
|
})
|
|
|
|
It("creates the container privileged", func() {
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec.Privileged).To(BeTrue())
|
|
})
|
|
|
|
It("runs the process as the specified user", func() {
|
|
Expect(fakeContainer.RunCallCount()).To(Equal(1))
|
|
|
|
containerSpec, _ := fakeContainer.RunArgsForCall(0)
|
|
Expect(containerSpec).To(Equal(garden.ProcessSpec{
|
|
ID: "task",
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
Dir: "some-artifact-root",
|
|
TTY: &garden.TTYSpec{WindowSize: &garden.WindowSize{Columns: 500, Rows: 500}},
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("when the configuration specifies paths for inputs", func() {
|
|
var inputSource *workerfakes.FakeArtifactSource
|
|
var otherInputSource *workerfakes.FakeArtifactSource
|
|
|
|
BeforeEach(func() {
|
|
inputSource = new(workerfakes.FakeArtifactSource)
|
|
otherInputSource = new(workerfakes.FakeArtifactSource)
|
|
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
Inputs: []atc.TaskInputConfig{
|
|
{Name: "some-input", Path: "some-input-configured-path"},
|
|
{Name: "some-other-input"},
|
|
},
|
|
}
|
|
})
|
|
|
|
Context("when all inputs are present", func() {
|
|
BeforeEach(func() {
|
|
repo.RegisterSource("some-input", inputSource)
|
|
repo.RegisterSource("some-other-input", otherInputSource)
|
|
})
|
|
|
|
It("creates the container with the inputs configured correctly", func() {
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Inputs).To(HaveLen(2))
|
|
for _, input := range containerSpec.Inputs {
|
|
switch input.DestinationPath() {
|
|
case "some-artifact-root/some-input-configured-path":
|
|
Expect(input.Source()).To(Equal(inputSource))
|
|
case "some-artifact-root/some-other-input":
|
|
Expect(input.Source()).To(Equal(otherInputSource))
|
|
default:
|
|
panic("unknown input: " + input.DestinationPath())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
Context("when any of the inputs are missing", func() {
|
|
BeforeEach(func() {
|
|
repo.RegisterSource("some-input", inputSource)
|
|
})
|
|
|
|
It("returns a MissingInputsError", func() {
|
|
Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{}))
|
|
Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("some-other-input"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when input is remapped", func() {
|
|
var remappedInputSource *workerfakes.FakeArtifactSource
|
|
|
|
BeforeEach(func() {
|
|
remappedInputSource = new(workerfakes.FakeArtifactSource)
|
|
taskPlan.InputMapping = map[string]string{"remapped-input": "remapped-input-src"}
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
Inputs: []atc.TaskInputConfig{
|
|
{Name: "remapped-input"},
|
|
},
|
|
}
|
|
})
|
|
|
|
Context("when all inputs are present in the in source repository", func() {
|
|
BeforeEach(func() {
|
|
repo.RegisterSource("remapped-input-src", remappedInputSource)
|
|
})
|
|
|
|
It("uses remapped input", func() {
|
|
Expect(fakeWorker.FindOrCreateContainerCallCount()).To(Equal(1))
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Inputs).To(HaveLen(1))
|
|
Expect(containerSpec.Inputs[0].Source()).To(Equal(remappedInputSource))
|
|
Expect(containerSpec.Inputs[0].DestinationPath()).To(Equal("some-artifact-root/remapped-input"))
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when any of the inputs are missing", func() {
|
|
It("returns a MissingInputsError", func() {
|
|
Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{}))
|
|
Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("remapped-input-src"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when some inputs are optional", func() {
|
|
var (
|
|
optionalInputSource, optionalInput2Source, requiredInputSource *workerfakes.FakeArtifactSource
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
optionalInputSource = new(workerfakes.FakeArtifactSource)
|
|
optionalInput2Source = new(workerfakes.FakeArtifactSource)
|
|
requiredInputSource = new(workerfakes.FakeArtifactSource)
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
},
|
|
Inputs: []atc.TaskInputConfig{
|
|
{Name: "optional-input", Optional: true},
|
|
{Name: "optional-input-2", Optional: true},
|
|
{Name: "required-input"},
|
|
},
|
|
}
|
|
})
|
|
|
|
Context("when an optional input is missing", func() {
|
|
BeforeEach(func() {
|
|
repo.RegisterSource("required-input", requiredInputSource)
|
|
repo.RegisterSource("optional-input-2", optionalInput2Source)
|
|
})
|
|
|
|
It("runs successfully without the optional input", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Inputs).To(HaveLen(2))
|
|
Expect(containerSpec.Inputs[0].Source()).To(Equal(optionalInput2Source))
|
|
Expect(containerSpec.Inputs[0].DestinationPath()).To(Equal("some-artifact-root/optional-input-2"))
|
|
Expect(containerSpec.Inputs[1].Source()).To(Equal(requiredInputSource))
|
|
Expect(containerSpec.Inputs[1].DestinationPath()).To(Equal("some-artifact-root/required-input"))
|
|
})
|
|
})
|
|
|
|
Context("when a required input is missing", func() {
|
|
BeforeEach(func() {
|
|
repo.RegisterSource("optional-input", optionalInputSource)
|
|
repo.RegisterSource("optional-input-2", optionalInput2Source)
|
|
})
|
|
|
|
It("returns a MissingInputsError", func() {
|
|
Expect(stepErr).To(BeAssignableToTypeOf(exec.MissingInputsError{}))
|
|
Expect(stepErr.(exec.MissingInputsError).Inputs).To(ConsistOf("required-input"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the configuration specifies paths for caches", func() {
|
|
var (
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
},
|
|
Caches: []atc.CacheConfig{
|
|
{Path: "some-path-1"},
|
|
{Path: "some-path-2"},
|
|
},
|
|
}
|
|
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: "some-artifact-root/some-path-1",
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: "some-artifact-root/some-path-2",
|
|
},
|
|
})
|
|
})
|
|
|
|
It("creates the container with the caches in the inputs", func() {
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Inputs).To(HaveLen(2))
|
|
Expect([]string{
|
|
containerSpec.Inputs[0].DestinationPath(),
|
|
containerSpec.Inputs[1].DestinationPath(),
|
|
}).To(ConsistOf(
|
|
"some-artifact-root/some-path-1",
|
|
"some-artifact-root/some-path-2",
|
|
))
|
|
})
|
|
|
|
Context("when task belongs to a job", func() {
|
|
BeforeEach(func() {
|
|
stepMetadata.JobID = 12
|
|
})
|
|
|
|
It("registers cache volumes as task caches", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
Expect(fakeVolume1.InitializeTaskCacheCallCount()).To(Equal(1))
|
|
_, jID, stepName, cachePath, p := fakeVolume1.InitializeTaskCacheArgsForCall(0)
|
|
Expect(jID).To(Equal(stepMetadata.JobID))
|
|
Expect(stepName).To(Equal("some-task"))
|
|
Expect(cachePath).To(Equal("some-path-1"))
|
|
Expect(p).To(Equal(bool(taskPlan.Privileged)))
|
|
|
|
Expect(fakeVolume2.InitializeTaskCacheCallCount()).To(Equal(1))
|
|
_, jID, stepName, cachePath, p = fakeVolume2.InitializeTaskCacheArgsForCall(0)
|
|
Expect(jID).To(Equal(stepMetadata.JobID))
|
|
Expect(stepName).To(Equal("some-task"))
|
|
Expect(cachePath).To(Equal("some-path-2"))
|
|
Expect(p).To(Equal(bool(taskPlan.Privileged)))
|
|
})
|
|
})
|
|
|
|
Context("when task does not belong to job (one-off build)", func() {
|
|
BeforeEach(func() {
|
|
stepMetadata.JobID = 0
|
|
})
|
|
|
|
It("does not initialize caches", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
Expect(fakeVolume1.InitializeTaskCacheCallCount()).To(Equal(0))
|
|
Expect(fakeVolume2.InitializeTaskCacheCallCount()).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the configuration specifies paths for outputs", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
Outputs: []atc.TaskOutputConfig{
|
|
{Name: "some-output", Path: "some-output-configured-path"},
|
|
{Name: "some-other-output"},
|
|
{Name: "some-trailing-slash-output", Path: "some-output-configured-path-with-trailing-slash/"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("configures them appropriately in the container spec", func() {
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Outputs).To(Equal(worker.OutputPaths{
|
|
"some-output": "some-artifact-root/some-output-configured-path/",
|
|
"some-other-output": "some-artifact-root/some-other-output/",
|
|
"some-trailing-slash-output": "some-artifact-root/some-output-configured-path-with-trailing-slash/",
|
|
}))
|
|
})
|
|
|
|
Context("when the process exits 0", func() {
|
|
BeforeEach(func() {
|
|
fakeProcess.WaitReturns(0, nil)
|
|
})
|
|
|
|
It("finishes the task via the delegate", func() {
|
|
Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
|
|
_, status := fakeDelegate.FinishedArgsForCall(0)
|
|
Expect(status).To(Equal(exec.ExitStatus(0)))
|
|
})
|
|
|
|
Describe("the registered sources", func() {
|
|
var (
|
|
artifactSource1 worker.ArtifactSource
|
|
artifactSource2 worker.ArtifactSource
|
|
artifactSource3 worker.ArtifactSource
|
|
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeNewlyCreatedVolume1 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume2 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume3 *workerfakes.FakeVolume
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeNewlyCreatedVolume1 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume1.HandleReturns("some-handle-1")
|
|
fakeNewlyCreatedVolume2 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume2.HandleReturns("some-handle-2")
|
|
fakeNewlyCreatedVolume3 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
var found bool
|
|
artifactSource1, found = repo.SourceFor("some-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource2, found = repo.SourceFor("some-other-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource3, found = repo.SourceFor("some-trailing-slash-output")
|
|
Expect(found).To(BeTrue())
|
|
})
|
|
|
|
It("does not register the task as a source", func() {
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(ConsistOf(artifactSource1, artifactSource2, artifactSource3))
|
|
})
|
|
|
|
Describe("streaming to a destination", func() {
|
|
var streamedOut io.ReadCloser
|
|
var fakeDestination *workerfakes.FakeArtifactDestination
|
|
|
|
BeforeEach(func() {
|
|
fakeDestination = new(workerfakes.FakeArtifactDestination)
|
|
|
|
streamedOut = gbytes.NewBuffer()
|
|
fakeVolume1.StreamOutReturns(streamedOut, nil)
|
|
})
|
|
|
|
It("passes existing output volumes to the resource", func() {
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.Outputs).To(Equal(worker.OutputPaths{
|
|
"some-output": "some-artifact-root/some-output-configured-path/",
|
|
"some-other-output": "some-artifact-root/some-other-output/",
|
|
"some-trailing-slash-output": "some-artifact-root/some-output-configured-path-with-trailing-slash/",
|
|
}))
|
|
})
|
|
|
|
It("streams the data from the volumes to the destination", func() {
|
|
err := artifactSource1.StreamTo(logger, fakeDestination)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(fakeVolume1.StreamOutCallCount()).To(Equal(1))
|
|
path := fakeVolume1.StreamOutArgsForCall(0)
|
|
Expect(path).To(Equal("."))
|
|
|
|
Expect(fakeDestination.StreamInCallCount()).To(Equal(1))
|
|
dest, src := fakeDestination.StreamInArgsForCall(0)
|
|
Expect(dest).To(Equal("."))
|
|
Expect(src).To(Equal(streamedOut))
|
|
})
|
|
})
|
|
|
|
Describe("streaming a file out", func() {
|
|
Context("when the container can stream out", func() {
|
|
var (
|
|
fileContent = "file-content"
|
|
|
|
tgzBuffer *gbytes.Buffer
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
tgzBuffer = gbytes.NewBuffer()
|
|
fakeVolume1.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 := artifactSource1.StreamFile(logger, "some-path")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(ioutil.ReadAll(reader)).To(Equal([]byte(fileContent)))
|
|
|
|
path := fakeVolume1.StreamOutArgsForCall(0)
|
|
Expect(path).To(Equal("some-path"))
|
|
})
|
|
|
|
Describe("closing the stream", func() {
|
|
It("closes the stream from the versioned source", func() {
|
|
reader, err := artifactSource1.StreamFile(logger, "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 := artifactSource1.StreamFile(logger, "some-path")
|
|
Expect(err).To(MatchError(exec.FileNotFoundError{Path: "some-path"}))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the volume cannot stream out", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeVolume1.StreamOutReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
_, err := artifactSource1.StreamFile(logger, "some-path")
|
|
Expect(err).To(Equal(disaster))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyReturns(nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:exit-status" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(stepErr).To(Equal(context.Canceled))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(stepErr).To(Equal(context.Canceled))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when volume mounts are present on the container", func() {
|
|
var (
|
|
fakeMountPath1 string = "some-artifact-root/some-output-configured-path/"
|
|
fakeMountPath2 string = "some-artifact-root/some-other-output/"
|
|
fakeMountPath3 string = "some-artifact-root/some-output-configured-path-with-trailing-slash/"
|
|
|
|
fakeNewlyCreatedVolume1 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume2 *workerfakes.FakeVolume
|
|
fakeNewlyCreatedVolume3 *workerfakes.FakeVolume
|
|
|
|
fakeVolume1 *workerfakes.FakeVolume
|
|
fakeVolume2 *workerfakes.FakeVolume
|
|
fakeVolume3 *workerfakes.FakeVolume
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
fakeNewlyCreatedVolume1 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume1.HandleReturns("some-handle-1")
|
|
fakeNewlyCreatedVolume2 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume2.HandleReturns("some-handle-2")
|
|
fakeNewlyCreatedVolume3 = new(workerfakes.FakeVolume)
|
|
fakeNewlyCreatedVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeVolume1 = new(workerfakes.FakeVolume)
|
|
fakeVolume1.HandleReturns("some-handle-1")
|
|
fakeVolume2 = new(workerfakes.FakeVolume)
|
|
fakeVolume2.HandleReturns("some-handle-2")
|
|
fakeVolume3 = new(workerfakes.FakeVolume)
|
|
fakeVolume3.HandleReturns("some-handle-3")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume1,
|
|
MountPath: fakeMountPath1,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume2,
|
|
MountPath: fakeMountPath2,
|
|
},
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume3,
|
|
MountPath: fakeMountPath3,
|
|
},
|
|
})
|
|
})
|
|
|
|
It("registers the outputs as sources", func() {
|
|
cancel()
|
|
Expect(stepErr).To(Equal(context.Canceled))
|
|
|
|
artifactSource1, found := repo.SourceFor("some-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource2, found := repo.SourceFor("some-other-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
artifactSource3, found := repo.SourceFor("some-trailing-slash-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(ConsistOf(artifactSource1, artifactSource2, artifactSource3))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when output is remapped", func() {
|
|
var (
|
|
fakeMountPath string = "some-artifact-root/generic-remapped-output/"
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
taskPlan.OutputMapping = map[string]string{"generic-remapped-output": "specific-remapped-output"}
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
},
|
|
Outputs: []atc.TaskOutputConfig{
|
|
{Name: "generic-remapped-output"},
|
|
},
|
|
}
|
|
|
|
fakeProcess.WaitReturns(0, nil)
|
|
|
|
fakeVolume := new(workerfakes.FakeVolume)
|
|
fakeVolume.HandleReturns("some-handle")
|
|
|
|
fakeContainer.VolumeMountsReturns([]worker.VolumeMount{
|
|
worker.VolumeMount{
|
|
Volume: fakeVolume,
|
|
MountPath: fakeMountPath,
|
|
},
|
|
})
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("registers the outputs as sources with specific name", func() {
|
|
artifactSource, found := repo.SourceFor("specific-remapped-output")
|
|
Expect(found).To(BeTrue())
|
|
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(ConsistOf(artifactSource))
|
|
})
|
|
})
|
|
|
|
Context("when an image artifact name is specified", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.ImageArtifactName = "some-image-artifact"
|
|
|
|
fakeProcess.WaitReturns(0, nil)
|
|
})
|
|
|
|
Context("when the image artifact is registered in the source repo", func() {
|
|
var imageArtifactSource *workerfakes.FakeArtifactSource
|
|
|
|
BeforeEach(func() {
|
|
imageArtifactSource = new(workerfakes.FakeArtifactSource)
|
|
repo.RegisterSource("some-image-artifact", imageArtifactSource)
|
|
})
|
|
|
|
It("chooses a worker and creates the container with the image artifact source", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ImageArtifactSource: imageArtifactSource,
|
|
}))
|
|
|
|
Expect(workerSpec.ResourceType).To(Equal(""))
|
|
})
|
|
|
|
Describe("when task config specifies image and/or image resource as well as image artifact", func() {
|
|
Context("when streaming the metadata from the worker succeeds", func() {
|
|
var metadataReader io.ReadCloser
|
|
BeforeEach(func() {
|
|
metadataReader = ioutil.NopCloser(strings.NewReader("some-tar-contents"))
|
|
imageArtifactSource.StreamFileReturns(metadataReader, nil)
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
|
|
Context("when the task config also specifies image", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("still chooses a worker and creates the container with the volume and a metadata stream", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ImageArtifactSource: imageArtifactSource,
|
|
}))
|
|
|
|
Expect(workerSpec.ResourceType).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Context("when the task config also specifies image_resource", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
ImageResource: &atc.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "super-secret-source"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("still chooses a worker and creates the container with the volume and a metadata stream", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ImageArtifactSource: imageArtifactSource,
|
|
}))
|
|
|
|
Expect(workerSpec.ResourceType).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Context("when the task config also specifies image and image_resource", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
ImageResource: &atc.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "super-secret-source"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("still chooses a worker and creates the container with the volume and a metadata stream", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec).To(Equal(worker.ImageSpec{
|
|
ImageArtifactSource: imageArtifactSource,
|
|
}))
|
|
Expect(workerSpec.ResourceType).To(Equal(""))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the image artifact is NOT registered in the source repo", func() {
|
|
It("returns a MissingTaskImageSourceError", func() {
|
|
Expect(stepErr).To(Equal(exec.MissingTaskImageSourceError{"some-image-artifact"}))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the image_resource is specified (even if RootfsURI is configured)", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
ImageResource: &atc.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "super-secret-source"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
},
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("creates the specs with the image resource", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec.ImageResource).To(Equal(&worker.ImageResource{
|
|
Type: "docker",
|
|
Source: atc.Source{"some": "super-secret-source"},
|
|
Params: &atc.Params{"some": "params"},
|
|
Version: &atc.Version{"some": "version"},
|
|
}))
|
|
|
|
Expect(workerSpec).To(Equal(worker.WorkerSpec{
|
|
TeamID: 123,
|
|
Platform: "some-platform",
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
Tags: []string{"step", "tags"},
|
|
ResourceType: "docker",
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("when the RootfsURI is configured", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config = &atc.TaskConfig{
|
|
Platform: "some-platform",
|
|
RootfsURI: "some-image",
|
|
Params: map[string]string{"SOME": "params"},
|
|
Run: atc.TaskRunConfig{
|
|
Path: "ls",
|
|
Args: []string{"some", "args"},
|
|
},
|
|
}
|
|
})
|
|
|
|
It("creates the specs with the image resource", func() {
|
|
_, _, _, containerSpec, _, workerSpec, _ := fakePool.FindOrChooseWorkerForContainerArgsForCall(0)
|
|
Expect(containerSpec.ImageSpec.ImageURL).To(Equal("some-image"))
|
|
|
|
Expect(workerSpec).To(Equal(worker.WorkerSpec{
|
|
TeamID: 123,
|
|
Platform: "some-platform",
|
|
ResourceTypes: interpolatedResourceTypes,
|
|
Tags: []string{"step", "tags"},
|
|
}))
|
|
})
|
|
})
|
|
|
|
Context("when a run dir is specified", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config.Run.Dir = "/some/dir"
|
|
})
|
|
|
|
It("runs a process in the specified (custom) directory", func() {
|
|
containerSpec, _ := fakeContainer.RunArgsForCall(0)
|
|
Expect(containerSpec.Dir).To(Equal("some-artifact-root/some/dir"))
|
|
})
|
|
})
|
|
|
|
Context("when a run user is specified", func() {
|
|
BeforeEach(func() {
|
|
taskPlan.Config.Run.User = "some-user"
|
|
})
|
|
|
|
It("adds the user to the container spec", func() {
|
|
_, _, _, _, containerSpec, _ := fakeWorker.FindOrCreateContainerArgsForCall(0)
|
|
Expect(containerSpec.User).To(Equal("some-user"))
|
|
})
|
|
|
|
It("doesn't bother adding the user to the run spec", func() {
|
|
containerSpec, _ := fakeContainer.RunArgsForCall(0)
|
|
Expect(containerSpec.User).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("when the process exits 0", func() {
|
|
BeforeEach(func() {
|
|
fakeProcess.WaitReturns(0, nil)
|
|
})
|
|
|
|
It("saves the exit status property", func() {
|
|
Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1))
|
|
|
|
name, value := fakeContainer.SetPropertyArgsForCall(0)
|
|
Expect(name).To(Equal("concourse:exit-status"))
|
|
Expect(value).To(Equal("0"))
|
|
})
|
|
|
|
It("is successful", func() {
|
|
Expect(stepErr).To(BeNil())
|
|
Expect(taskStep.Succeeded()).To(BeTrue())
|
|
})
|
|
|
|
It("doesn't register a source", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(BeEmpty())
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyReturns(nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:exit-status" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the process exits nonzero", func() {
|
|
BeforeEach(func() {
|
|
fakeProcess.WaitReturns(1, nil)
|
|
})
|
|
|
|
It("finishes the task via the delegate", func() {
|
|
Expect(fakeDelegate.FinishedCallCount()).To(Equal(1))
|
|
_, status := fakeDelegate.FinishedArgsForCall(0)
|
|
Expect(status).To(Equal(exec.ExitStatus(1)))
|
|
})
|
|
|
|
It("saves the exit status property", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
Expect(fakeContainer.SetPropertyCallCount()).To(Equal(1))
|
|
|
|
name, value := fakeContainer.SetPropertyArgsForCall(0)
|
|
Expect(name).To(Equal("concourse:exit-status"))
|
|
Expect(value).To(Equal("1"))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
|
|
Context("when saving the exit status succeeds", func() {
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyReturns(nil)
|
|
})
|
|
|
|
It("returns successfully", func() {
|
|
Expect(stepErr).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("when saving the exit status fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.SetPropertyStub = func(name string, value string) error {
|
|
defer GinkgoRecover()
|
|
|
|
if name == "concourse:exit-status" {
|
|
return disaster
|
|
}
|
|
|
|
return nil
|
|
}
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when waiting on the process fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeProcess.WaitReturns(0, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when the process is interrupted", func() {
|
|
var stopped chan struct{}
|
|
BeforeEach(func() {
|
|
stopped = make(chan struct{})
|
|
|
|
fakeProcess.WaitStub = func() (int, error) {
|
|
defer GinkgoRecover()
|
|
|
|
<-stopped
|
|
return 128 + 15, nil
|
|
}
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return nil
|
|
}
|
|
|
|
cancel()
|
|
})
|
|
|
|
It("stops the container", func() {
|
|
Expect(fakeContainer.StopCallCount()).To(Equal(1))
|
|
Expect(fakeContainer.StopArgsForCall(0)).To(BeFalse())
|
|
Expect(stepErr).To(Equal(context.Canceled))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
|
|
Context("when container.stop returns an error", func() {
|
|
var disaster error
|
|
|
|
BeforeEach(func() {
|
|
disaster = errors.New("gotta get away")
|
|
|
|
fakeContainer.StopStub = func(bool) error {
|
|
close(stopped)
|
|
return disaster
|
|
}
|
|
})
|
|
|
|
It("doesn't return the error", func() {
|
|
Expect(stepErr).To(Equal(context.Canceled))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
It("doesn't register a source", func() {
|
|
sourceMap := repo.AsMap()
|
|
Expect(sourceMap).To(BeEmpty())
|
|
})
|
|
})
|
|
|
|
Context("when running the task's script fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeContainer.RunReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when creating the container fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakeWorker.FindOrCreateContainerReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when finding or choosing the worker fails", func() {
|
|
disaster := errors.New("nope")
|
|
|
|
BeforeEach(func() {
|
|
fakePool.FindOrChooseWorkerForContainerReturns(nil, disaster)
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(Equal(disaster))
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when missing the platform", func() {
|
|
|
|
BeforeEach(func() {
|
|
taskPlan.Config.Platform = ""
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(HaveOccurred())
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
|
|
Context("when missing the path to the executable", func() {
|
|
|
|
BeforeEach(func() {
|
|
taskPlan.Config.Run.Path = ""
|
|
})
|
|
|
|
It("returns the error", func() {
|
|
Expect(stepErr).To(HaveOccurred())
|
|
})
|
|
|
|
It("is not successful", func() {
|
|
Expect(taskStep.Succeeded()).To(BeFalse())
|
|
})
|
|
})
|
|
})
|
|
})
|