1package executehelpers
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"path/filepath"
8	"sync"
9
10	"github.com/concourse/concourse/atc"
11	"github.com/concourse/concourse/fly/commands/internal/flaghelpers"
12	"github.com/concourse/concourse/fly/ui/progress"
13	"github.com/concourse/concourse/go-concourse/concourse"
14	"github.com/vbauerster/mpb/v4"
15)
16
17type Input struct {
18	Name string
19	Path string
20
21	Plan atc.Plan
22}
23
24func DetermineInputs(
25	fact atc.PlanFactory,
26	team concourse.Team,
27	taskInputs []atc.TaskInputConfig,
28	localInputMappings []flaghelpers.InputPairFlag,
29	userInputMappings []flaghelpers.InputMappingPairFlag,
30	jobInputImage string,
31	inputsFrom flaghelpers.JobFlag,
32	includeIgnored bool,
33	platform string,
34	tags []string,
35) ([]Input, map[string]string, *atc.ImageResource, atc.VersionedResourceTypes, error) {
36	inputMappings := ConvertInputMappings(userInputMappings)
37
38	err := CheckForUnknownInputMappings(localInputMappings, taskInputs)
39	if err != nil {
40		return nil, nil, nil, nil, err
41	}
42
43	err = CheckForInputType(localInputMappings)
44	if err != nil {
45		return nil, nil, nil, nil, err
46	}
47
48	if inputsFrom.PipelineName == "" && inputsFrom.JobName == "" {
49		wd, err := os.Getwd()
50		if err != nil {
51			return nil, nil, nil, nil, err
52		}
53
54		required := false
55		for _, input := range taskInputs {
56			if input.Name == filepath.Base(wd) {
57				required = true
58				break
59			}
60		}
61
62		provided := false
63		for _, input := range localInputMappings {
64			if input.Name == filepath.Base(wd) {
65				provided = true
66				break
67			}
68		}
69
70		if required && !provided {
71			localInputMappings = append(localInputMappings, flaghelpers.InputPairFlag{
72				Name: filepath.Base(wd),
73				Path: ".",
74			})
75		}
76	}
77
78	inputsFromLocal, err := GenerateLocalInputs(fact, team, localInputMappings, includeIgnored, platform, tags)
79	if err != nil {
80		return nil, nil, nil, nil, err
81	}
82
83	inputsFromJob, imageResourceFromJob, resourceTypes, err := FetchInputsFromJob(fact, team, inputsFrom, jobInputImage)
84	if err != nil {
85		return nil, nil, nil, nil, err
86	}
87
88	inputs := []Input{}
89	for _, taskInput := range taskInputs {
90		input, found := inputsFromLocal[taskInput.Name]
91		if !found {
92
93			jobInputName := taskInput.Name
94			if name, ok := inputMappings[taskInput.Name]; ok {
95				jobInputName = name
96			}
97
98			input, found = inputsFromJob[jobInputName]
99			if !found {
100				if taskInput.Optional {
101					continue
102				} else {
103					return nil, nil, nil, nil, fmt.Errorf("missing required input `%s`", taskInput.Name)
104				}
105			}
106		}
107
108		inputs = append(inputs, input)
109	}
110
111	return inputs, inputMappings, imageResourceFromJob, resourceTypes, nil
112}
113
114func ConvertInputMappings(variables []flaghelpers.InputMappingPairFlag) map[string]string {
115	inputMappings := map[string]string{}
116	for _, flag := range variables {
117		inputMappings[flag.Name] = flag.Value
118	}
119	return inputMappings
120}
121
122func CheckForInputType(inputMaps []flaghelpers.InputPairFlag) error {
123	for _, i := range inputMaps {
124		if i.Path != "" {
125			fi, err := os.Stat(i.Path)
126			if err != nil {
127				return err
128			}
129			switch mode := fi.Mode(); {
130			case mode.IsRegular():
131				return errors.New(i.Path + " not a folder")
132			}
133		}
134	}
135	return nil
136}
137
138func CheckForUnknownInputMappings(inputMappings []flaghelpers.InputPairFlag, validInputs []atc.TaskInputConfig) error {
139	for _, inputMapping := range inputMappings {
140		if !TaskInputsContainsName(validInputs, inputMapping.Name) {
141			return fmt.Errorf("unknown input `%s`", inputMapping.Name)
142		}
143	}
144	return nil
145}
146
147func TaskInputsContainsName(inputs []atc.TaskInputConfig, name string) bool {
148	for _, input := range inputs {
149		if input.Name == name {
150			return true
151		}
152	}
153	return false
154}
155
156func GenerateLocalInputs(
157	fact atc.PlanFactory,
158	team concourse.Team,
159	inputMappings []flaghelpers.InputPairFlag,
160	includeIgnored bool,
161	platform string,
162	tags []string,
163) (map[string]Input, error) {
164	inputs := map[string]Input{}
165
166	artifacts := new(sync.Map)
167
168	prog := progress.New()
169
170	for _, mapping := range inputMappings {
171		name := mapping.Name
172		path := mapping.Path
173
174		prog.Go("uploading "+name, func(bar *mpb.Bar) error {
175			artifact, err := Upload(bar, team, path, includeIgnored, platform, tags)
176			if err != nil {
177				return err
178			}
179
180			artifacts.Store(name, artifact)
181
182			return nil
183		})
184	}
185
186	err := prog.Wait()
187	if err != nil {
188		return nil, err
189	}
190
191	for _, mapping := range inputMappings {
192		val, _ := artifacts.Load(mapping.Name)
193
194		inputs[mapping.Name] = Input{
195			Name: mapping.Name,
196			Path: mapping.Path,
197			Plan: fact.NewPlan(atc.ArtifactInputPlan{
198				ArtifactID: val.(atc.WorkerArtifact).ID,
199				Name:       mapping.Name,
200			}),
201		}
202	}
203
204	return inputs, nil
205}
206
207func FetchInputsFromJob(fact atc.PlanFactory, team concourse.Team, inputsFrom flaghelpers.JobFlag, imageName string) (map[string]Input, *atc.ImageResource, atc.VersionedResourceTypes, error) {
208	kvMap := map[string]Input{}
209
210	if inputsFrom.PipelineName == "" && inputsFrom.JobName == "" {
211		return kvMap, nil, nil, nil
212	}
213
214	buildInputs, found, err := team.BuildInputsForJob(inputsFrom.PipelineName, inputsFrom.JobName)
215	if err != nil {
216		return nil, nil, nil, err
217	}
218
219	if !found {
220		return nil, nil, nil, fmt.Errorf("build inputs for %s/%s not found", inputsFrom.PipelineName, inputsFrom.JobName)
221	}
222
223	versionedResourceTypes, found, err := team.VersionedResourceTypes(inputsFrom.PipelineName)
224	if err != nil {
225		return nil, nil, nil, err
226	}
227
228	if !found {
229		return nil, nil, nil, fmt.Errorf("versioned resource types of %s not found", inputsFrom.PipelineName)
230	}
231
232	var imageResource *atc.ImageResource
233	if imageName != "" {
234		imageResource, found, err = FetchImageResourceFromJobInputs(buildInputs, imageName)
235		if err != nil {
236			return nil, nil, nil, err
237		}
238
239		if !found {
240			return nil, nil, nil, fmt.Errorf("image resource %s not found", imageName)
241		}
242	}
243
244	for _, buildInput := range buildInputs {
245		version := buildInput.Version
246
247		kvMap[buildInput.Name] = Input{
248			Name: buildInput.Name,
249
250			Plan: fact.NewPlan(atc.GetPlan{
251				Name:                   buildInput.Name,
252				Type:                   buildInput.Type,
253				Source:                 buildInput.Source,
254				Version:                &version,
255				Params:                 buildInput.Params,
256				Tags:                   buildInput.Tags,
257				VersionedResourceTypes: versionedResourceTypes,
258			}),
259		}
260	}
261
262	return kvMap, imageResource, versionedResourceTypes, nil
263}
264
265func FetchImageResourceFromJobInputs(inputs []atc.BuildInput, imageName string) (*atc.ImageResource, bool, error) {
266
267	for _, input := range inputs {
268		if input.Name == imageName {
269			version := input.Version
270			imageResource := atc.ImageResource{
271				Type:    input.Type,
272				Source:  input.Source,
273				Version: version,
274				Params:  input.Params,
275			}
276			return &imageResource, true, nil
277		}
278	}
279
280	return nil, false, nil
281}
282