634 lines
17 KiB
Go
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
|
|
}
|