diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 8228ab779..47d392c82 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -32,12 +32,13 @@ import ( lifecycleplatform "github.com/buildpacks/lifecycle/platform" ) +var buildConfigDir = cnbBuildConfigDir() + const ( packName = "Pack CLI" - cnbDir = "/cnb" - buildConfigDir = "/cnb/build-config" - buildpacksDir = "/cnb/buildpacks" + cnbDir = "/cnb" + buildpacksDir = "/cnb/buildpacks" orderPath = "/cnb/order.toml" stackPath = "/cnb/stack.toml" @@ -68,6 +69,7 @@ const ( // Builder represents a pack builder, used to build images type Builder struct { baseImageName string + buildConfigEnv map[string]string image imgutil.Image layerWriterFactory archive.TarWriterFactory lifecycle Lifecycle @@ -147,6 +149,7 @@ func constructBuilder(img imgutil.Image, newName string, errOnMissingLabel bool, metadata: metadata, lifecycleDescriptor: constructLifecycleDescriptor(metadata), env: map[string]string{}, + buildConfigEnv: map[string]string{}, validateMixins: true, additionalBuildpacks: *buildpack.NewModuleManager(opts.flatten, opts.depth), additionalExtensions: *buildpack.NewModuleManager(opts.flatten, opts.depth), @@ -350,6 +353,11 @@ func (b *Builder) SetEnv(env map[string]string) { b.env = env } +// SetBuildConfigEnv sets an environment variable to a value that will take action on platform environment variables basedon filename suffix +func (b *Builder) SetBuildConfigEnv(env map[string]string) { + b.buildConfigEnv = env +} + // SetOrder sets the order of the builder func (b *Builder) SetOrder(order dist.Order) { b.order = order @@ -526,6 +534,19 @@ func (b *Builder) Save(logger logging.Logger, creatorMetadata CreatorMetadata) e return errors.Wrap(err, "adding run.tar layer") } + if len(b.buildConfigEnv) > 0 { + logger.Debugf("Provided Build Config Environment Variables\n %s", style.Map(b.env, " ", "\n")) + } + + buildConfigEnvTar, err := b.buildConfigEnvLayer(tmpDir, b.buildConfigEnv) + if err != nil { + return errors.Wrap(err, "retrieving build-config-env layer") + } + + if err := b.image.AddLayer(buildConfigEnvTar); err != nil { + return errors.Wrap(err, "adding build-config-env layer") + } + if len(b.env) > 0 { logger.Debugf("Provided Environment Variables\n %s", style.Map(b.env, " ", "\n")) } @@ -904,7 +925,7 @@ func (b *Builder) defaultDirsLayer(dest string) (string, error) { } // can't use filepath.Join(), to ensure Windows doesn't transform it to Windows join - for _, path := range []string{cnbDir, dist.BuildpacksDir, dist.ExtensionsDir, platformDir, platformDir + "/env"} { + for _, path := range []string{cnbDir, dist.BuildpacksDir, dist.ExtensionsDir, platformDir, platformDir + "/env", buildConfigDir, buildConfigDir + "/env"} { if err := lw.WriteHeader(b.rootOwnedDir(path, ts)); err != nil { return "", errors.Wrapf(err, "creating %s dir in layer", style.Symbol(path)) } @@ -1103,6 +1124,33 @@ func (b *Builder) envLayer(dest string, env map[string]string) (string, error) { return fh.Name(), nil } +func (b *Builder) buildConfigEnvLayer(dest string, env map[string]string) (string, error) { + fh, err := os.Create(filepath.Join(dest, "build-config-env.tar")) + if err != nil { + return "", err + } + defer fh.Close() + + lw := b.layerWriterFactory.NewWriter(fh) + defer lw.Close() + + for k, v := range env { + if err := lw.WriteHeader(&tar.Header{ + Name: path.Join(buildConfigDir, "env", k), + Size: int64(len(v)), + Mode: 0644, + ModTime: archive.NormalizedDateTime, + }); err != nil { + return "", err + } + if _, err := lw.Write([]byte(v)); err != nil { + return "", err + } + } + + return fh.Name(), nil +} + func (b *Builder) whiteoutLayer(tmpDir string, i int, bpInfo dist.ModuleInfo) (string, error) { bpWhiteoutsTmpDir := filepath.Join(tmpDir, strconv.Itoa(i)+"_whiteouts") if err := os.MkdirAll(bpWhiteoutsTmpDir, os.ModePerm); err != nil { @@ -1258,3 +1306,11 @@ func (e errModuleTar) Info() dist.ModuleInfo { func (e errModuleTar) Path() string { return "" } + +func cnbBuildConfigDir() string { + if env, ok := os.LookupEnv("CNB_BUILD_CONFIG_DIR"); !ok { + return "/cnb/build-config" + } else { + return env + } +} diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index 13a958c72..2745e9e97 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -384,6 +384,20 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { ) }) + it("creates the build-config dir", func() { + h.AssertNil(t, subject.Save(logger, builder.CreatorMetadata{})) + h.AssertEq(t, baseImage.IsSaved(), true) + + layerTar, err := baseImage.FindLayerWithPath("/cnb/build-config") + h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/cnb/build-config", + h.IsDirectory(), + h.HasOwnerAndGroup(0, 0), + h.HasFileMode(0755), + h.HasModTime(archive.NormalizedDateTime), + ) + }) + it("creates the buildpacks dir", func() { h.AssertNil(t, subject.Save(logger, builder.CreatorMetadata{})) h.AssertEq(t, baseImage.IsSaved(), true) @@ -1607,6 +1621,35 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { }) }) + when("#SetBuildConfigEnv", func() { + it.Before(func() { + subject.SetBuildConfigEnv(map[string]string{ + "SOME_KEY": "some-val", + "OTHER_KEY.append": "other-val", + "OTHER_KEY.delim": ":", + }) + h.AssertNil(t, subject.Save(logger, builder.CreatorMetadata{})) + h.AssertEq(t, baseImage.IsSaved(), true) + }) + + it("adds the env vars as files to the image", func() { + layerTar, err := baseImage.FindLayerWithPath("/cnb/build-config/env/SOME_KEY") + h.AssertNil(t, err) + h.AssertOnTarEntry(t, layerTar, "/cnb/build-config/env/SOME_KEY", + h.ContentEquals(`some-val`), + h.HasModTime(archive.NormalizedDateTime), + ) + h.AssertOnTarEntry(t, layerTar, "/cnb/build-config/env/OTHER_KEY.append", + h.ContentEquals(`other-val`), + h.HasModTime(archive.NormalizedDateTime), + ) + h.AssertOnTarEntry(t, layerTar, "/cnb/build-config/env/OTHER_KEY.delim", + h.ContentEquals(`:`), + h.HasModTime(archive.NormalizedDateTime), + ) + }) + }) + when("#SetEnv", func() { it.Before(func() { subject.SetEnv(map[string]string{ diff --git a/internal/commands/builder_create.go b/internal/commands/builder_create.go index b606b0873..a98cb4352 100644 --- a/internal/commands/builder_create.go +++ b/internal/commands/builder_create.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os" "path/filepath" "strings" @@ -76,20 +75,18 @@ Creating a custom builder allows you to control what buildpacks are used and wha return errors.Wrap(err, "getting absolute path for config") } - envMap, warnings, err := parseBuildConfigEnv(builderConfig.Build.Env, flags.BuilderTomlPath) + envMap, warnings, err := ParseBuildConfigEnv(builderConfig.Build.Env, flags.BuilderTomlPath) for _, v := range warnings { logger.Warn(v) } if err != nil { return err } - if err := generateBuildConfigEnvFiles(envMap); err != nil { - return err - } imageName := args[0] if err := pack.CreateBuilder(cmd.Context(), client.CreateBuilderOptions{ RelativeBaseDir: relativeBaseDir, + BuildConfigEnv: envMap, BuilderName: imageName, Config: builderConfig, Publish: flags.Publish, @@ -150,45 +147,6 @@ func validateCreateFlags(flags *BuilderCreateFlags, cfg config.Config) error { return nil } -func generateBuildConfigEnvFiles(envMap map[string]string) error { - dir, err := createBuildConfigEnvDir() - if err != nil { - return err - } - for k, v := range envMap { - f, err := os.Create(filepath.Join(dir, k)) - if err != nil { - return err - } - f.WriteString(v) - if e := f.Close(); e != nil { - return e - } - } - return nil -} - -func CnbBuildConfigDir() string { - if v := os.Getenv("CNB_BUILD_CONFIG_DIR"); v == "" || len(v) == 0 { - return "/cnb/build-config" - } else { - return v - } -} - -func createBuildConfigEnvDir() (dir string, err error) { - dir = filepath.Join(CnbBuildConfigDir(), "env") - _, err = os.Stat(dir) - if os.IsNotExist(err) { - err := os.MkdirAll(dir, os.ModePerm) - if err != nil { - return dir, err - } - return dir, nil - } - return dir, nil -} - func getActionType(suffix builder.Suffix) (suffixString string, err error) { const delim = "." switch suffix { @@ -206,7 +164,21 @@ func getActionType(suffix builder.Suffix) (suffixString string, err error) { return suffixString, errors.Errorf("unknown action type %s", style.Symbol(string(suffix))) } } -func GetBuildConfigEnvFileName(env builder.BuildConfigEnv) (suffixName, delimName string, err error) { + +func getFilePrefixSuffix(filename string) (prefix, suffix string, err error) { + val := strings.Split(filename, ".") + if len(val) <= 1 { + return val[0], suffix, errors.Errorf("Suffix might be null") + } + if len(val) == 2 { + suffix = val[1] + } else { + strings.Join(val[1:], ".") + } + return val[0], suffix, err +} + +func getBuildConfigEnvFileName(env builder.BuildConfigEnv) (suffixName, delimName string, err error) { suffix, err := getActionType(env.Suffix) if err != nil { return suffixName, delimName, err @@ -222,8 +194,9 @@ func GetBuildConfigEnvFileName(env builder.BuildConfigEnv) (suffixName, delimNam return suffixName, delimName, err } -func parseBuildConfigEnv(env []builder.BuildConfigEnv, path string) (envMap map[string]string, warnings []string, err error) { +func ParseBuildConfigEnv(env []builder.BuildConfigEnv, path string) (envMap map[string]string, warnings []string, err error) { envMap = map[string]string{} + var appendOrPrependWithoutDelim = 0 for _, v := range env { if name := v.Name; name == "" || len(name) == 0 { return nil, nil, errors.Wrapf(errors.Errorf("env name should not be empty"), "parse contents of '%s'", path) @@ -231,7 +204,7 @@ func parseBuildConfigEnv(env []builder.BuildConfigEnv, path string) (envMap map[ if val := v.Value; val == "" || len(val) == 0 { warnings = append(warnings, fmt.Sprintf("empty value for key/name %s", style.Symbol(v.Name))) } - suffixName, delimName, err := GetBuildConfigEnvFileName(v) + suffixName, delimName, err := getBuildConfigEnvFileName(v) if err != nil { return envMap, warnings, err } @@ -246,5 +219,19 @@ func parseBuildConfigEnv(env []builder.BuildConfigEnv, path string) (envMap map[ } envMap[suffixName] = v.Value } + + for k := range envMap { + name, suffix, err := getFilePrefixSuffix(k) + if err != nil { + continue + } + if _, ok := envMap[name+".delim"]; (suffix == "append" || suffix == "prepend") && !ok { + warnings = append(warnings, fmt.Sprintf(errors.Errorf("env with name/key %s with suffix %s must to have a %s value", style.Symbol(name), style.Symbol(suffix), style.Symbol("delim")).Error(), "parse contents of '%s'", path)) + appendOrPrependWithoutDelim++ + } + } + if appendOrPrependWithoutDelim > 0 { + return envMap, warnings, errors.Errorf("error parsing [[build.env]] in file '%s'", path) + } return envMap, warnings, err } diff --git a/internal/commands/builder_create_test.go b/internal/commands/builder_create_test.go index b36b67611..f5f968439 100644 --- a/internal/commands/builder_create_test.go +++ b/internal/commands/builder_create_test.go @@ -13,6 +13,7 @@ import ( "github.com/sclevine/spec/report" "github.com/spf13/cobra" + "github.com/buildpacks/pack/builder" "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" "github.com/buildpacks/pack/internal/config" @@ -47,76 +48,109 @@ const validConfigWithExtensions = ` ` -const ActionNONE = validConfig + ` -[[build.env]] -name = "actionNone" -value = "actionNoneValue" -` -const ActionDEFAULT = validConfig + ` -[[build.env]] -name = "actionDefault" -value = "actionDefaultValue" -suffix = "default" -` -const ActionOVERRIDE = validConfig + ` -[[build.env]] -name = "actionOverride" -value = "actionOverrideValue" -suffix = "override" -` -const ActionAPPEND = validConfig + ` -[[build.env]] -name = "actionAppend" -value = "actionAppendValue" -suffix = "append" -` -const ActionPREPEND = validConfig + ` -[[build.env]] -name = "actionPrepend" -value = "actionPrependValue" -suffix = "prepend" -` -const ActionDELIMIT = validConfig + ` -[[build.env]] -name = "actionDelimit" -delim = ":" -` -const ActionUNKNOWN = validConfig + ` -[[build.env]] -name = "actionUnknown" -value = "actionUnknownValue" -suffix = "unknown" -` -const ActionMULTIPLE = validConfig + ` -[[build.env]] -name = "MY_VAR" -value = "actionAppendValue" -suffix = "append" -delim = ":" -[[build.env]] -name = "MY_VAR" -value = "actionDefaultValue" -suffix = "default" -delim = ":" -[[build.env]] -name = "MY_VAR" -value = "actionPrependValue" -suffix = "prepend" -delim = ":" -` +var BuildConfigEnvSuffixNone = builder.BuildConfigEnv{ + Name: "suffixNone", + Value: "suffixNoneValue", +} -const ActionWarning = validConfig + ` -[[build.env]] -name = "actionWarning" -value = "" -` +var BuildConfigEnvSuffixNoneWithEmptySuffix = builder.BuildConfigEnv{ + Name: "suffixNoneWithEmptySuffix", + Value: "suffixNoneWithEmptySuffixValue", + Suffix: "", +} -const ActionError = validConfig + ` -[[build.env]] -name = "" -value = "some-value" -suffix = "default" -` +var BuildConfigEnvSuffixDefault = builder.BuildConfigEnv{ + Name: "suffixDefault", + Value: "suffixDefaultValue", + Suffix: "default", +} + +var BuildConfigEnvSuffixOverride = builder.BuildConfigEnv{ + Name: "suffixOverride", + Value: "suffixOverrideValue", + Suffix: "override", +} + +var BuildConfigEnvSuffixAppend = builder.BuildConfigEnv{ + Name: "suffixAppend", + Value: "suffixAppendValue", + Suffix: "append", + Delim: ":", +} + +var BuildConfigEnvSuffixPrepend = builder.BuildConfigEnv{ + Name: "suffixPrepend", + Value: "suffixPrependValue", + Suffix: "prepend", + Delim: ":", +} + +var BuildConfigEnvDelimWithoutSuffix = builder.BuildConfigEnv{ + Name: "delimWithoutSuffix", + Delim: ":", +} + +var BuildConfigEnvSuffixUnknown = builder.BuildConfigEnv{ + Name: "suffixUnknown", + Value: "suffixUnknownValue", + Suffix: "unknown", +} + +var BuildConfigEnvSuffixMultiple = []builder.BuildConfigEnv{ + { + Name: "MY_VAR", + Value: "suffixAppendValueValue", + Suffix: "append", + Delim: ";", + }, + { + Name: "MY_VAR", + Value: "suffixDefaultValue", + Suffix: "default", + Delim: "%", + }, + { + Name: "MY_VAR", + Value: "suffixPrependValue", + Suffix: "prepend", + Delim: ":", + }, +} + +var BuildConfigEnvEmptyValue = builder.BuildConfigEnv{ + Name: "warning", + Value: "", +} + +var BuildConfigEnvEmptyName = builder.BuildConfigEnv{ + Name: "", + Value: "suffixUnknownValue", + Suffix: "default", +} + +var BuildConfigEnvSuffixPrependWithoutDelim = builder.BuildConfigEnv{ + Name: "suffixPrepend", + Value: "suffixPrependValue", + Suffix: "prepend", +} + +var BuildConfigEnvDelimWithoutSuffixAppendOrPrepend = builder.BuildConfigEnv{ + Name: "delimWithoutActionAppendOrPrepend", + Value: "some-value", + Delim: ":", +} + +var BuildConfigEnvDelimWithSameSuffixAndName = []builder.BuildConfigEnv{ + { + Name: "MY_VAR", + Value: "some-value", + Suffix: "", + }, + { + Name: "MY_VAR", + Value: "some-value", + }, +} func TestCreateCommand(t *testing.T) { color.Disable(true) @@ -242,183 +276,112 @@ func testCreateCommand(t *testing.T, when spec.G, it spec.S) { }) }) - when("buildConfigEnv files generated", func() { - var fileIndex = 0 - buildConfigEnvDir := commands.CnbBuildConfigDir() - it.Before(func() { - err := os.MkdirAll(buildConfigEnvDir, os.ModePerm) + when("#ParseBuildpackConfigEnv", func() { + it("should create envMap as expected when suffix is omitted", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixNone}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixNone.Name: BuildConfigEnvSuffixNone.Value, + }) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertNil(t, os.WriteFile(builderConfigPath, []byte(getBuildConfigEnvFileContent(fileIndex)), 0666)) }) - it.After(func() { - err := os.RemoveAll(buildConfigEnvDir) + it("should create envMap as expected when suffix is empty string", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixNoneWithEmptySuffix}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixNoneWithEmptySuffix.Name: BuildConfigEnvSuffixNoneWithEmptySuffix.Value, + }) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - fileIndex++ }) - it("should create content as expected when ActionType `NONE`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap as expected when suffix is `default`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixDefault}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixDefault.Name + "." + string(BuildConfigEnvSuffixDefault.Suffix): BuildConfigEnvSuffixDefault.Value, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should create content as expected when ActionType `DEFAULT`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap as expected when suffix is `override`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixOverride}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixOverride.Name + "." + string(BuildConfigEnvSuffixOverride.Suffix): BuildConfigEnvSuffixOverride.Value, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should create content as expected when ActionType `OVERRIDE`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap as expected when suffix is `append`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixAppend}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixAppend.Name + "." + string(BuildConfigEnvSuffixAppend.Suffix): BuildConfigEnvSuffixAppend.Value, + BuildConfigEnvSuffixAppend.Name + ".delim": BuildConfigEnvSuffixAppend.Delim, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should create content as expected when ActionType `APPEND`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap as expected when suffix is `prepend`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixPrepend}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixPrepend.Name + "." + string(BuildConfigEnvSuffixPrepend.Suffix): BuildConfigEnvSuffixPrepend.Value, + BuildConfigEnvSuffixPrepend.Name + ".delim": BuildConfigEnvSuffixPrepend.Delim, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) + h.AssertEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should create content as expected when ActionType `PREPEND`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap as expected when delim is specified", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvDelimWithoutSuffix}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvDelimWithoutSuffix.Name: BuildConfigEnvDelimWithoutSuffix.Value, + BuildConfigEnvDelimWithoutSuffix.Name + ".delim": BuildConfigEnvDelimWithoutSuffix.Delim, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) + h.AssertNotEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should create content as expected when ActionType `DELIMIT`", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should create envMap with a warning when `value` is empty", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvEmptyValue}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvEmptyValue.Name: BuildConfigEnvEmptyValue.Value, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) + h.AssertNotEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should return an error when unknown ActionType passed", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, - }) - var bufErr bytes.Buffer - command.SetErr(&bufErr) - h.AssertNil(t, command.Execute()) - h.AssertNotEq(t, bufErr.String(), "") - name := actionTypesMap[fileIndex][0][0] - _, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) + it("should return an error when `name` is empty", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvEmptyName}, "") + h.AssertEq(t, envMap, map[string]string(nil)) + h.AssertEq(t, len(warnings), 0) h.AssertNotNil(t, err) }) - it("should create content as expected when multiple ActionTypes passed", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should return warnings when `apprend` or `prepend` is used without `delim`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixPrependWithoutDelim}, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixPrependWithoutDelim.Name + "." + string(BuildConfigEnvSuffixPrependWithoutDelim.Suffix): BuildConfigEnvSuffixPrependWithoutDelim.Value, }) - h.AssertNil(t, command.Execute()) - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) - - name = actionTypesMap[fileIndex][1][0] - file, err = os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err = os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][1][1], string(content)) - - name = actionTypesMap[fileIndex][2][0] - file, err = os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err = os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][2][1], string(content)) - - name = actionTypesMap[fileIndex][3][0] - file, err = os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err = os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][3][1], string(content)) + h.AssertNotEq(t, len(warnings), 0) + h.AssertNotNil(t, err) }) - it("should show warnings when env value is empty", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should return an error when unknown `suffix` is used", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv([]builder.BuildConfigEnv{BuildConfigEnvSuffixUnknown}, "") + h.AssertEq(t, envMap, map[string]string{}) + h.AssertEq(t, len(warnings), 0) + h.AssertNotNil(t, err) + }) + it("should override with the last specified delim when `[[build.env]]` has multiple delims with same `name` with a `append` or `prepend` suffix", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv(BuildConfigEnvSuffixMultiple, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvSuffixMultiple[0].Name + "." + string(BuildConfigEnvSuffixMultiple[0].Suffix): BuildConfigEnvSuffixMultiple[0].Value, + BuildConfigEnvSuffixMultiple[1].Name + "." + string(BuildConfigEnvSuffixMultiple[1].Suffix): BuildConfigEnvSuffixMultiple[1].Value, + BuildConfigEnvSuffixMultiple[2].Name + "." + string(BuildConfigEnvSuffixMultiple[2].Suffix): BuildConfigEnvSuffixMultiple[2].Value, + BuildConfigEnvSuffixMultiple[2].Name + ".delim": BuildConfigEnvSuffixMultiple[2].Delim, }) - var bufOut bytes.Buffer - command.SetOut(&bufOut) - h.AssertNil(t, command.Execute()) - h.AssertNotEq(t, bufOut.String(), "") - name := actionTypesMap[fileIndex][0][0] - file, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) + h.AssertNotEq(t, len(warnings), 0) h.AssertNil(t, err) - h.AssertEq(t, name, file.Name()) - content, err := os.ReadFile(filepath.Join(buildConfigEnvDir, file.Name())) - h.AssertNil(t, err) - h.AssertEq(t, actionTypesMap[fileIndex][0][1], string(content)) }) - it("should return an error when env.Name is empty", func() { - command.SetArgs([]string{ - "some/builder", - "--config", builderConfigPath, + it("should override `value` with the last read value when a `[[build.env]]` has same `name` with same `suffix`", func() { + envMap, warnings, err := commands.ParseBuildConfigEnv(BuildConfigEnvDelimWithSameSuffixAndName, "") + h.AssertEq(t, envMap, map[string]string{ + BuildConfigEnvDelimWithSameSuffixAndName[1].Name: BuildConfigEnvDelimWithSameSuffixAndName[1].Value, }) - var bufErr bytes.Buffer - command.SetErr(&bufErr) - h.AssertNil(t, command.Execute()) - h.AssertNotEq(t, bufErr.String(), "") - name := actionTypesMap[fileIndex][0][0] - _, err := os.Stat(filepath.Clean(filepath.Join(buildConfigEnvDir, name))) - h.AssertNotNil(t, err) + h.AssertNotEq(t, len(warnings), 0) + h.AssertNil(t, err) }) }) @@ -464,48 +427,3 @@ func testCreateCommand(t *testing.T, when spec.G, it spec.S) { }) }) } - -func getBuildConfigEnvFileContent(index int) string { - switch index { - case 0: - return ActionNONE - case 1: - return ActionDEFAULT - case 2: - return ActionOVERRIDE - case 3: - return ActionAPPEND - case 4: - return ActionPREPEND - case 5: - return ActionDELIMIT - case 6: - return ActionUNKNOWN - case 7: - return ActionMULTIPLE - case 8: - return ActionWarning - case 9: - return ActionError - default: - return "" - } -} - -var actionTypesMap = map[int]map[int]map[int]string{ - 0: {0: {0: "actionNone", 1: "actionNoneValue"}}, - 1: {0: {0: "actionDefault.default", 1: "actionDefaultValue"}}, - 2: {0: {0: "actionOverride.override", 1: "actionOverrideValue"}}, - 3: {0: {0: "actionAppend.append", 1: "actionAppendValue"}}, - 4: {0: {0: "actionPrepend.prepend", 1: "actionPrependValue"}}, - 5: {0: {0: "actionDelim.delim", 1: ":"}}, - 6: {0: {0: "actionUnknown.unknown", 1: "actionUnknownValue"}}, - 7: { - 0: {0: "MY_VAR.append", 1: "actionAppendValue"}, - 1: {0: "MY_VAR.default", 1: "actionDefaultValue"}, - 2: {0: "MY_VAR.prepend", 1: "actionPrependValue"}, - 3: {0: "MY_VAR.delim", 1: ":"}, - }, - 8: {0: {0: "actionWarning", 1: ""}}, - 9: {0: {0: ".default", 1: "some-value"}}, -} diff --git a/pkg/client/create_builder.go b/pkg/client/create_builder.go index 28cd2fa7c..12a6527c6 100644 --- a/pkg/client/create_builder.go +++ b/pkg/client/create_builder.go @@ -29,6 +29,9 @@ type CreateBuilderOptions struct { // Name of the builder. BuilderName string + // BuildConfigEnv for Builder + BuildConfigEnv map[string]string + // Configuration that defines the functionality a builder provides. Config pubbldr.Config @@ -79,6 +82,11 @@ func (c *Client) CreateBuilder(ctx context.Context, opts CreateBuilderOptions) e bldr.SetStack(opts.Config.Stack) } bldr.SetRunImage(opts.Config.Run) + if opts.BuildConfigEnv == nil || len(opts.BuildConfigEnv) == 0 { + bldr.SetBuildConfigEnv(make(map[string]string)) + } else { + bldr.SetBuildConfigEnv(opts.BuildConfigEnv) + } return bldr.Save(c.logger, builder.CreatorMetadata{Version: c.version}) } @@ -191,6 +199,11 @@ func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOption } bldr.SetLifecycle(lifecycle) + if opts.BuildConfigEnv == nil || len(opts.BuildConfigEnv) == 0 { + bldr.SetBuildConfigEnv(make(map[string]string)) + } else { + bldr.SetBuildConfigEnv(opts.BuildConfigEnv) + } return bldr, nil }