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