summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Duvall <dduvall@wikimedia.org>2018-04-04 15:28:21 -0700
committerDan Duvall <dduvall@wikimedia.org>2018-04-05 13:24:01 -0700
commit26b998456a566989e054f18f5dc8934fa3fc904d (patch)
treebab51963a654987b572cb22c6ef9f33753049fa5
parent73ee493d6e54ed097edfad2a8d782584a6035cea (diff)
downloadblubber-26b998456a566989e054f18f5dc8934fa3fc904d.tar.gz
Introduce strict/versioned config parsing
Summary: Introduced a `version` config field that must be specified and match `config.CurrentVersion`. Changed `config.ReadConfig` to use `yaml.UnmarshalStrict` to ensure that errors are surfaced when unknown/bad fields are present in the given YAML config. A smaller `config.VersionConfig` is now unmarshaled first to prevalidate the new `version` field before the entire config is parsed. Fixes T191460 Test Plan: Run `go test ./...`. Run `blubber` against some configuration containing invalid fields and ensure that it surfaces a YAML error. Reviewers: thcipriani, demon, hashar, mmodell, mobrovac, #release-engineering-team Reviewed By: thcipriani, #release-engineering-team Tags: #release-engineering-team Maniphest Tasks: T191460 Differential Revision: https://phabricator.wikimedia.org/D1021
-rw-r--r--blubber.example.yaml1
-rw-r--r--config/apt_test.go3
-rw-r--r--config/artifacts_test.go4
-rw-r--r--config/common_test.go4
-rw-r--r--config/config.go5
-rw-r--r--config/config_test.go15
-rw-r--r--config/flag_test.go1
-rw-r--r--config/lives_test.go12
-rw-r--r--config/node_test.go4
-rw-r--r--config/python_test.go3
-rw-r--r--config/reader.go20
-rw-r--r--config/reader_test.go32
-rw-r--r--config/runs_test.go12
-rw-r--r--config/validation.go26
-rw-r--r--config/variant_test.go7
-rw-r--r--config/version.go12
-rw-r--r--config/version_test.go44
-rw-r--r--docker/compiler_test.go4
18 files changed, 193 insertions, 16 deletions
diff --git a/blubber.example.yaml b/blubber.example.yaml
index e47eefb..e4fe3c9 100644
--- a/blubber.example.yaml
+++ b/blubber.example.yaml
@@ -1,4 +1,5 @@
---
+version: v1
base: debian:jessie
apt:
packages: [libjpeg, libyaml]
diff --git a/config/apt_test.go b/config/apt_test.go
index 2f0df11..480c1b7 100644
--- a/config/apt_test.go
+++ b/config/apt_test.go
@@ -12,6 +12,7 @@ import (
func TestAptConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
apt:
packages:
- libfoo
@@ -66,6 +67,7 @@ func TestAptConfigValidation(t *testing.T) {
t.Run("packages", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
apt:
packages:
- f1
@@ -81,6 +83,7 @@ func TestAptConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
apt:
packages:
- foo
diff --git a/config/artifacts_test.go b/config/artifacts_test.go
index 85bad02..3092572 100644
--- a/config/artifacts_test.go
+++ b/config/artifacts_test.go
@@ -11,6 +11,7 @@ import (
func TestArtifactsConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
variants:
build: {}
@@ -79,6 +80,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
t.Run("from", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo:
@@ -92,6 +94,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
t.Run("missing", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo:
@@ -109,6 +112,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo:
diff --git a/config/common_test.go b/config/common_test.go
index 20a07f5..537c025 100644
--- a/config/common_test.go
+++ b/config/common_test.go
@@ -10,6 +10,7 @@ import (
func TestCommonConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: fooimage
sharedvolume: true
entrypoint: ["/bin/foo"]
@@ -33,6 +34,7 @@ func TestCommonConfigValidation(t *testing.T) {
t.Run("base", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
variants: {}`))
@@ -41,6 +43,7 @@ func TestCommonConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
base:
variants: {}`))
@@ -49,6 +52,7 @@ func TestCommonConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo fighter
variants: {}`))
diff --git a/config/config.go b/config/config.go
index 46be3d1..695759f 100644
--- a/config/config.go
+++ b/config/config.go
@@ -7,6 +7,7 @@ package config
// Config holds the root fields of a Blubber configuration.
//
type Config struct {
- CommonConfig `yaml:",inline"`
- Variants map[string]VariantConfig `yaml:"variants" validate:"variants,dive"`
+ CommonConfig `yaml:",inline"`
+ Variants map[string]VariantConfig `yaml:"variants" validate:"variants,dive"`
+ VersionConfig `yaml:",inline"`
}
diff --git a/config/config_test.go b/config/config_test.go
index 051e110..f6b6c1e 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -8,10 +8,24 @@ import (
"phabricator.wikimedia.org/source/blubber/config"
)
+func TestConfig(t *testing.T) {
+ cfg, err := config.ReadConfig([]byte(`---
+ version: v1
+ variants:
+ foo: {}`))
+
+ if assert.NoError(t, err) {
+ assert.Equal(t, "v1", cfg.Version)
+ assert.Contains(t, cfg.Variants, "foo")
+ assert.IsType(t, config.VariantConfig{}, cfg.Variants["foo"])
+ }
+}
+
func TestConfigValidation(t *testing.T) {
t.Run("variants", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: {}`))
@@ -21,6 +35,7 @@ func TestConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build foo: {}
foo bar: {}`))
diff --git a/config/flag_test.go b/config/flag_test.go
index d2651f6..e7481a6 100644
--- a/config/flag_test.go
+++ b/config/flag_test.go
@@ -10,6 +10,7 @@ import (
func TestFlagOverwrite(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
node: { dependencies: true }
sharedvolume: false
diff --git a/config/lives_test.go b/config/lives_test.go
index dec1f99..d6097b5 100644
--- a/config/lives_test.go
+++ b/config/lives_test.go
@@ -11,6 +11,7 @@ import (
func TestLivesConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
lives:
in: /some/directory
@@ -39,6 +40,7 @@ func TestLivesConfig(t *testing.T) {
func TestLivesConfigDefaults(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo`))
if assert.NoError(t, err) {
@@ -94,6 +96,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("in", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
in: /foo`))
@@ -102,6 +105,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives: {}`))
assert.False(t, config.IsValidationError(err))
@@ -109,6 +113,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("non-root", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
in: /`))
@@ -121,6 +126,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("non-root tricky", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
in: /foo/..`))
@@ -133,6 +139,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("absolute", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
in: foo/bar`))
@@ -147,6 +154,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("as", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
as: foo-bar.baz`))
@@ -155,6 +163,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives: {}`))
assert.False(t, config.IsValidationError(err))
@@ -162,6 +171,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("no spaces", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
as: foo bar`))
@@ -174,6 +184,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("long enough", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
as: fo`))
@@ -186,6 +197,7 @@ func TestLivesConfigValidation(t *testing.T) {
t.Run("not root", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
lives:
as: root`))
diff --git a/config/node_test.go b/config/node_test.go
index 5e0c4f3..6b7519a 100644
--- a/config/node_test.go
+++ b/config/node_test.go
@@ -11,6 +11,7 @@ import (
func TestNodeConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
node:
dependencies: true
@@ -162,6 +163,7 @@ func TestNodeConfigValidation(t *testing.T) {
t.Run("env", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
node:
env: production`))
@@ -170,6 +172,7 @@ func TestNodeConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
node: {}`))
assert.False(t, config.IsValidationError(err))
@@ -177,6 +180,7 @@ func TestNodeConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
node:
env: foo bar`))
diff --git a/config/python_test.go b/config/python_test.go
index 5912ae2..55e49c7 100644
--- a/config/python_test.go
+++ b/config/python_test.go
@@ -11,6 +11,7 @@ import (
func TestPythonConfigUnmarshalMerge(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
python:
version: python2.7
@@ -36,6 +37,7 @@ func TestPythonConfigUnmarshalMerge(t *testing.T) {
func TestPythonConfigMergeEmpty(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
python:
requirements: [requirements.txt]
@@ -57,6 +59,7 @@ func TestPythonConfigMergeEmpty(t *testing.T) {
func TestPythonConfigDoNotMergeNil(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
python:
requirements: [requirements.txt]
diff --git a/config/reader.go b/config/reader.go
index 378d523..4386aa7 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -91,11 +91,27 @@ func ExpandVariant(config *Config, name string) (*VariantConfig, error) {
// ReadConfig unmarshals the given YAML bytes into a new Config struct.
//
func ReadConfig(data []byte) (*Config, error) {
- var config Config
+ var (
+ version VersionConfig
+ config Config
+ )
+ // Unmarshal (un-strictly) config version first for pre-validation
+ err := yaml.Unmarshal(data, &version)
+
+ if err != nil {
+ return nil, err
+ }
+
+ if err = Validate(version); err != nil {
+ return nil, err
+ }
+
+ // Unmarshal the default config
yaml.Unmarshal([]byte(DefaultConfig), &config)
- err := yaml.Unmarshal(data, &config)
+ // And finally strictly unmarshal the entire user-provided config
+ err = yaml.UnmarshalStrict(data, &config)
if err != nil {
return nil, err
diff --git a/config/reader_test.go b/config/reader_test.go
index e11e7af..51269ea 100644
--- a/config/reader_test.go
+++ b/config/reader_test.go
@@ -11,6 +11,7 @@ import (
func ExampleResolveIncludes() {
cfg, _ := config.ReadConfig([]byte(`---
+ version: v1
variants:
varA: { includes: [varB, varC] }
varB: { includes: [varD, varE] }
@@ -26,8 +27,37 @@ func ExampleResolveIncludes() {
// Output: [varF varD varE varB varC varA]
}
+func TestReadConfig_ErrorsOnUnknownYAML(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ version: v1
+ newphone: whodis
+ variants:
+ foo: {}`))
+
+ assert.EqualError(t,
+ err,
+ "yaml: unmarshal errors:\n"+
+ " line 2: field newphone not found in struct config.Config",
+ )
+}
+
+func TestReadConfig_ValidateVersionBeforeStrictUnmarshal(t *testing.T) {
+ _, err := config.ReadConfig([]byte(`---
+ version: foo
+ newphone: whodis
+ variants:
+ foo: {}`))
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `version: config version "foo" is unsupported`, msg)
+ }
+}
+
func TestResolveIncludesPreventsInfiniteRecursion(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
varA: { includes: [varB] }
varB: { includes: [varA] }`))
@@ -41,6 +71,7 @@ func TestResolveIncludesPreventsInfiniteRecursion(t *testing.T) {
func TestMultiLevelIncludes(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: nodejs-slim
variants:
build:
@@ -70,6 +101,7 @@ func TestMultiLevelIncludes(t *testing.T) {
func TestMultiIncludes(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
mammal:
base: neutral
diff --git a/config/runs_test.go b/config/runs_test.go
index 18d3726..8ae87fc 100644
--- a/config/runs_test.go
+++ b/config/runs_test.go
@@ -11,6 +11,7 @@ import (
func TestRunsConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
runs:
as: someuser
@@ -85,6 +86,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("as", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
as: foo-bar.baz`))
@@ -93,6 +95,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs: {}`))
assert.False(t, config.IsValidationError(err))
@@ -100,6 +103,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("no spaces", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
as: foo bar`))
@@ -112,6 +116,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("long enough", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
as: fo`))
@@ -124,6 +129,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("not root", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
as: root`))
@@ -138,6 +144,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("environment", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
environment:
foo: bar
@@ -152,6 +159,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs: {}`))
assert.False(t, config.IsValidationError(err))
@@ -160,6 +168,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
t.Run("spaces", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
environment:
foo fighter: bar`))
@@ -173,6 +182,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("dashes", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
environment:
foo-fighter: bar`))
@@ -186,6 +196,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("dots", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
environment:
foo.fighter: bar`))
@@ -199,6 +210,7 @@ func TestRunsConfigValidation(t *testing.T) {
t.Run("starts with number", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
runs:
environment:
1foo: bar`))
diff --git a/config/validation.go b/config/validation.go
index 646e49d..2529e00 100644
--- a/config/validation.go
+++ b/config/validation.go
@@ -31,20 +31,22 @@ var (
variantNameRegexp = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$`)
humanizedErrors = map[string]string{
- "abspath": `{{.Field}}: "{{.Value}}" is not a valid absolute non-root path`,
- "baseimage": `{{.Field}}: "{{.Value}}" is not a valid base image reference`,
- "debianpackage": `{{.Field}}: "{{.Value}}" is not a valid Debian package name`,
- "envvars": `{{.Field}}: contains invalid environment variable names`,
- "nodeenv": `{{.Field}}: "{{.Value}}" is not a valid Node environment name`,
- "required": `{{.Field}}: is required`,
- "username": `{{.Field}}: "{{.Value}}" is not a valid user name`,
- "variantref": `{{.Field}}: references an unknown variant "{{.Value}}"`,
- "variants": `{{.Field}}: contains a bad variant name`,
+ "abspath": `{{.Field}}: "{{.Value}}" is not a valid absolute non-root path`,
+ "baseimage": `{{.Field}}: "{{.Value}}" is not a valid base image reference`,
+ "currentversion": `{{.Field}}: config version "{{.Value}}" is unsupported`,
+ "debianpackage": `{{.Field}}: "{{.Value}}" is not a valid Debian package name`,
+ "envvars": `{{.Field}}: contains invalid environment variable names`,
+ "nodeenv": `{{.Field}}: "{{.Value}}" is not a valid Node environment name`,
+ "required": `{{.Field}}: is required`,
+ "username": `{{.Field}}: "{{.Value}}" is not a valid user name`,
+ "variantref": `{{.Field}}: references an unknown variant "{{.Value}}"`,
+ "variants": `{{.Field}}: contains a bad variant name`,
}
validatorAliases = map[string]string{
- "nodeenv": "alphanum",
- "username": "hostname,ne=root",
+ "currentversion": "eq=" + CurrentVersion,
+ "nodeenv": "alphanum",
+ "username": "hostname,ne=root",
}
validatorFuncs = map[string]validator.FuncCtx{
@@ -86,7 +88,7 @@ func newValidator() *validator.Validate {
// Config value. If the returned error is not nil, it will contain a
// user-friendly message describing all invalid field values.
//
-func Validate(config Config) error {
+func Validate(config interface{}) error {
validate := newValidator()
ctx := context.WithValue(context.Background(), rootCfgCtx, config)
diff --git a/config/variant_test.go b/config/variant_test.go
index a2f52c9..f5216f6 100644
--- a/config/variant_test.go
+++ b/config/variant_test.go
@@ -12,6 +12,7 @@ import (
func TestVariantConfig(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo
variants:
build: {}
@@ -215,6 +216,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("includes", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: { includes: [build] }`))
@@ -224,6 +226,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: {}`))
@@ -233,6 +236,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: { includes: [build, foobuild, foo_build] }`))
@@ -252,6 +256,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("ok", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: { copies: build }`))
@@ -261,6 +266,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("optional", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: {}`))
@@ -270,6 +276,7 @@ func TestVariantConfigValidation(t *testing.T) {
t.Run("bad", func(t *testing.T) {
_, err := config.ReadConfig([]byte(`---
+ version: v1
variants:
build: {}
foo: { copies: foobuild }`))
diff --git a/config/version.go b/config/version.go
new file mode 100644
index 0000000..4a145fd
--- /dev/null
+++ b/config/version.go
@@ -0,0 +1,12 @@
+package config
+
+// CurrentVersion declares the currently supported config version.
+//
+const CurrentVersion string = "v1"
+
+// VersionConfig contains a single field that allows for validation of the
+// config version independent from an entire Config struct.
+//
+type VersionConfig struct {
+ Version string `yaml:"version" validate:"required,currentversion"`
+}
diff --git a/config/version_test.go b/config/version_test.go
new file mode 100644
index 0000000..07df93e
--- /dev/null
+++ b/config/version_test.go
@@ -0,0 +1,44 @@
+package config_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "phabricator.wikimedia.org/source/blubber/config"
+)
+
+func TestVersionConfig_YAML(t *testing.T) {
+ cfg, err := config.ReadConfig([]byte(`---
+ version: v1
+ variants:
+ foo: {}`))
+
+ assert.Nil(t, err)
+
+ if assert.NoError(t, err) {
+ assert.Equal(t, "v1", cfg.Version)
+ }
+}
+
+func TestVersionConfig_Validation(t *testing.T) {
+ t.Run("supported version", func(t *testing.T) {
+ err := config.Validate(config.VersionConfig{
+ Version: "v1",
+ })
+
+ assert.False(t, config.IsValidationError(err))
+ })
+
+ t.Run("unsupported version", func(t *testing.T) {
+ err := config.Validate(config.VersionConfig{
+ Version: "v2",
+ })
+
+ if assert.True(t, config.IsValidationError(err)) {
+ msg := config.HumanizeValidationError(err)
+
+ assert.Equal(t, `version: config version "v2" is unsupported`, msg)
+ }
+ })
+}
diff --git a/docker/compiler_test.go b/docker/compiler_test.go
index 846304b..c5e2a28 100644
--- a/docker/compiler_test.go
+++ b/docker/compiler_test.go
@@ -13,6 +13,7 @@ import (
func TestSingleStageHasNoName(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo/bar
variants:
development: {}`))
@@ -27,6 +28,7 @@ func TestSingleStageHasNoName(t *testing.T) {
func TestMultiStageIncludesStageNames(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo/bar
variants:
build: {}
@@ -50,6 +52,7 @@ func TestMultiStageIncludesStageNames(t *testing.T) {
func TestMultipleArtifactsFromSameStage(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo/bar
variants:
build: {}
@@ -76,6 +79,7 @@ func TestMultipleArtifactsFromSameStage(t *testing.T) {
func TestMetaDataLabels(t *testing.T) {
cfg, err := config.ReadConfig([]byte(`---
+ version: v1
base: foo/bar
variants:
development: {}`))