concourse/atc/exec/put_step.go

218 lines
5.5 KiB
Go

package exec
import (
"context"
"code.cloudfoundry.org/lager"
"code.cloudfoundry.org/lager/lagerctx"
"github.com/concourse/concourse/atc"
"github.com/concourse/concourse/atc/creds"
"github.com/concourse/concourse/atc/db"
"github.com/concourse/concourse/atc/resource"
"github.com/concourse/concourse/atc/worker"
)
//go:generate counterfeiter . PutDelegate
type PutDelegate interface {
BuildStepDelegate
Initializing(lager.Logger)
Starting(lager.Logger)
Finished(lager.Logger, ExitStatus, VersionInfo)
SaveOutput(lager.Logger, atc.PutPlan, atc.Source, atc.VersionedResourceTypes, VersionInfo)
}
// PutStep produces a resource version using preconfigured params and any data
// available in the worker.ArtifactRepository.
type PutStep struct {
planID atc.PlanID
plan atc.PutPlan
metadata StepMetadata
containerMetadata db.ContainerMetadata
secrets creds.Secrets
resourceFactory resource.ResourceFactory
resourceConfigFactory db.ResourceConfigFactory
strategy worker.ContainerPlacementStrategy
pool worker.Pool
delegate PutDelegate
succeeded bool
}
func NewPutStep(
planID atc.PlanID,
plan atc.PutPlan,
metadata StepMetadata,
containerMetadata db.ContainerMetadata,
secrets creds.Secrets,
resourceFactory resource.ResourceFactory,
resourceConfigFactory db.ResourceConfigFactory,
strategy worker.ContainerPlacementStrategy,
pool worker.Pool,
delegate PutDelegate,
) *PutStep {
return &PutStep{
planID: planID,
plan: plan,
metadata: metadata,
containerMetadata: containerMetadata,
secrets: secrets,
resourceFactory: resourceFactory,
resourceConfigFactory: resourceConfigFactory,
pool: pool,
strategy: strategy,
delegate: delegate,
}
}
// Run chooses a worker that supports the step's resource type and creates a
// container.
//
// All worker.ArtifactSources present in the worker.ArtifactRepository are then brought into
// the container, using volumes if possible, and streaming content over if not.
//
// The resource's put script is then invoked. If the context is canceled, the
// script will be interrupted.
func (step *PutStep) Run(ctx context.Context, state RunState) error {
logger := lagerctx.FromContext(ctx)
logger = logger.Session("put-step", lager.Data{
"step-name": step.plan.Name,
"job-id": step.metadata.JobID,
})
step.delegate.Initializing(logger)
variables := creds.NewVariables(step.secrets, step.metadata.TeamName, step.metadata.PipelineName)
source, err := creds.NewSource(variables, step.plan.Source).Evaluate()
if err != nil {
return err
}
params, err := creds.NewParams(variables, step.plan.Params).Evaluate()
if err != nil {
return err
}
resourceTypes, err := creds.NewVersionedResourceTypes(variables, step.plan.VersionedResourceTypes).Evaluate()
if err != nil {
return err
}
var putInputs PutInputs
if step.plan.Inputs == nil {
// Put step defaults to all inputs if not specified
putInputs = NewAllInputs()
} else if step.plan.Inputs.All {
putInputs = NewAllInputs()
} else {
// Covers both cases where inputs are specified and when there are no
// inputs specified and "all" field is given a false boolean, which will
// result in no inputs attached
putInputs = NewSpecificInputs(step.plan.Inputs.Specified)
}
containerInputs, err := putInputs.FindAll(state.Artifacts())
if err != nil {
return err
}
containerSpec := worker.ContainerSpec{
ImageSpec: worker.ImageSpec{
ResourceType: step.plan.Type,
},
Tags: step.plan.Tags,
TeamID: step.metadata.TeamID,
Dir: step.containerMetadata.WorkingDirectory,
Env: step.metadata.Env(),
Inputs: containerInputs,
}
workerSpec := worker.WorkerSpec{
ResourceType: step.plan.Type,
Tags: step.plan.Tags,
TeamID: step.metadata.TeamID,
ResourceTypes: resourceTypes,
}
owner := db.NewBuildStepContainerOwner(step.metadata.BuildID, step.planID, step.metadata.TeamID)
chosenWorker, err := step.pool.FindOrChooseWorkerForContainer(
ctx,
logger,
owner,
containerSpec,
step.containerMetadata,
workerSpec,
step.strategy,
)
if err != nil {
return err
}
containerSpec.BindMounts = []worker.BindMountSource{
&worker.CertsVolumeMount{Logger: logger},
}
container, err := chosenWorker.FindOrCreateContainer(
ctx,
logger,
step.delegate,
owner,
containerSpec,
resourceTypes,
)
if err != nil {
return err
}
step.delegate.Starting(logger)
putResource := step.resourceFactory.NewResourceForContainer(container)
versionedSource, err := putResource.Put(
ctx,
resource.IOConfig{
Stdout: step.delegate.Stdout(),
Stderr: step.delegate.Stderr(),
},
source,
params,
)
if err != nil {
logger.Error("failed-to-put-resource", err)
if err, ok := err.(resource.ErrResourceScriptFailed); ok {
step.delegate.Finished(logger, ExitStatus(err.ExitStatus), VersionInfo{})
return nil
}
return err
}
versionInfo := VersionInfo{
Version: versionedSource.Version(),
Metadata: versionedSource.Metadata(),
}
if step.plan.Resource != "" {
step.delegate.SaveOutput(logger, step.plan, source, resourceTypes, versionInfo)
}
state.StoreResult(step.planID, versionInfo)
step.succeeded = true
step.delegate.Finished(logger, 0, versionInfo)
return nil
}
// Succeeded returns true if the resource script exited successfully.
func (step *PutStep) Succeeded() bool {
return step.succeeded
}