Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(build): add a substitute-file option #355

Merged
merged 1 commit into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ]
}