Files
concourse-concourse/atc/exec/task_config_source_test.go
T

634 lines
17 KiB
Go

package exec_test
import (
"context"
"errors"
"fmt"
"code.cloudfoundry.org/lager/v3/lagertest"
"github.com/concourse/concourse/atc"
. "github.com/concourse/concourse/atc/exec"
"github.com/concourse/concourse/atc/exec/build"
"github.com/concourse/concourse/atc/exec/execfakes"
"github.com/concourse/concourse/atc/runtime/runtimetest"
"github.com/concourse/concourse/vars"
"github.com/concourse/concourse/worker/baggageclaim"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"sigs.k8s.io/yaml"
)
var _ = Describe("TaskConfigSource", func() {
var (
taskConfig atc.TaskConfig
taskVars atc.Params
repo *build.Repository
logger *lagertest.TestLogger
)
BeforeEach(func() {
logger = lagertest.NewTestLogger("task-config-source-test")
repo = build.NewRepository()
taskConfig = atc.TaskConfig{
Platform: "some-platform",
RootfsURI: "some-image",
ImageResource: &atc.ImageResource{
Type: "docker",
Source: atc.Source{
"a": "b",
"evaluated-value": "((task-variable-name))",
},
Params: atc.Params{
"some": "params",
"evaluated-value": "((task-variable-name))",
},
Version: atc.Version{"some": "version"},
},
Params: atc.TaskEnv{
"key1": "key1-((task-variable-name))",
"key2": "key2-((task-variable-name))",
},
Run: atc.TaskRunConfig{
Path: "ls",
Args: []string{"-al", "((task-variable-name))"},
Dir: "some/dir",
User: "some-user",
},
Inputs: []atc.TaskInputConfig{
{Name: "some-input", Path: "some-path"},
},
}
taskVars = atc.Params{
"task-variable-name": "task-variable-value",
}
})
Describe("StaticConfigSource", func() {
It("fetches task config successfully", func() {
configSource := StaticConfigSource{Config: &taskConfig}
fetchedConfig, fetchErr := configSource.FetchConfig(context.TODO(), logger, repo)
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig).To(Equal(taskConfig))
})
It("fetches config of nil task successfully", func() {
configSource := StaticConfigSource{Config: nil}
fetchedConfig, fetchErr := configSource.FetchConfig(context.TODO(), logger, repo)
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig).To(Equal(atc.TaskConfig{}))
})
})
Describe("FileConfigSource", func() {
var (
configSource FileConfigSource
fakeStreamer *execfakes.FakeStreamer
fetchErr error
artifactName string
)
BeforeEach(func() {
artifactName = "some-artifact-name"
fakeStreamer = new(execfakes.FakeStreamer)
configSource = FileConfigSource{
ConfigPath: artifactName + "/build.yml",
Streamer: fakeStreamer,
}
})
JustBeforeEach(func() {
_, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
Context("when the path does not indicate an artifact source", func() {
BeforeEach(func() {
configSource.ConfigPath = "foo-bar.yml"
})
It("returns an error", func() {
Expect(fetchErr).To(Equal(UnspecifiedArtifactSourceError{"foo-bar.yml"}))
})
})
Context("when the file's artifact can be found in the repository", func() {
var volume *runtimetest.Volume
BeforeEach(func() {
volume = runtimetest.NewVolume("some-volume")
repo.RegisterArtifact(build.ArtifactName(artifactName), volume, false)
})
Context("when the artifact provides a proper file", func() {
var streamedOut *gbytes.Buffer
BeforeEach(func() {
marshalled, err := yaml.Marshal(taskConfig)
Expect(err).NotTo(HaveOccurred())
streamedOut = gbytes.BufferWithBytes(marshalled)
fakeStreamer.StreamFileReturns(streamedOut, nil)
})
It("fetches the file via the correct artifact & path", func() {
_, artifact, dest := fakeStreamer.StreamFileArgsForCall(0)
Expect(artifact).To(Equal(volume))
Expect(dest).To(Equal("build.yml"))
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("closes the stream", func() {
Expect(streamedOut.Closed()).To(BeTrue())
})
})
Context("when the artifact source provides an invalid configuration", func() {
var streamedOut *gbytes.Buffer
BeforeEach(func() {
invalidConfig := taskConfig
invalidConfig.Platform = ""
invalidConfig.Run = atc.TaskRunConfig{}
marshalled, err := yaml.Marshal(invalidConfig)
Expect(err).NotTo(HaveOccurred())
streamedOut = gbytes.BufferWithBytes(marshalled)
fakeStreamer.StreamFileReturns(streamedOut, nil)
})
It("returns an error", func() {
Expect(fetchErr).To(HaveOccurred())
})
})
Context("when the artifact source provides a malformed file", func() {
var streamedOut *gbytes.Buffer
BeforeEach(func() {
streamedOut = gbytes.BufferWithBytes([]byte("bogus"))
fakeStreamer.StreamFileReturns(streamedOut, nil)
})
It("fails", func() {
Expect(fetchErr).To(HaveOccurred())
})
It("closes the stream", func() {
Expect(streamedOut.Closed()).To(BeTrue())
})
})
Context("when the artifact source provides a valid file with invalid keys", func() {
var streamedOut *gbytes.Buffer
BeforeEach(func() {
streamedOut = gbytes.BufferWithBytes([]byte(`
platform: beos
intputs: []
run: {path: a/file}
`))
fakeStreamer.StreamFileReturns(streamedOut, nil)
})
It("fails", func() {
Expect(fetchErr).To(HaveOccurred())
})
It("closes the stream", func() {
Expect(streamedOut.Closed()).To(BeTrue())
})
})
Context("when streaming the file out fails", func() {
disaster := errors.New("nope")
BeforeEach(func() {
fakeStreamer.StreamFileReturns(nil, disaster)
})
It("returns the error", func() {
Expect(fetchErr).To(HaveOccurred())
})
})
Context("when the file task is not found", func() {
BeforeEach(func() {
fakeStreamer.StreamFileReturns(nil, baggageclaim.ErrFileNotFound)
})
It("returns the error", func() {
Expect(fetchErr).To(HaveOccurred())
Expect(fetchErr.Error()).To(Equal(fmt.Sprintf("task config '%s/build.yml' not found", artifactName)))
})
})
})
Context("when the file's artifact source cannot be found in the repository", func() {
It("returns an UnknownArtifactSourceError", func() {
Expect(fetchErr).To(Equal(UnknownArtifactSourceError{SourceName: build.ArtifactName(artifactName), ConfigPath: artifactName + "/build.yml"}))
})
})
})
Describe("OverrideParamsConfigSource", func() {
var (
config atc.TaskConfig
configSource TaskConfigSource
overrideParams atc.TaskEnv
fetchedConfig atc.TaskConfig
fetchErr error
)
BeforeEach(func() {
config = atc.TaskConfig{
Platform: "some-platform",
RootfsURI: "some-image",
Params: atc.TaskEnv{"PARAM": "A", "ORIG_PARAM": "D"},
Run: atc.TaskRunConfig{
Path: "echo",
Args: []string{"bananapants"},
},
}
overrideParams = atc.TaskEnv{"PARAM": "B", "EXTRA_PARAM": "C"}
})
Context("when there are no params to override", func() {
BeforeEach(func() {
configSource = &OverrideParamsConfigSource{
ConfigSource: StaticConfigSource{Config: &config},
}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("returns the same config", func() {
Expect(fetchedConfig).To(Equal(config))
})
It("returns no warnings", func() {
Expect(configSource.Warnings()).To(HaveLen(0))
})
})
Context("when override params are specified", func() {
BeforeEach(func() {
configSource = &OverrideParamsConfigSource{
ConfigSource: StaticConfigSource{Config: &config},
Params: overrideParams,
}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("returns the config with overridden parameters", func() {
Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
"ORIG_PARAM": "D",
"PARAM": "B",
"EXTRA_PARAM": "C",
}))
})
It("returns a deprecation warning", func() {
Expect(configSource.Warnings()).To(HaveLen(1))
Expect(configSource.Warnings()[0]).To(ContainSubstring("EXTRA_PARAM was defined in pipeline but missing from task file"))
})
})
})
Describe("OverrideContainerLimitsSource", func() {
var (
config atc.TaskConfig
noLimitsConfig atc.TaskConfig
configSource TaskConfigSource
overrideLimits atc.ContainerLimits
fetchedConfig atc.TaskConfig
fetchErr error
)
BeforeEach(func() {
config = atc.TaskConfig{
Platform: "some-platform",
RootfsURI: "some-image",
Limits: &atc.ContainerLimits{CPU: newCPULimit(1024), Memory: newMemoryLimit(209715200)},
Run: atc.TaskRunConfig{
Path: "echo",
Args: []string{"bananapants"},
},
}
noLimitsConfig = atc.TaskConfig{
Platform: "some-platform",
RootfsURI: "some-image",
Run: atc.TaskRunConfig{
Path: "echo",
Args: []string{"bananapants"},
},
}
overrideLimits = atc.ContainerLimits{CPU: newCPULimit(2048), Memory: newMemoryLimit(209715200)}
})
Context("when there are no container limits to override", func() {
BeforeEach(func() {
configSource = &OverrideContainerLimitsSource{
ConfigSource: StaticConfigSource{Config: &config},
}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("returns the same config", func() {
Expect(fetchedConfig).To(Equal(config))
})
})
Context("when override container limits are specified but no configuration container limits", func() {
BeforeEach(func() {
configSource = &OverrideContainerLimitsSource{
ConfigSource: StaticConfigSource{Config: &noLimitsConfig},
Limits: &overrideLimits,
}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("returns the config with overridden limits", func() {
Expect(fetchedConfig).NotTo(BeNil())
Expect(*fetchedConfig.Limits).To(Equal(atc.ContainerLimits{
CPU: newCPULimit(2048),
Memory: newMemoryLimit(209715200),
}))
})
})
Context("when override container limits are specified", func() {
BeforeEach(func() {
configSource = &OverrideContainerLimitsSource{
ConfigSource: StaticConfigSource{Config: &config},
Limits: &overrideLimits,
}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
It("succeeds", func() {
Expect(fetchErr).NotTo(HaveOccurred())
})
It("returns the config with overridden limits", func() {
Expect(fetchedConfig).NotTo(BeNil())
Expect(*fetchedConfig.Limits).To(Equal(atc.ContainerLimits{
CPU: newCPULimit(2048),
Memory: newMemoryLimit(209715200),
}))
})
})
})
Describe("ValidatingConfigSource", func() {
var (
fakeConfigSource *execfakes.FakeTaskConfigSource
configSource TaskConfigSource
fetchedConfig atc.TaskConfig
fetchErr error
)
BeforeEach(func() {
fakeConfigSource = new(execfakes.FakeTaskConfigSource)
configSource = ValidatingConfigSource{fakeConfigSource}
})
JustBeforeEach(func() {
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
Context("when the config is valid", func() {
config := atc.TaskConfig{
Platform: "some-platform",
RootfsURI: "some-image",
Params: atc.TaskEnv{"PARAM": "A"},
Run: atc.TaskRunConfig{
Path: "echo",
Args: []string{"bananapants"},
},
}
BeforeEach(func() {
fakeConfigSource.FetchConfigReturns(config, nil)
})
It("returns the config and no error", func() {
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig).To(Equal(config))
})
})
Context("when the config is invalid", func() {
BeforeEach(func() {
fakeConfigSource.FetchConfigReturns(atc.TaskConfig{
RootfsURI: "some-image",
Params: atc.TaskEnv{"PARAM": "A"},
Run: atc.TaskRunConfig{
Args: []string{"bananapants"},
},
}, nil)
})
It("returns the validation error", func() {
Expect(fetchErr).To(HaveOccurred())
})
})
Context("when fetching the config fails", func() {
disaster := errors.New("nope")
BeforeEach(func() {
fakeConfigSource.FetchConfigReturns(atc.TaskConfig{}, disaster)
})
It("returns the error", func() {
Expect(fetchErr).To(Equal(disaster))
})
})
})
Describe("InterpolateTemplateConfigSource", func() {
var (
configSource TaskConfigSource
fetchedConfig atc.TaskConfig
fetchErr error
expectAllKeys bool
)
JustBeforeEach(func() {
configSource = StaticConfigSource{Config: &taskConfig}
configSource = InterpolateTemplateConfigSource{
ConfigSource: configSource,
Vars: []vars.Variables{vars.StaticVariables(taskVars)},
ExpectAllKeys: expectAllKeys,
}
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
Context("when expect all keys", func() {
BeforeEach(func() {
expectAllKeys = true
})
It("fetches task config successfully", func() {
Expect(fetchErr).ToNot(HaveOccurred())
})
It("resolves task config parameters successfully", func() {
Expect(fetchedConfig.Run.Args).To(Equal([]string{"-al", "task-variable-value"}))
Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
"key1": "key1-task-variable-value",
"key2": "key2-task-variable-value",
}))
Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
"a": "b",
"evaluated-value": "task-variable-value",
}))
})
})
Context("when not expect all keys", func() {
BeforeEach(func() {
expectAllKeys = false
taskVars = atc.Params{}
})
It("fetches task config successfully", func() {
Expect(fetchErr).ToNot(HaveOccurred())
})
It("resolves task config parameters successfully", func() {
Expect(fetchedConfig.Run.Args).To(Equal([]string{"-al", "((task-variable-name))"}))
Expect(fetchedConfig.Params).To(Equal(atc.TaskEnv{
"key1": "key1-((task-variable-name))",
"key2": "key2-((task-variable-name))",
}))
Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
"a": "b",
"evaluated-value": "((task-variable-name))",
}))
})
})
})
Context("BaseResourceTypeDefaultsApplySource", func() {
var (
configSource TaskConfigSource
resourceTypes atc.ResourceTypes
fetchedConfig atc.TaskConfig
fetchErr error
)
JustBeforeEach(func() {
configSource = StaticConfigSource{Config: &taskConfig}
configSource = BaseResourceTypeDefaultsApplySource{
ConfigSource: configSource,
ResourceTypes: resourceTypes,
}
fetchedConfig, fetchErr = configSource.FetchConfig(context.TODO(), logger, repo)
})
Context("resourceTypes is empty, and no base resource type defaults configured", func() {
It("fetchedConfig should be identical to the original", func() {
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig).To(Equal(taskConfig))
})
})
Context("resourceTypes is empty, and base resource type defaults configured", func() {
BeforeEach(func() {
atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{"docker": atc.Source{"some-key": "some-value"}})
})
AfterEach(func() {
atc.LoadBaseResourceTypeDefaults(map[string]atc.Source{})
})
It("defaults should be added to image source", func() {
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
"a": "b",
"evaluated-value": "((task-variable-name))",
"some-key": "some-value",
}))
})
})
Context("resourceTypes contains image source type", func() {
BeforeEach(func() {
resourceTypes = atc.ResourceTypes{
{
Name: "docker",
Defaults: atc.Source{"some-key": "some-value"},
},
}
})
It("defaults should be added to image source", func() {
Expect(fetchErr).ToNot(HaveOccurred())
Expect(fetchedConfig.ImageResource.Source).To(Equal(atc.Source{
"a": "b",
"evaluated-value": "((task-variable-name))",
"some-key": "some-value",
}))
})
})
})
})
func newCPULimit(cpuLimit uint64) *atc.CPULimit {
limit := atc.CPULimit(cpuLimit)
return &limit
}
func newMemoryLimit(memoryLimit uint64) *atc.MemoryLimit {
limit := atc.MemoryLimit(memoryLimit)
return &limit
}