From 69cbba4450dde95d2540af756861c4d7d2d11782 Mon Sep 17 00:00:00 2001 From: Ramkumar Chinchani Date: Fri, 9 Dec 2022 01:16:12 +0000 Subject: [PATCH] feat(build): add a substitute-file option Currently, all substitution args must be specified on command-line. Sometimes convenient to add them all in a file and read that file. Signed-off-by: Ramkumar Chinchani --- build.go | 20 +++++++++ cmd/build.go | 5 +++ log/log.go | 8 ++++ test/basic.bats | 117 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 149 insertions(+), 1 deletion(-) diff --git a/build.go b/build.go index c83d271f..68f14b79 100644 --- a/build.go +++ b/build.go @@ -15,6 +15,7 @@ import ( "github.com/opencontainers/umoci/mutate" "github.com/opencontainers/umoci/oci/casext" "github.com/pkg/errors" + "gopkg.in/yaml.v2" "stackerbuild.io/stacker/container" "stackerbuild.io/stacker/log" "stackerbuild.io/stacker/types" @@ -27,6 +28,7 @@ type BuildArgs struct { LeaveUnladen bool NoCache bool Substitute []string + SubstituteFile string OnRunFailure string LayerTypes []types.LayerType OrderOnly bool @@ -44,6 +46,24 @@ type Builder struct { // NewBuilder initializes a new Builder struct func NewBuilder(opts *BuildArgs) *Builder { + if opts.SubstituteFile != "" { + bytes, err := os.ReadFile(opts.SubstituteFile) + if err != nil { + log.Fatalf("unable to read substitute-file:%s, err:%e", opts.SubstituteFile, err) + return nil + } + + var yamlMap map[string]string + if err := yaml.Unmarshal(bytes, &yamlMap); err != nil { + log.Fatalf("unable to unmarshal substitute-file:%s, err:%s", opts.SubstituteFile, err) + return nil + } + + for k, v := range yamlMap { + opts.Substitute = append(opts.Substitute, fmt.Sprintf("%s=%s", k, v)) + } + } + return &Builder{ builtStackerfiles: make(map[string]*types.Stackerfile, 1), opts: opts, diff --git a/cmd/build.go b/cmd/build.go index 47c8abdf..6d633283 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -37,6 +37,10 @@ func initCommonBuildFlags() []cli.Flag { Name: "substitute", Usage: "variable substitution in stackerfiles, FOO=bar format", }, + cli.StringFlag{ + Name: "substitute-file", + Usage: "file containing variable substitution in stackerfiles, 'FOO: bar' yaml format", + }, cli.StringFlag{ Name: "on-run-failure", Usage: "command to run inside container if run fails (useful for inspection)", @@ -90,6 +94,7 @@ func newBuildArgs(ctx *cli.Context) (stacker.BuildArgs, error) { Config: config, NoCache: ctx.Bool("no-cache"), Substitute: ctx.StringSlice("substitute"), + SubstituteFile: ctx.String("substitute-file"), OnRunFailure: ctx.String("on-run-failure"), OrderOnly: ctx.Bool("order-only"), HashRequired: ctx.Bool("require-hash"), diff --git a/log/log.go b/log/log.go index 03e67f1c..553383f0 100644 --- a/log/log.go +++ b/log/log.go @@ -46,6 +46,14 @@ func Infof(msg string, v ...interface{}) { addStackerLogSentinel(log.NewEntry(log.Log.(*log.Logger))).Infof(msg, v...) } +func Errorf(msg string, v ...interface{}) { + addStackerLogSentinel(log.NewEntry(log.Log.(*log.Logger))).Errorf(msg, v...) +} + +func Fatalf(msg string, v ...interface{}) { + addStackerLogSentinel(log.NewEntry(log.Log.(*log.Logger))).Fatalf(msg, v...) +} + type TextHandler struct { out io.StringWriter timestamp bool diff --git a/test/basic.bats b/test/basic.bats index 096f1f34..71b38caa 100644 --- a/test/basic.bats +++ b/test/basic.bats @@ -187,4 +187,119 @@ EOF bad_stacker build [ "$status" -eq 1 ] echo $output | grep "forbidden" -} +} + +@test "basic workings with substitutions from a file" { + cat > subs.yaml << EOF + FAVICON: favicon.ico +EOF + cat > stacker.yaml < stacker_yaml_annotation + + # now we need to do --substitute FAVICON=favicon.ico + sed -e 's/$FAVICON/favicon.ico/g' stacker.yaml > stacker_after_subs + + diff -U5 stacker_yaml_annotation stacker_after_subs + + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.Env[0]')" = "FOO=bar" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.User')" = "1000" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.Volumes["/data/db"]')" = "{}" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.Labels["foo"]')" = "bar" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.Labels["bar"]')" = "baz" ] + [ "$(cat oci/blobs/sha256/$config | jq -r '.config.WorkingDir')" = "/meshuggah/rocks" ] + + # TODO: this kind of sucks and is backwards, but now when running as a + # privileged container, stacker's code will render $SUDO_USER as the user. + # However, when running as an unprivileged user, the re-exec will cause + # stacker to think that it is running as root, and render the author as + # root. We could/should fix this, but AFAIK nobody pays attention to this + # anyway... + if [ "$PRIVILEGE_LEVEL" = "priv" ]; then + [ "$(cat oci/blobs/sha256/$config | jq -r '.author')" = "$SUDO_USER@$(hostname)" ] + else + [ "$(cat oci/blobs/sha256/$config | jq -r '.author')" = "root@$(hostname)" ] + fi + cat oci/blobs/sha256/$config | jq -r '.author' + + manifest2=$(cat oci/index.json | jq -r .manifests[0].digest | cut -f2 -d:) + [ "$manifest" = "$manifest2" ] + layer2=$(cat oci/blobs/sha256/$manifest | jq -r .layers[0].digest) + [ "$layer" = "$layer2" ] + + # let's check that the main tar stuff is understood by umoci + umoci unpack --image oci:layer1 dest + [ ! -f dest/rootfs/favicon.ico ] + [ ! -d dest/rootfs/stacker ] +}