1package exec_test
2
3import (
4	"context"
5	"errors"
6	"fmt"
7
8	"code.cloudfoundry.org/lager/lagertest"
9	"github.com/concourse/baggageclaim"
10	"github.com/concourse/concourse/atc"
11	. "github.com/concourse/concourse/atc/exec"
12	"github.com/concourse/concourse/atc/exec/build"
13	"github.com/concourse/concourse/atc/exec/execfakes"
14	"github.com/concourse/concourse/atc/runtime/runtimefakes"
15	"github.com/concourse/concourse/atc/worker/workerfakes"
16	"github.com/concourse/concourse/vars"
17	. "github.com/onsi/ginkgo"
18	. "github.com/onsi/gomega"
19	"github.com/onsi/gomega/gbytes"
20	"sigs.k8s.io/yaml"
21)
22
23var _ = Describe("TaskConfigSource", func() {
24	var (
25		taskConfig atc.TaskConfig
26		taskVars   atc.Params
27		repo       *build.Repository
28		logger     *lagertest.TestLogger
29	)
30
31	BeforeEach(func() {
32		logger = lagertest.NewTestLogger("task-config-source-test")
33		repo = build.NewRepository()
34		taskConfig = atc.TaskConfig{
35			Platform:  "some-platform",
36			RootfsURI: "some-image",
37			ImageResource: &atc.ImageResource{
38				Type: "docker",
39				Source: atc.Source{
40					"a":               "b",
41					"evaluated-value": "((task-variable-name))",
42				},
43				Params: atc.Params{
44					"some":            "params",
45					"evaluated-value": "((task-variable-name))",
46				},
47				Version: atc.Version{"some": "version"},
48			},
49			Params: atc.TaskEnv{
50				"key1": "key1-((task-variable-name))",
51				"key2": "key2-((task-variable-name))",
52			},
53			Run: atc.TaskRunConfig{
54				Path: "ls",
55				Args: []string{"-al", "((task-variable-name))"},
56				Dir:  "some/dir",
57				User: "some-user",
58			},
59			Inputs: []atc.TaskInputConfig{
60				{Name: "some-input", Path: "some-path"},
61			},
62		}
63		taskVars = atc.Params{
64			"task-variable-name": "task-variable-value",
65		}
66	})
67
68	Describe("StaticConfigSource", func() {
69
70		It("fetches task config successfully", func() {
71			configSource := StaticConfigSource{Config: &taskConfig}
72			fetchedConfig, fetchErr := configSource.FetchConfig(context.TODO(), logger, repo)
73			Expect(fetchErr).ToNot(HaveOccurred())
74			Expect(fetchedConfig).To(Equal(taskConfig))
75		})
76
77		It("fetches config of nil task successfully", func() {
78			configSource := StaticConfigSource{Config: nil}
79			fetchedConfig, fetchErr := configSource.FetchConfig(context.TODO(), logger, repo)
80			Expect(fetchErr).ToNot(HaveOccurred())
81			Expect(fetchedConfig).To(Equal(atc.TaskConfig{}))
82		})
83	})
84
85	Describe("FileConfigSource", func() {
86		var (
87			configSource     FileConfigSource
88			fakeWorkerClient *workerfakes.FakeClient
89			fetchErr         error
90			artifactName     string
91		)
92
93		BeforeEach(func() {
94
95			artifactName = "some-artifact-name"
96			fakeWorkerClient = new(workerfakes.FakeClient)
97			configSource = FileConfigSource{
98				ConfigPath: artifactName + "/build.yml",
99				Client:     fakeWorkerClient,
100			}
101		})
102
103		JustBeforeEach(func() {
104			_, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
105		})
106
107		Context("when the path does not indicate an artifact source", func() {
108			BeforeEach(func() {
109				configSource.ConfigPath = "foo-bar.yml"
110			})
111
112			It("returns an error", func() {
113				Expect(fetchErr).To(Equal(UnspecifiedArtifactSourceError{"foo-bar.yml"}))
114			})
115		})
116
117		Context("when the file's artifact can be found in the repository", func() {
118			var fakeArtifact *runtimefakes.FakeArtifact
119
120			BeforeEach(func() {
121				fakeArtifact = new(runtimefakes.FakeArtifact)
122				repo.RegisterArtifact(build.ArtifactName(artifactName), fakeArtifact)
123			})
124
125			Context("when the artifact provides a proper file", func() {
126				var streamedOut *gbytes.Buffer
127
128				BeforeEach(func() {
129					marshalled, err := yaml.Marshal(taskConfig)
130					Expect(err).NotTo(HaveOccurred())
131
132					streamedOut = gbytes.BufferWithBytes(marshalled)
133					fakeWorkerClient.StreamFileFromArtifactReturns(streamedOut, nil)
134				})
135
136				It("fetches the file via the correct artifact & path", func() {
137					_, _, artifact, dest := fakeWorkerClient.StreamFileFromArtifactArgsForCall(0)
138					Expect(artifact).To(Equal(fakeArtifact))
139					Expect(dest).To(Equal("build.yml"))
140				})
141
142				It("succeeds", func() {
143					Expect(fetchErr).NotTo(HaveOccurred())
144				})
145
146				It("closes the stream", func() {
147					Expect(streamedOut.Closed()).To(BeTrue())
148				})
149			})
150
151			Context("when the artifact source provides an invalid configuration", func() {
152				var streamedOut *gbytes.Buffer
153
154				BeforeEach(func() {
155					invalidConfig := taskConfig
156					invalidConfig.Platform = ""
157					invalidConfig.Run = atc.TaskRunConfig{}
158
159					marshalled, err := yaml.Marshal(invalidConfig)
160					Expect(err).NotTo(HaveOccurred())
161
162					streamedOut = gbytes.BufferWithBytes(marshalled)
163					fakeWorkerClient.StreamFileFromArtifactReturns(streamedOut, nil)
164				})
165
166				It("returns an error", func() {
167					Expect(fetchErr).To(HaveOccurred())
168				})
169			})
170
171			Context("when the artifact source provides a malformed file", func() {
172				var streamedOut *gbytes.Buffer
173
174				BeforeEach(func() {
175					streamedOut = gbytes.BufferWithBytes([]byte("bogus"))
176					fakeWorkerClient.StreamFileFromArtifactReturns(streamedOut, nil)
177				})
178
179				It("fails", func() {
180					Expect(fetchErr).To(HaveOccurred())
181				})
182
183				It("closes the stream", func() {
184					Expect(streamedOut.Closed()).To(BeTrue())
185				})
186			})
187
188			Context("when the artifact source provides a valid file with invalid keys", func() {
189				var streamedOut *gbytes.Buffer
190
191				BeforeEach(func() {
192					streamedOut = gbytes.BufferWithBytes([]byte(`
193platform: beos
194
195intputs: []
196
197run: {path: a/file}
198`))
199					fakeWorkerClient.StreamFileFromArtifactReturns(streamedOut, nil)
200				})
201
202				It("fails", func() {
203					Expect(fetchErr).To(HaveOccurred())
204				})
205
206				It("closes the stream", func() {
207					Expect(streamedOut.Closed()).To(BeTrue())
208				})
209			})
210
211			Context("when streaming the file out fails", func() {
212				disaster := errors.New("nope")
213
214				BeforeEach(func() {
215					fakeWorkerClient.StreamFileFromArtifactReturns(nil, disaster)
216				})
217
218				It("returns the error", func() {
219					Expect(fetchErr).To(HaveOccurred())
220				})
221			})
222
223			Context("when the file task is not found", func() {
224				BeforeEach(func() {
225					fakeWorkerClient.StreamFileFromArtifactReturns(nil, baggageclaim.ErrFileNotFound)
226				})
227
228				It("returns the error", func() {
229					Expect(fetchErr).To(HaveOccurred())
230					Expect(fetchErr.Error()).To(Equal(fmt.Sprintf("task config '%s/build.yml' not found", artifactName)))
231				})
232			})
233		})
234
235		Context("when the file's artifact source cannot be found in the repository", func() {
236			It("returns an UnknownArtifactSourceError", func() {
237				Expect(fetchErr).To(Equal(UnknownArtifactSourceError{SourceName: build.ArtifactName(artifactName), ConfigPath: artifactName + "/build.yml"}))
238			})
239		})
240	})
241
242	Describe("OverrideParamsConfigSource", func() {
243		var (
244			config       atc.TaskConfig
245			configSource TaskConfigSource
246
247			overrideParams atc.TaskEnv
248
249			fetchedConfig atc.TaskConfig
250			fetchErr      error
251		)
252
253		BeforeEach(func() {
254			config = atc.TaskConfig{
255				Platform:  "some-platform",
256				RootfsURI: "some-image",
257				Params:    atc.TaskEnv{"PARAM": "A", "ORIG_PARAM": "D"},
258				Run: atc.TaskRunConfig{
259					Path: "echo",
260					Args: []string{"bananapants"},
261				},
262			}
263
264			overrideParams = atc.TaskEnv{"PARAM": "B", "EXTRA_PARAM": "C"}
265		})
266
267		Context("when there are no params to override", func() {
268			BeforeEach(func() {
269				configSource = &OverrideParamsConfigSource{
270					ConfigSource: StaticConfigSource{Config: &config},
271				}
272			})
273
274			JustBeforeEach(func() {
275				fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
276			})
277
278			It("succeeds", func() {
279				Expect(fetchErr).NotTo(HaveOccurred())
280			})
281
282			It("returns the same config", func() {
283				Expect(fetchedConfig).To(Equal(config))
284			})
285
286			It("returns no warnings", func() {
287				Expect(configSource.Warnings()).To(HaveLen(0))
288			})
289		})
290
291		Context("when override params are specified", func() {
292			BeforeEach(func() {
293				configSource = &OverrideParamsConfigSource{
294					ConfigSource: StaticConfigSource{Config: &config},
295					Params:       overrideParams,
296				}
297			})
298
299			JustBeforeEach(func() {
300				fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
301			})
302
303			It("succeeds", func() {
304				Expect(fetchErr).NotTo(HaveOccurred())
305			})
306
307			It("returns the config with overridden parameters", func() {
308				Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
309					"ORIG_PARAM":  "D",
310					"PARAM":       "B",
311					"EXTRA_PARAM": "C",
312				}))
313			})
314
315			It("returns a deprecation warning", func() {
316				Expect(configSource.Warnings()).To(HaveLen(1))
317				Expect(configSource.Warnings()[0]).To(ContainSubstring("EXTRA_PARAM was defined in pipeline but missing from task file"))
318			})
319		})
320	})
321
322	Describe("ValidatingConfigSource", func() {
323		var (
324			fakeConfigSource *execfakes.FakeTaskConfigSource
325
326			configSource TaskConfigSource
327
328			fetchedConfig atc.TaskConfig
329			fetchErr      error
330		)
331
332		BeforeEach(func() {
333			fakeConfigSource = new(execfakes.FakeTaskConfigSource)
334
335			configSource = ValidatingConfigSource{fakeConfigSource}
336		})
337
338		JustBeforeEach(func() {
339			fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
340		})
341
342		Context("when the config is valid", func() {
343			config := atc.TaskConfig{
344				Platform:  "some-platform",
345				RootfsURI: "some-image",
346				Params:    atc.TaskEnv{"PARAM": "A"},
347				Run: atc.TaskRunConfig{
348					Path: "echo",
349					Args: []string{"bananapants"},
350				},
351			}
352
353			BeforeEach(func() {
354				fakeConfigSource.FetchConfigReturns(config, nil)
355			})
356
357			It("returns the config and no error", func() {
358				Expect(fetchErr).ToNot(HaveOccurred())
359				Expect(fetchedConfig).To(Equal(config))
360			})
361		})
362
363		Context("when the config is invalid", func() {
364			BeforeEach(func() {
365				fakeConfigSource.FetchConfigReturns(atc.TaskConfig{
366					RootfsURI: "some-image",
367					Params:    atc.TaskEnv{"PARAM": "A"},
368					Run: atc.TaskRunConfig{
369						Args: []string{"bananapants"},
370					},
371				}, nil)
372			})
373
374			It("returns the validation error", func() {
375				Expect(fetchErr).To(HaveOccurred())
376			})
377		})
378
379		Context("when fetching the config fails", func() {
380			disaster := errors.New("nope")
381
382			BeforeEach(func() {
383				fakeConfigSource.FetchConfigReturns(atc.TaskConfig{}, disaster)
384			})
385
386			It("returns the error", func() {
387				Expect(fetchErr).To(Equal(disaster))
388			})
389		})
390	})
391
392	Describe("InterpolateTemplateConfigSource", func() {
393		var (
394			configSource  TaskConfigSource
395			fetchedConfig atc.TaskConfig
396			fetchErr      error
397			expectAllKeys bool
398		)
399
400		JustBeforeEach(func() {
401			configSource = StaticConfigSource{Config: &taskConfig}
402			configSource = InterpolateTemplateConfigSource{
403				ConfigSource:  configSource,
404				Vars:          []vars.Variables{vars.StaticVariables(taskVars)},
405				ExpectAllKeys: expectAllKeys,
406			}
407			fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
408		})
409
410		Context("when expect all keys", func() {
411			BeforeEach(func() {
412				expectAllKeys = true
413			})
414
415			It("fetches task config successfully", func() {
416				Expect(fetchErr).ToNot(HaveOccurred())
417			})
418
419			It("resolves task config parameters successfully", func() {
420				Expect(fetchedConfig.Run.Args).To(Equal([]string{"-al", "task-variable-value"}))
421				Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
422					"key1": "key1-task-variable-value",
423					"key2": "key2-task-variable-value",
424				}))
425				Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
426					"a":               "b",
427					"evaluated-value": "task-variable-value",
428				}))
429			})
430		})
431
432		Context("when not expect all keys", func() {
433			BeforeEach(func() {
434				expectAllKeys = false
435				taskVars = atc.Params{}
436			})
437
438			It("fetches task config successfully", func() {
439				Expect(fetchErr).ToNot(HaveOccurred())
440			})
441
442			It("resolves task config parameters successfully", func() {
443				Expect(fetchedConfig.Run.Args).To(Equal([]string{"-al", "((task-variable-name))"}))
444				Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
445					"key1": "key1-((task-variable-name))",
446					"key2": "key2-((task-variable-name))",
447				}))
448				Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
449					"a":               "b",
450					"evaluated-value": "((task-variable-name))",
451				}))
452			})
453		})
454	})
455
456	Context("BaseResourceTypeDefaultsApplySource", func() {
457		var (
458			configSource  TaskConfigSource
459			resourceTypes atc.VersionedResourceTypes
460			fetchedConfig atc.TaskConfig
461			fetchErr      error
462		)
463
464		JustBeforeEach(func() {
465			configSource = StaticConfigSource{Config: &taskConfig}
466			configSource = BaseResourceTypeDefaultsApplySource{
467				ConfigSource:  configSource,
468				ResourceTypes: resourceTypes,
469			}
470			fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
471		})
472
473		Context("resourceTypes is empty, and no base resource type defaults configured", func() {
474			It("fetchedConfig should be identical to the original", func() {
475				Expect(fetchErr).ToNot(HaveOccurred())
476				Expect(fetchedConfig).To(Equal(taskConfig))
477			})
478		})
479
480		Context("resourceTypes is empty, and base resource type defaults configured", func() {
481			BeforeEach(func() {
482				atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{"docker": atc.Source{"some-key": "some-value"}})
483			})
484			AfterEach(func() {
485				atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{})
486			})
487
488			It("defaults should be added to image source", func() {
489				Expect(fetchErr).ToNot(HaveOccurred())
490				Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
491					"a":               "b",
492					"evaluated-value": "((task-variable-name))",
493					"some-key":        "some-value",
494				}))
495			})
496		})
497
498		Context("resourceTypes contains image source type", func() {
499			BeforeEach(func() {
500				resourceTypes = atc.VersionedResourceTypes{
501					{
502						ResourceType: atc.ResourceType{
503							Name:     "docker",
504							Defaults: atc.Source{"some-key": "some-value"},
505						},
506					},
507				}
508			})
509
510			It("defaults should be added to image source", func() {
511				Expect(fetchErr).ToNot(HaveOccurred())
512				Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
513					"a":               "b",
514					"evaluated-value": "((task-variable-name))",
515					"some-key":        "some-value",
516				}))
517			})
518		})
519	})
520})
521