pack/builder/config_reader_test.go

391 lines
10 KiB
Go

package builder_test
import (
"os"
"path/filepath"
"testing"
"github.com/heroku/color"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/pack/builder"
h "github.com/buildpacks/pack/testhelpers"
)
func TestConfig(t *testing.T) {
color.Disable(true)
defer color.Disable(false)
spec.Run(t, "testConfig", testConfig, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testConfig(t *testing.T, when spec.G, it spec.S) {
when("#ReadConfig", func() {
var (
tmpDir string
builderConfigPath string
err error
)
it.Before(func() {
tmpDir, err = os.MkdirTemp("", "config-test")
h.AssertNil(t, err)
builderConfigPath = filepath.Join(tmpDir, "builder.toml")
})
it.After(func() {
h.AssertNil(t, os.RemoveAll(tmpDir))
})
when("file is written properly", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[buildpacks]]
id = "buildpack/1"
version = "0.0.1"
uri = "https://example.com/buildpack-1.tgz"
[[buildpacks]]
image = "example.com/buildpack:2"
[[buildpacks]]
uri = "https://example.com/buildpack-3.tgz"
[[order]]
[[order.group]]
id = "buildpack/1"
exec-env = ["production"]
[[build.env]]
name = "key1"
value = "value1"
suffix = "append"
delim = "%"
exec-env = ["test"]
`), 0666))
})
it("returns a builder config", func() {
builderConfig, warns, err := builder.ReadConfig(builderConfigPath)
h.AssertNil(t, err)
h.AssertEq(t, len(warns), 0)
h.AssertEq(t, builderConfig.Buildpacks[0].ID, "buildpack/1")
h.AssertEq(t, builderConfig.Buildpacks[0].Version, "0.0.1")
h.AssertEq(t, builderConfig.Buildpacks[0].URI, "https://example.com/buildpack-1.tgz")
h.AssertEq(t, builderConfig.Buildpacks[0].ImageName, "")
h.AssertEq(t, builderConfig.Buildpacks[1].ID, "")
h.AssertEq(t, builderConfig.Buildpacks[1].URI, "")
h.AssertEq(t, builderConfig.Buildpacks[1].ImageName, "example.com/buildpack:2")
h.AssertEq(t, builderConfig.Buildpacks[2].ID, "")
h.AssertEq(t, builderConfig.Buildpacks[2].URI, "https://example.com/buildpack-3.tgz")
h.AssertEq(t, builderConfig.Buildpacks[2].ImageName, "")
h.AssertEq(t, builderConfig.Order[0].Group[0].ID, "buildpack/1")
h.AssertTrue(t, len(builderConfig.Order[0].Group[0].ExecEnv) == 1)
h.AssertEq(t, builderConfig.Order[0].Group[0].ExecEnv[0], "production")
h.AssertTrue(t, len(builderConfig.Build.Env) == 1)
h.AssertEq(t, builderConfig.Build.Env[0].Name, "key1")
h.AssertEq(t, builderConfig.Build.Env[0].Value, "value1")
h.AssertEq(t, string(builderConfig.Build.Env[0].Suffix), "append")
h.AssertTrue(t, len(builderConfig.Build.Env[0].ExecEnv) == 1)
h.AssertEq(t, builderConfig.Build.Env[0].ExecEnv[0], "test")
})
})
when("an error occurs while reading", func() {
it("bubbles up the error", func() {
_, _, err := builder.ReadConfig(builderConfigPath)
h.AssertError(t, err, "opening config file")
})
})
when("detecting warnings", func() {
when("'groups' field is used", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[buildpacks]]
id = "some.buildpack"
version = "some.buildpack.version"
[[groups]]
[[groups.buildpacks]]
id = "some.buildpack"
version = "some.buildpack.version"
[[order]]
[[order.group]]
id = "some.buildpack"
`), 0666))
})
it("returns error when obsolete 'groups' field is used", func() {
_, warns, err := builder.ReadConfig(builderConfigPath)
h.AssertError(t, err, "parse contents of")
h.AssertEq(t, len(warns), 0)
})
})
when("'order' is missing or empty", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[buildpacks]]
id = "some.buildpack"
version = "some.buildpack.version"
`), 0666))
})
it("returns warnings", func() {
_, warns, err := builder.ReadConfig(builderConfigPath)
h.AssertNil(t, err)
h.AssertSliceContainsOnly(t, warns, "empty 'order' definition")
})
})
when("unknown buildpack key is present", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[buildpacks]]
url = "noop-buildpack.tgz"
`), 0666))
})
it("returns an error", func() {
_, _, err := builder.ReadConfig(builderConfigPath)
h.AssertError(t, err, "unknown configuration element 'buildpacks.url'")
})
})
when("unknown array table is present", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[buidlpack]]
uri = "noop-buildpack.tgz"
`), 0666))
})
it("returns an error", func() {
_, _, err := builder.ReadConfig(builderConfigPath)
h.AssertError(t, err, "unknown configuration element 'buidlpack'")
})
})
})
when("system buildpack is defined", func() {
it.Before(func() {
h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(`
[[system.pre.buildpacks]]
id = "id-1"
version = "1.0"
optional = false
[[system.post.buildpacks]]
id = "id-2"
version = "2.0"
optional = true
`), 0666))
})
it("returns a builder config", func() {
builderConfig, _, err := builder.ReadConfig(builderConfigPath)
h.AssertNil(t, err)
h.AssertEq(t, len(builderConfig.System.Pre.Buildpacks), 1)
h.AssertEq(t, len(builderConfig.System.Post.Buildpacks), 1)
// Verify system.pre.buildpacks
h.AssertEq(t, builderConfig.System.Pre.Buildpacks[0].ID, "id-1")
h.AssertEq(t, builderConfig.System.Pre.Buildpacks[0].Version, "1.0")
h.AssertEq(t, builderConfig.System.Pre.Buildpacks[0].Optional, false)
// Verify system.post.buildpacks
h.AssertEq(t, builderConfig.System.Post.Buildpacks[0].ID, "id-2")
h.AssertEq(t, builderConfig.System.Post.Buildpacks[0].Version, "2.0")
h.AssertEq(t, builderConfig.System.Post.Buildpacks[0].Optional, true)
})
})
})
when("#ValidateConfig()", func() {
var (
testID = "testID"
testRunImage = "test-run-image"
testBuildImage = "test-build-image"
)
it("returns error if no stack id and no run images", func() {
config := builder.Config{
Stack: builder.StackConfig{
BuildImage: testBuildImage,
RunImage: testRunImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})
it("returns error if no build image", func() {
config := builder.Config{
Stack: builder.StackConfig{
ID: testID,
RunImage: testRunImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})
it("returns error if no run image", func() {
config := builder.Config{
Stack: builder.StackConfig{
ID: testID,
BuildImage: testBuildImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})
it("returns error if no run images image", func() {
config := builder.Config{
Build: builder.BuildConfig{
Image: testBuildImage,
},
Run: builder.RunConfig{
Images: []builder.RunImageConfig{{
Image: "",
}},
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images.image is required")
})
it("returns error if no stack or run image", func() {
config := builder.Config{
Build: builder.BuildConfig{
Image: testBuildImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})
it("returns error if no stack and no build image", func() {
config := builder.Config{
Run: builder.RunConfig{
Images: []builder.RunImageConfig{{
Image: testBuildImage,
}},
}}
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})
it("returns error if no stack, run, or build image", func() {
config := builder.Config{}
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})
})
when("#ParseBuildConfigEnv()", func() {
it("should return an error when name is not defined", func() {
_, _, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "",
Value: "vaiue",
},
}, "")
h.AssertNotNil(t, err)
})
it("should warn when the value is nil or empty string", func() {
env, warn, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "key",
Value: "",
Suffix: "override",
},
}, "")
h.AssertNotNil(t, warn)
h.AssertNil(t, err)
h.AssertMapContains[string, string](t, env, h.NewKeyValue[string, string]("key.override", ""))
})
it("should return an error when unknown suffix is specified", func() {
_, _, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "key",
Value: "",
Suffix: "invalid",
},
}, "")
h.AssertNotNil(t, err)
})
it("should override and show a warning when suffix or delim is defined multiple times", func() {
env, warn, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "key1",
Value: "value1",
Suffix: "append",
Delim: "%",
},
{
Name: "key1",
Value: "value2",
Suffix: "append",
Delim: ",",
},
{
Name: "key1",
Value: "value3",
Suffix: "default",
Delim: ";",
},
{
Name: "key1",
Value: "value4",
Suffix: "prepend",
Delim: ":",
},
}, "")
h.AssertNotNil(t, warn)
h.AssertNil(t, err)
h.AssertMapContains[string, string](
t,
env,
h.NewKeyValue[string, string]("key1.append", "value2"),
h.NewKeyValue[string, string]("key1.default", "value3"),
h.NewKeyValue[string, string]("key1.prepend", "value4"),
h.NewKeyValue[string, string]("key1.delim", ":"),
)
h.AssertMapNotContains[string, string](
t,
env,
h.NewKeyValue[string, string]("key1.append", "value1"),
h.NewKeyValue[string, string]("key1.delim", "%"),
h.NewKeyValue[string, string]("key1.delim", ","),
h.NewKeyValue[string, string]("key1.delim", ";"),
)
})
it("should return an error when `suffix` is defined as `append` or `prepend` without a `delim`", func() {
_, warn, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "key",
Value: "value",
Suffix: "append",
},
}, "")
h.AssertNotNil(t, warn)
h.AssertNotNil(t, err)
})
it("when suffix is NONE or omitted should default to `override`", func() {
env, warn, err := builder.ParseBuildConfigEnv([]builder.BuildConfigEnv{
{
Name: "key",
Value: "value",
Suffix: "",
},
}, "")
h.AssertNotNil(t, warn)
h.AssertNil(t, err)
h.AssertMapContains[string, string](t, env, h.NewKeyValue[string, string]("key", "value"))
})
})
}