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