Skip to content

Commit

Permalink
feat(build): add a substitute-file option
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
rchincha committed Dec 9, 2022
1 parent 817d36a commit 69cbba4
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 1 deletion.
20 changes: 20 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -27,6 +28,7 @@ type BuildArgs struct {
LeaveUnladen bool
NoCache bool
Substitute []string
SubstituteFile string
OnRunFailure string
LayerTypes []types.LayerType
OrderOnly bool
Expand All @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -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"),
Expand Down
8 changes: 8 additions & 0 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
117 changes: 116 additions & 1 deletion test/basic.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
centos:
from:
type: tar
url: .stacker/layer-bases/centos.tar
import:
- ./stacker.yaml
- https://www.cisco.com/favicon.ico
- ./executable
run:
- cp /stacker/\$FAVICON /\$FAVICON
- ls -al /stacker
- cp /stacker/executable /usr/bin/executable
entrypoint: echo hello world
environment:
FOO: bar
volumes:
- /data/db
labels:
foo: bar
bar: baz
working_dir: /meshuggah/rocks
runtime_user: 1000
layer1:
from:
type: built
tag: centos
run:
- rm /favicon.ico
EOF

touch executable
chmod +x executable
mkdir -p .stacker/layer-bases
chmod 777 .stacker/layer-bases
image_copy oci:$CENTOS_OCI oci:.stacker/layer-bases/oci:centos
umoci unpack --image .stacker/layer-bases/oci:centos dest
tar caf .stacker/layer-bases/centos.tar -C dest/rootfs .
rm -rf dest

stacker build --substitute-file subs.yaml
[ "$status" -eq 0 ]

# did we really download the image to the right place?
[ -f .stacker/layer-bases/centos.tar ]

# did run actually copy the favicon to the right place?
stacker grab centos:/favicon.ico
[ "$(sha .stacker/imports/centos/favicon.ico)" == "$(sha favicon.ico)" ]

[ ! -f roots/layer1/rootfs/favicon.ico ] || [ ! -f roots/layer1/overlay/favicon.ico ]

rm executable
stacker grab centos:/usr/bin/executable
[ "$(stat --format="%a" executable)" = "755" ]

# did we do a copy correctly?
[ "$(sha .stacker/imports/centos/stacker.yaml)" == "$(sha ./stacker.yaml)" ]

# check OCI image generation
manifest=$(cat oci/index.json | jq -r .manifests[0].digest | cut -f2 -d:)
layer=$(cat oci/blobs/sha256/$manifest | jq -r .layers[0].digest)
config=$(cat oci/blobs/sha256/$manifest | jq -r .config.digest | cut -f2 -d:)
[ "$(cat oci/blobs/sha256/$config | jq -r '.config.Entrypoint | join(" ")')" = "echo hello world" ]

publishedGitVersion=$(cat oci/blobs/sha256/$manifest | jq -r '.annotations."io.stackeroci.stacker.git_version"')
# ci does not clone tags. There it tests the fallback-to-commit path.
myGitVersion=$(run_git describe --tags) || myGitVersion=$(run_git rev-parse HEAD)
[ -n "$(run_git status --porcelain --untracked-files=no)" ] &&
dirty="-dirty" || dirty=""
[ "$publishedGitVersion" = "$myGitVersion$dirty" ]

# need to trim the extra newline from jq
cat oci/blobs/sha256/$manifest | jq -r '.annotations."io.stackeroci.stacker.stacker_yaml"' | sed '$ d' > 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 ]
}

0 comments on commit 69cbba4

Please sign in to comment.