diff --git a/src/cmd/linuxkit/pkglib/docker.go b/src/cmd/linuxkit/pkglib/docker.go index ed6057b08..4e60653d0 100644 --- a/src/cmd/linuxkit/pkglib/docker.go +++ b/src/cmd/linuxkit/pkglib/docker.go @@ -37,6 +37,7 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/builder" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" + "github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/moby/buildkit/session/upload/uploadprovider" log "github.com/sirupsen/logrus" ) @@ -446,30 +447,57 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, if err != nil { return fmt.Errorf("error parsing dockerfile from bytes into AST %s: %v", dockerfile, err) } - stages, _, err := instructions.Parse(ast.AST) + stages, metaArgs, err := instructions.Parse(ast.AST) if err != nil { return fmt.Errorf("error parsing dockerfile from AST into stages %s: %v", dockerfile, err) } + // fill optMetaArgs with args found while parsing Dockerfile + optMetaArgs := make(map[string]string) + for _, cmd := range metaArgs { + for _, metaArg := range cmd.Args { + optMetaArgs[metaArg.Key] = metaArg.ValueString() + } + } + // replace parsed args with provided BuildArgs if keys found + for k, v := range imageBuildOpts.BuildArgs { + if _, found := optMetaArgs[k]; found { + optMetaArgs[k] = *v + } + } + + shlex := shell.NewLex(ast.EscapeToken) // go through each stage, get the basename of the image, see if we have it in the linuxkit cache imageStores := map[string]string{} for _, stage := range stages { + // check if we have args in FROM and replace them: + // ARG IMAGE=linuxkit/img + // FROM ${IMAGE} as src + // will be parsed as: + // FROM linuxkit/img as src + name, err := shlex.ProcessWordWithMap(stage.BaseName, optMetaArgs) + if err != nil { + return fmt.Errorf("could not process word for image %s: %v", stage.BaseName, err) + } + if name == "" { + return fmt.Errorf("base name (%s) should not be blank", stage.BaseName) + } // see if the provided image name is tagged (docker.io/linuxkit/foo:latest) or digested (docker.io/linuxkit/foo@sha256:abcdefg) // if neither, we have an error - ref, err := reference.Parse(util.ReferenceExpand(stage.BaseName)) + ref, err := reference.Parse(util.ReferenceExpand(name)) if err != nil { - return fmt.Errorf("could not resolve references for image %s: %v", stage.BaseName, err) + return fmt.Errorf("could not resolve references for image %s: %v", name, err) } gdesc, err := c.FindDescriptor(&ref) if err != nil { - return fmt.Errorf("invalid name %s", stage.BaseName) + return fmt.Errorf("invalid name %s", name) } // not found, so nothing to look up if gdesc == nil { continue } hash := gdesc.Digest - imageStores[stage.BaseName] = hash.String() + imageStores[name] = hash.String() } if len(imageStores) > 0 { // if we made it here, we found the reference diff --git a/test/cases/000_build/041_chained_builds_with_args/build1/Dockerfile b/test/cases/000_build/041_chained_builds_with_args/build1/Dockerfile new file mode 100644 index 000000000..cd1a46780 --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/build1/Dockerfile @@ -0,0 +1,5 @@ +ARG IMAGE=alpine +ARG TAG=3.15 +FROM ${IMAGE}:${TAG} + +COPY foo /foo \ No newline at end of file diff --git a/test/cases/000_build/041_chained_builds_with_args/build1/build.yml b/test/cases/000_build/041_chained_builds_with_args/build1/build.yml new file mode 100644 index 000000000..f195d8096 --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/build1/build.yml @@ -0,0 +1,5 @@ +image: chained-build-with-args-image1-test-does-not-exist-anywhere-else-123456789 # this must be used as FROM in the chained image +network: true +arches: + - amd64 + - arm64 diff --git a/test/cases/000_build/041_chained_builds_with_args/build1/foo b/test/cases/000_build/041_chained_builds_with_args/build1/foo new file mode 100644 index 000000000..ba0e162e1 --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/build1/foo @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/test/cases/000_build/041_chained_builds_with_args/build2/Dockerfile b/test/cases/000_build/041_chained_builds_with_args/build2/Dockerfile new file mode 100644 index 000000000..122072ae4 --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/build2/Dockerfile @@ -0,0 +1,7 @@ +# those IMAGE and TAG *must* be updated when changing ../build1; just run `lkt pkg show-tag ../build1` and place it here +ARG IMAGE=linuxkit/chained-build-with-args-image1-test-does-not-exist-anywhere-else-123456789 +ARG TAG=6a40ba0866b7ce18b3b13390bf88bfeb6ac0db4e +FROM ${IMAGE}:${TAG} as src +FROM alpine:3.15 +COPY --from=src /foo /foo +RUN cat /foo \ No newline at end of file diff --git a/test/cases/000_build/041_chained_builds_with_args/build2/build.yml b/test/cases/000_build/041_chained_builds_with_args/build2/build.yml new file mode 100644 index 000000000..9294b440a --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/build2/build.yml @@ -0,0 +1,5 @@ +image: chained-build-with-args-test-second +network: true +arches: + - amd64 + - arm64 diff --git a/test/cases/000_build/041_chained_builds_with_args/test.sh b/test/cases/000_build/041_chained_builds_with_args/test.sh new file mode 100755 index 000000000..13a836f8c --- /dev/null +++ b/test/cases/000_build/041_chained_builds_with_args/test.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# SUMMARY: Check that chained builds support args +# LABELS: +# REPEAT: + +set -ex + +# Source libraries. Uncomment if needed/defined +#. "${RT_LIB}" +. "${RT_PROJECT_ROOT}/_lib/lib.sh" + +# Test code goes here +echo linuxkit is "$(which linuxkit)" + +# build the first, use it to build the second +linuxkit pkg build --force ./build1 +linuxkit pkg build --force ./build2 + +exit 0