1package exec 2 3import ( 4 "context" 5 "io" 6 7 "code.cloudfoundry.org/lager" 8 "code.cloudfoundry.org/lager/lagerctx" 9 "github.com/concourse/concourse/atc" 10 "github.com/concourse/concourse/atc/creds" 11 "github.com/concourse/concourse/atc/db" 12 "github.com/concourse/concourse/atc/resource" 13 "github.com/concourse/concourse/atc/runtime" 14 "github.com/concourse/concourse/atc/worker" 15 "github.com/concourse/concourse/tracing" 16 "github.com/concourse/concourse/vars" 17) 18 19//go:generate counterfeiter . PutDelegate 20 21type PutDelegate interface { 22 ImageVersionDetermined(db.UsedResourceCache) error 23 RedactImageSource(source atc.Source) (atc.Source, error) 24 25 Stdout() io.Writer 26 Stderr() io.Writer 27 28 Variables() *vars.BuildVariables 29 30 Initializing(lager.Logger) 31 Starting(lager.Logger) 32 Finished(lager.Logger, ExitStatus, runtime.VersionResult) 33 SelectedWorker(lager.Logger, string) 34 Errored(lager.Logger, string) 35 36 SaveOutput(lager.Logger, atc.PutPlan, atc.Source, atc.VersionedResourceTypes, runtime.VersionResult) 37} 38 39// PutStep produces a resource version using preconfigured params and any data 40// available in the worker.ArtifactRepository. 41type PutStep struct { 42 planID atc.PlanID 43 plan atc.PutPlan 44 metadata StepMetadata 45 containerMetadata db.ContainerMetadata 46 resourceFactory resource.ResourceFactory 47 resourceConfigFactory db.ResourceConfigFactory 48 strategy worker.ContainerPlacementStrategy 49 workerClient worker.Client 50 delegate PutDelegate 51 succeeded bool 52} 53 54func NewPutStep( 55 planID atc.PlanID, 56 plan atc.PutPlan, 57 metadata StepMetadata, 58 containerMetadata db.ContainerMetadata, 59 resourceFactory resource.ResourceFactory, 60 resourceConfigFactory db.ResourceConfigFactory, 61 strategy worker.ContainerPlacementStrategy, 62 workerClient worker.Client, 63 delegate PutDelegate, 64) Step { 65 return &PutStep{ 66 planID: planID, 67 plan: plan, 68 metadata: metadata, 69 containerMetadata: containerMetadata, 70 resourceFactory: resourceFactory, 71 resourceConfigFactory: resourceConfigFactory, 72 workerClient: workerClient, 73 strategy: strategy, 74 delegate: delegate, 75 } 76} 77 78// Run chooses a worker that supports the step's resource type and creates a 79// container. 80// 81// All worker.ArtifactSources present in the worker.ArtifactRepository are then brought into 82// the container, using volumes if possible, and streaming content over if not. 83// 84// The resource's put script is then invoked. If the context is canceled, the 85// script will be interrupted. 86func (step *PutStep) Run(ctx context.Context, state RunState) error { 87 ctx, span := tracing.StartSpan(ctx, "put", tracing.Attrs{ 88 "team": step.metadata.TeamName, 89 "pipeline": step.metadata.PipelineName, 90 "job": step.metadata.JobName, 91 "build": step.metadata.BuildName, 92 "resource": step.plan.Resource, 93 "name": step.plan.Name, 94 }) 95 96 err := step.run(ctx, state) 97 tracing.End(span, err) 98 99 return err 100} 101 102func (step *PutStep) run(ctx context.Context, state RunState) error { 103 logger := lagerctx.FromContext(ctx) 104 logger = logger.Session("put-step", lager.Data{ 105 "step-name": step.plan.Name, 106 "job-id": step.metadata.JobID, 107 }) 108 109 step.delegate.Initializing(logger) 110 111 variables := step.delegate.Variables() 112 113 source, err := creds.NewSource(variables, step.plan.Source).Evaluate() 114 if err != nil { 115 return err 116 } 117 118 params, err := creds.NewParams(variables, step.plan.Params).Evaluate() 119 if err != nil { 120 return err 121 } 122 123 resourceTypes, err := creds.NewVersionedResourceTypes(variables, step.plan.VersionedResourceTypes).Evaluate() 124 if err != nil { 125 return err 126 } 127 128 var putInputs PutInputs 129 if step.plan.Inputs == nil { 130 // Put step defaults to all inputs if not specified 131 putInputs = NewAllInputs() 132 } else if step.plan.Inputs.All { 133 putInputs = NewAllInputs() 134 } else if step.plan.Inputs.Detect { 135 putInputs = NewDetectInputs(step.plan.Params) 136 } else { 137 // Covers both cases where inputs are specified and when there are no 138 // inputs specified and "all" field is given a false boolean, which will 139 // result in no inputs attached 140 putInputs = NewSpecificInputs(step.plan.Inputs.Specified) 141 } 142 143 containerInputs, err := putInputs.FindAll(state.ArtifactRepository()) 144 if err != nil { 145 return err 146 } 147 148 containerSpec := worker.ContainerSpec{ 149 ImageSpec: worker.ImageSpec{ 150 ResourceType: step.plan.Type, 151 }, 152 Tags: step.plan.Tags, 153 TeamID: step.metadata.TeamID, 154 155 Dir: step.containerMetadata.WorkingDirectory, 156 157 Env: step.metadata.Env(), 158 159 ArtifactByPath: containerInputs, 160 } 161 tracing.Inject(ctx, &containerSpec) 162 163 workerSpec := worker.WorkerSpec{ 164 ResourceType: step.plan.Type, 165 Tags: step.plan.Tags, 166 TeamID: step.metadata.TeamID, 167 ResourceTypes: resourceTypes, 168 } 169 170 owner := db.NewBuildStepContainerOwner(step.metadata.BuildID, step.planID, step.metadata.TeamID) 171 172 containerSpec.BindMounts = []worker.BindMountSource{ 173 &worker.CertsVolumeMount{Logger: logger}, 174 } 175 176 imageSpec := worker.ImageFetcherSpec{ 177 ResourceTypes: resourceTypes, 178 Delegate: step.delegate, 179 } 180 181 processSpec := runtime.ProcessSpec{ 182 Path: "/opt/resource/out", 183 Args: []string{resource.ResourcesDir("put")}, 184 StdoutWriter: step.delegate.Stdout(), 185 StderrWriter: step.delegate.Stderr(), 186 } 187 188 resourceToPut := step.resourceFactory.NewResource(source, params, nil) 189 190 result, err := step.workerClient.RunPutStep( 191 ctx, 192 logger, 193 owner, 194 containerSpec, 195 workerSpec, 196 step.strategy, 197 step.containerMetadata, 198 imageSpec, 199 processSpec, 200 step.delegate, 201 resourceToPut, 202 ) 203 if err != nil { 204 logger.Error("failed-to-put-resource", err) 205 return err 206 } 207 208 if result.ExitStatus != 0 { 209 step.delegate.Finished(logger, ExitStatus(result.ExitStatus), runtime.VersionResult{}) 210 return nil 211 } 212 213 versionResult := result.VersionResult 214 // step.plan.Resource maps to an actual resource that may have been used outside of a pipeline context. 215 // Hence, if it was used outside the pipeline context, we don't want to save the output. 216 if step.plan.Resource != "" { 217 step.delegate.SaveOutput(logger, step.plan, source, resourceTypes, versionResult) 218 } 219 220 state.StoreResult(step.planID, versionResult) 221 222 step.succeeded = true 223 224 step.delegate.Finished(logger, 0, versionResult) 225 226 return nil 227 228} 229 230// Succeeded returns true if the resource script exited successfully. 231func (step *PutStep) Succeeded() bool { 232 return step.succeeded 233} 234