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