1package atc_test
2
3import (
4	"github.com/concourse/concourse/atc"
5	. "github.com/concourse/concourse/atc"
6
7	. "github.com/onsi/ginkgo"
8	. "github.com/onsi/gomega"
9)
10
11var _ = Describe("TaskConfig", func() {
12	Describe("validating", func() {
13		var (
14			invalidConfig TaskConfig
15			validConfig   TaskConfig
16		)
17
18		BeforeEach(func() {
19			validConfig = TaskConfig{
20				Platform: "linux",
21				Run: TaskRunConfig{
22					Path: "reboot",
23				},
24			}
25
26			invalidConfig = validConfig
27		})
28
29		Describe("decode task yaml", func() {
30			Context("given a valid task config", func() {
31				It("works", func() {
32					data := []byte(`
33platform: beos
34
35inputs: []
36
37run: {path: a/file}
38`)
39					task, err := NewTaskConfig(data)
40					Expect(err).ToNot(HaveOccurred())
41					Expect(task.Platform).To(Equal("beos"))
42					Expect(task.Run.Path).To(Equal("a/file"))
43				})
44
45				It("converts yaml booleans to strings in params", func() {
46					data := []byte(`
47platform: beos
48
49params:
50  testParam: true
51
52run: {path: a/file}
53`)
54					config, err := NewTaskConfig(data)
55					Expect(err).ToNot(HaveOccurred())
56					Expect(config.Params["testParam"]).To(Equal("true"))
57				})
58
59				It("converts yaml ints to the correct string in params", func() {
60					data := []byte(`
61platform: beos
62
63params:
64  testParam: 1059262
65
66run: {path: a/file}
67`)
68					config, err := NewTaskConfig(data)
69					Expect(err).ToNot(HaveOccurred())
70					Expect(config.Params["testParam"]).To(Equal("1059262"))
71				})
72
73				It("converts large yaml ints to the correct string in params", func() {
74					data := []byte(`
75platform: beos
76
77params:
78  testParam: 18446744073709551615
79
80run: {path: a/file}
81`)
82					config, err := NewTaskConfig(data)
83					Expect(err).ToNot(HaveOccurred())
84					Expect(config.Params["testParam"]).To(Equal("18446744073709551615"))
85				})
86
87				It("does not preserve unquoted float notation", func() {
88					data := []byte(`
89platform: beos
90
91params:
92  testParam: 1.8446744e+19
93
94run: {path: a/file}
95`)
96					config, err := NewTaskConfig(data)
97					Expect(err).ToNot(HaveOccurred())
98					Expect(config.Params["testParam"]).To(Equal("18446744000000000000"))
99				})
100
101				It("(obviously) preserves quoted float notation", func() {
102					data := []byte(`
103platform: beos
104
105params:
106  testParam: "1.8446744e+19"
107
108run: {path: a/file}
109`)
110					config, err := NewTaskConfig(data)
111					Expect(err).ToNot(HaveOccurred())
112					Expect(config.Params["testParam"]).To(Equal("1.8446744e+19"))
113				})
114
115				It("converts yaml floats to the correct string in params", func() {
116					data := []byte(`
117platform: beos
118
119params:
120  testParam: 1059262.123123123
121
122run: {path: a/file}
123`)
124					config, err := NewTaskConfig(data)
125					Expect(err).ToNot(HaveOccurred())
126					Expect(config.Params["testParam"]).To(Equal("1059262.123123123"))
127				})
128
129				It("converts maps to json in params", func() {
130					data := []byte(`
131platform: beos
132
133params:
134  testParam:
135    foo: bar
136
137run: {path: a/file}
138`)
139					config, err := NewTaskConfig(data)
140					Expect(err).ToNot(HaveOccurred())
141					Expect(config.Params["testParam"]).To(Equal(`{"foo":"bar"}`))
142				})
143
144				It("converts empty values to empty string in params", func() {
145					data := []byte(`
146platform: beos
147
148params:
149  testParam:
150
151run: {path: a/file}
152`)
153					config, err := NewTaskConfig(data)
154					Expect(err).ToNot(HaveOccurred())
155					Expect(config.Params["testParam"]).To(Equal(""))
156				})
157			})
158
159			Context("given a valid task config with numeric params", func() {
160				It("works", func() {
161					data := []byte(`
162platform: beos
163
164params:
165  FOO: 1
166
167run: {path: a/file}
168`)
169					task, err := NewTaskConfig(data)
170					Expect(err).ToNot(HaveOccurred())
171					Expect(task.Platform).To(Equal("beos"))
172					Expect(task.Params).To(Equal(atc.TaskEnv{"FOO": "1"}))
173				})
174			})
175
176			Context("given a valid task config with extra keys", func() {
177				It("returns an error", func() {
178					data := []byte(`
179platform: beos
180
181intputs: []
182
183run: {path: a/file}
184`)
185					_, err := NewTaskConfig(data)
186					Expect(err).To(HaveOccurred())
187				})
188			})
189
190			Context("given an invalid task config", func() {
191				It("errors on validation", func() {
192					data := []byte(`
193platform: beos
194
195inputs: ['a/b/c']
196outputs: ['a/b/c']
197
198run: {path: a/file}
199`)
200					_, err := NewTaskConfig(data)
201					Expect(err).To(HaveOccurred())
202				})
203			})
204		})
205
206		Context("when platform is missing", func() {
207			BeforeEach(func() {
208				invalidConfig.Platform = ""
209			})
210
211			It("returns an error", func() {
212				Expect(invalidConfig.Validate()).To(MatchError(ContainSubstring("missing 'platform'")))
213			})
214		})
215
216		Context("when container limits are specified", func() {
217			Context("when memory and cpu limits are correctly specified", func() {
218				It("successfully parses the limits with memory units", func() {
219					data := []byte(`
220platform: beos
221container_limits: { cpu: 1024, memory: 1KB }
222
223run: {path: a/file}
224`)
225					task, err := NewTaskConfig(data)
226					Expect(err).ToNot(HaveOccurred())
227					cpu := uint64(1024)
228					memory := uint64(1024)
229					Expect(task.Limits).To(Equal(&ContainerLimits{
230						CPU:    &cpu,
231						Memory: &memory,
232					}))
233				})
234
235				It("successfully parses the limits without memory units", func() {
236					data := []byte(`
237platform: beos
238container_limits: { cpu: 1024, memory: 209715200 }
239
240run: {path: a/file}
241`)
242					task, err := NewTaskConfig(data)
243					Expect(err).ToNot(HaveOccurred())
244					cpu := uint64(1024)
245					memory := uint64(209715200)
246					Expect(task.Limits).To(Equal(&ContainerLimits{
247						CPU:    &cpu,
248						Memory: &memory,
249					}))
250				})
251			})
252
253			Context("when either one of memory or cpu is correctly specified", func() {
254				It("parses the provided memory limit without any errors", func() {
255					data := []byte(`
256platform: beos
257container_limits: { memory: 1KB }
258
259run: {path: a/file}
260`)
261					task, err := NewTaskConfig(data)
262					Expect(err).ToNot(HaveOccurred())
263					memory := uint64(1024)
264					Expect(task.Limits).To(Equal(&ContainerLimits{
265						Memory: &memory,
266					}))
267				})
268
269				It("parses the provided cpu limit without any errors", func() {
270					data := []byte(`
271platform: beos
272container_limits: { cpu: 355 }
273
274run: {path: a/file}
275`)
276					task, err := NewTaskConfig(data)
277					Expect(err).ToNot(HaveOccurred())
278					cpu := uint64(355)
279					Expect(task.Limits).To(Equal(&ContainerLimits{
280						CPU: &cpu,
281					}))
282				})
283			})
284
285			Context("when invalid memory limit value is provided", func() {
286				It("throws an error and does not continue", func() {
287					data := []byte(`
288platform: beos
289container_limits: { cpu: 1024, memory: abc1000kb  }
290
291run: {path: a/file}
292`)
293					_, err := NewTaskConfig(data)
294					Expect(err).To(MatchError(ContainSubstring("could not parse container memory limit")))
295				})
296
297			})
298
299			Context("when invalid cpu limit value is provided", func() {
300				It("throws an error and does not continue", func() {
301					data := []byte(`
302platform: beos
303container_limits: { cpu: str1ng-cpu-l1mit, memory: 20MB}
304
305run: {path: a/file}
306`)
307					_, err := NewTaskConfig(data)
308					Expect(err).To(MatchError(ContainSubstring("cpu limit must be an integer")))
309				})
310			})
311		})
312
313		Context("when the task has inputs", func() {
314			BeforeEach(func() {
315				validConfig.Inputs = append(validConfig.Inputs, TaskInputConfig{Name: "concourse"})
316			})
317
318			It("is valid", func() {
319				Expect(validConfig.Validate()).ToNot(HaveOccurred())
320			})
321
322			Context("when input.name is missing", func() {
323				BeforeEach(func() {
324					invalidConfig.Inputs = append(invalidConfig.Inputs, TaskInputConfig{Name: "concourse"}, TaskInputConfig{Name: ""})
325				})
326
327				It("returns an error", func() {
328					Expect(invalidConfig.Validate()).To(MatchError(ContainSubstring("input in position 1 is missing a name")))
329				})
330			})
331
332			Context("when input.name is missing multiple times", func() {
333				BeforeEach(func() {
334					invalidConfig.Inputs = append(
335						invalidConfig.Inputs,
336						TaskInputConfig{Name: "concourse"},
337						TaskInputConfig{Name: ""},
338						TaskInputConfig{Name: ""},
339					)
340				})
341
342				It("returns an error", func() {
343					err := invalidConfig.Validate()
344
345					Expect(err).To(MatchError(ContainSubstring("input in position 1 is missing a name")))
346					Expect(err).To(MatchError(ContainSubstring("input in position 2 is missing a name")))
347				})
348			})
349		})
350
351		Context("when the task has outputs", func() {
352			BeforeEach(func() {
353				validConfig.Outputs = append(validConfig.Outputs, TaskOutputConfig{Name: "concourse"})
354			})
355
356			It("is valid", func() {
357				Expect(validConfig.Validate()).ToNot(HaveOccurred())
358			})
359
360			Context("when output.name is missing", func() {
361				BeforeEach(func() {
362					invalidConfig.Outputs = append(invalidConfig.Outputs, TaskOutputConfig{Name: "concourse"}, TaskOutputConfig{Name: ""})
363				})
364
365				It("returns an error", func() {
366					Expect(invalidConfig.Validate()).To(MatchError(ContainSubstring("output in position 1 is missing a name")))
367				})
368			})
369
370			Context("when output.name is missing multiple times", func() {
371				BeforeEach(func() {
372					invalidConfig.Outputs = append(
373						invalidConfig.Outputs,
374						TaskOutputConfig{Name: "concourse"},
375						TaskOutputConfig{Name: ""},
376						TaskOutputConfig{Name: ""},
377					)
378				})
379
380				It("returns an error", func() {
381					err := invalidConfig.Validate()
382
383					Expect(err).To(MatchError(ContainSubstring("output in position 1 is missing a name")))
384					Expect(err).To(MatchError(ContainSubstring("output in position 2 is missing a name")))
385				})
386			})
387		})
388
389		Context("when run is missing", func() {
390			BeforeEach(func() {
391				invalidConfig.Run.Path = ""
392			})
393
394			It("returns an error", func() {
395				Expect(invalidConfig.Validate()).To(MatchError(ContainSubstring("missing path to executable to run")))
396			})
397		})
398
399	})
400
401})
402
403var _ = Context("ImageResource", func() {
404	var imageResource *atc.ImageResource
405	var resourceTypes atc.VersionedResourceTypes
406
407	Context("ApplySourceDefaults", func() {
408		BeforeEach(func() {
409			resourceTypes = atc.VersionedResourceTypes{}
410		})
411
412		JustBeforeEach(func() {
413			imageResource.ApplySourceDefaults(resourceTypes)
414		})
415
416		Context("when imageResource is nil", func() {
417			It("should not fail", func() {
418				Expect(imageResource).To(BeNil())
419			})
420		})
421
422		Context("when imageResource is initialized", func() {
423			BeforeEach(func() {
424				imageResource = &atc.ImageResource{
425					Type: "docker",
426					Source: atc.Source{
427						"a":               "b",
428						"evaluated-value": "((task-variable-name))",
429					},
430				}
431			})
432
433			Context("resourceTypes is empty, and no base resource type defaults configured", func() {
434				It("applied source should be identical to the original", func() {
435					Expect(imageResource.Source).To(Equal(atc.Source{
436						"a":               "b",
437						"evaluated-value": "((task-variable-name))",
438					}))
439				})
440			})
441
442			Context("resourceTypes is empty, and base resource type defaults configured", func() {
443				BeforeEach(func() {
444					atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{"docker": atc.Source{"some-key": "some-value"}})
445				})
446				AfterEach(func() {
447					atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{})
448				})
449
450				It("defaults should be added to image source", func() {
451					Expect(imageResource.Source).To(Equal(atc.Source{
452						"a":               "b",
453						"evaluated-value": "((task-variable-name))",
454						"some-key":        "some-value",
455					}))
456				})
457			})
458
459			Context("resourceTypes contains image source type", func() {
460				BeforeEach(func() {
461					resourceTypes = atc.VersionedResourceTypes{
462						{
463							ResourceType: atc.ResourceType{
464								Name:     "docker",
465								Defaults: atc.Source{"some-key": "some-value"},
466							},
467						},
468					}
469				})
470
471				It("defaults should be added to image source", func() {
472					Expect(imageResource.Source).To(Equal(atc.Source{
473						"a":               "b",
474						"evaluated-value": "((task-variable-name))",
475						"some-key":        "some-value",
476					}))
477				})
478			})
479		})
480	})
481})
482