buildkit: supports additionalBuildContext in builds via --build-context

As builds got more complicated, the ability to only access files from one location became quite limiting. With `multi-stage` builds where you can `copy` files from other parts of the Containerfile by adding the `--from` flag and pointing it to the name of another Containerfile stage or a remote image.

The new named build context feature is an extension of this pattern. You can now define additional build contexts when running the build command, give them a name, and then access them inside a Dockerfile the same way you previously did with build stages.

Additional build contexts can be defined with a new `--build-context [name]=[value]` flag. The key component defines the name for your build context and the value can be:

```console

    Local directory – e.g. --build-context project2=../path/to/project2/src
    HTTP URL to a tarball – e.g. --build-context src=https://example.org/releases/src.tar
    Container image – Define with a docker-image:// prefix, e.g. --build-context alpine=docker-image://alpine:3.15, ( also supports docker://, container-image:// )
```

On the Containerfile side, you can reference the build context on all commands that accept the “from” parameter. Here’s how that might look:
```Dockerfile
FROM [name]
COPY --from=[name] ...
RUN --mount=from=[name] …
```

The value of [name] is matched with the following priority order:

* Named build context defined with `--build-context [name]=..`
* Stage defined with `AS [name]` inside Dockerfile
* Remote image `[name]` in a container registry

Added Features

* Pinning images for `FROM` and `COPY`
* Specifying multiple buildcontexts from different projects
  and using them with `--from` in `ADD` and `COPY` directive
* Override a Remote Dependency with a Local One.
* Using additional context from external `Tar`

Signed-off-by: Aditya R <arajan@redhat.com>
This commit is contained in:
Aditya R 2022-05-10 15:41:37 +05:30 committed by Nalin Dahyabhai
parent a25837194a
commit d64f253500
12 changed files with 503 additions and 24 deletions

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
@ -157,6 +158,22 @@ func buildCmd(c *cobra.Command, inputArgs []string, iopts buildOptions) error {
}
}
additionalBuildContext := make(map[string]*define.AdditionalBuildContext)
if c.Flag("build-context").Changed {
for _, contextString := range iopts.BuildContext {
av := strings.SplitN(contextString, "=", 2)
if len(av) > 1 {
parseAdditionalBuildContext, err := parse.GetAdditionalBuildContext(av[1])
if err != nil {
return errors.Wrapf(err, "while parsing additional build context")
}
additionalBuildContext[av[0]] = &parseAdditionalBuildContext
} else {
return fmt.Errorf("while parsing additional build context: %q, accepts value in the form of key=value", av)
}
}
}
containerfiles := getContainerfiles(iopts.File)
format, err := getFormat(iopts.Format)
if err != nil {
@ -344,6 +361,7 @@ func buildCmd(c *cobra.Command, inputArgs []string, iopts buildOptions) error {
Annotations: iopts.Annotation,
Architecture: systemContext.ArchitectureChoice,
Args: args,
AdditionalBuildContexts: additionalBuildContext,
BlobDirectory: iopts.BlobCache,
CNIConfigDir: iopts.CNIConfigDir,
CNIPluginPath: iopts.CNIPlugInPath,

View File

@ -11,6 +11,21 @@ import (
"golang.org/x/sync/semaphore"
)
// AdditionalBuildContext contains verbose details about a parsed build context from --build-context
type AdditionalBuildContext struct {
// Value is the URL of an external tar archive.
IsURL bool
// Value is the name of an image which may or may not have already been pulled.
IsImage bool
// Value holds a URL, an image name, or an absolute filesystem path.
Value string
// Absolute filesystem path to downloaded and exported build context
// from external tar archive. This will be populated only if following
// buildcontext is created from IsURL and was downloaded before in any
// of the RUN step.
DownloadedCache string
}
// CommonBuildOptions are resources that can be defined by flags for both buildah from and build
type CommonBuildOptions struct {
// AddHost is the list of hostnames to add to the build container's /etc/hosts.
@ -121,6 +136,8 @@ type BuildOptions struct {
Compression archive.Compression
// Arguments which can be interpolated into Dockerfiles
Args map[string]string
// Map of external additional build contexts
AdditionalBuildContexts map[string]*AdditionalBuildContext
// Name of the image to write to.
Output string
// BuildOutput specifies if any custom build output is selected for following build.

View File

@ -68,6 +68,32 @@ resulting image's configuration.
Please refer to the [BUILD TIME VARIABLES](#build-time-variables) section for the
list of variables that can be overridden within the Containerfile at run time.
**--build-context** *name=value*
Specify an additional build context using its short name and its location. Additional
build contexts can be referenced in the same manner as we access different stages in `COPY`
instruction.
Valid values could be:
* Local directory e.g. --build-context project2=../path/to/project2/src
* HTTP URL to a tarball e.g. --build-context src=https://example.org/releases/src.tar
* Container image specified with a container-image:// prefix, e.g. --build-context alpine=container-image://alpine:3.15, (also accepts docker://, docker-image://)
On the Containerfile side, you can reference the build context on all commands that accept the “from” parameter.
Heres how that might look:
```Dockerfile
FROM [name]
COPY --from=[name] ...
RUN --mount=from=[name] …
```
The value of `[name]` is matched with the following priority order:
* Named build context defined with --build-context [name]=..
* Stage defined with AS [name] inside Containerfile
* Image [name], either local or in a remote registry
**--cache-from**
Images to utilise as potential cache sources. Buildah does not currently support --cache-from so this is a NOOP.

View File

@ -212,7 +212,10 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
}
if options.AllPlatforms {
options.Platforms, err = platformsForBaseImages(ctx, logger, paths, files, options.From, options.Args, options.SystemContext)
if options.AdditionalBuildContexts == nil {
options.AdditionalBuildContexts = make(map[string]*define.AdditionalBuildContext)
}
options.Platforms, err = platformsForBaseImages(ctx, logger, paths, files, options.From, options.Args, options.AdditionalBuildContexts, options.SystemContext)
if err != nil {
return "", nil, err
}
@ -512,8 +515,8 @@ func preprocessContainerfileContents(logger *logrus.Logger, containerfile string
// platformsForBaseImages resolves the names of base images from the
// dockerfiles, and if they are all valid references to manifest lists, returns
// the list of platforms that are supported by all of the base images.
func platformsForBaseImages(ctx context.Context, logger *logrus.Logger, dockerfilepaths []string, dockerfiles [][]byte, from string, args map[string]string, systemContext *types.SystemContext) ([]struct{ OS, Arch, Variant string }, error) {
baseImages, err := baseImages(dockerfilepaths, dockerfiles, from, args)
func platformsForBaseImages(ctx context.Context, logger *logrus.Logger, dockerfilepaths []string, dockerfiles [][]byte, from string, args map[string]string, additionalBuildContext map[string]*define.AdditionalBuildContext, systemContext *types.SystemContext) ([]struct{ OS, Arch, Variant string }, error) {
baseImages, err := baseImages(dockerfilepaths, dockerfiles, from, args, additionalBuildContext)
if err != nil {
return nil, errors.Wrapf(err, "determining list of base images")
}
@ -641,7 +644,7 @@ func platformsForBaseImages(ctx context.Context, logger *logrus.Logger, dockerfi
// stage's base image with FROM, and returns the list of base images as
// provided. Each entry in the dockerfilenames slice corresponds to a slice in
// dockerfilecontents.
func baseImages(dockerfilenames []string, dockerfilecontents [][]byte, from string, args map[string]string) ([]string, error) {
func baseImages(dockerfilenames []string, dockerfilecontents [][]byte, from string, args map[string]string, additionalBuildContext map[string]*define.AdditionalBuildContext) ([]string, error) {
mainNode, err := imagebuilder.ParseDockerfile(bytes.NewReader(dockerfilecontents[0]))
if err != nil {
return nil, errors.Wrapf(err, "error parsing main Dockerfile: %s", dockerfilenames[0])
@ -680,6 +683,13 @@ func baseImages(dockerfilenames []string, dockerfilecontents [][]byte, from stri
child.Next.Value = from
from = ""
}
if replaceBuildContext, ok := additionalBuildContext[child.Next.Value]; ok {
if replaceBuildContext.IsImage {
child.Next.Value = replaceBuildContext.Value
} else {
return nil, fmt.Errorf("build context %q is not an image, can not be used for FROM %q", child.Next.Value, child.Next.Value)
}
}
base := child.Next.Value
if base != "scratch" && !nicknames[base] {
// TODO: this didn't undergo variable and arg

View File

@ -126,6 +126,7 @@ type Executor struct {
imageInfoLock sync.Mutex
imageInfoCache map[string]imageTypeAndHistoryAndDiffIDs
fromOverride string
additionalBuildContexts map[string]*define.AdditionalBuildContext
manifest string
secrets map[string]define.Secret
sshsources map[string]*sshagent.Source
@ -275,6 +276,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
rusageLogFile: rusageLogFile,
imageInfoCache: make(map[string]imageTypeAndHistoryAndDiffIDs),
fromOverride: options.From,
additionalBuildContexts: options.AdditionalBuildContexts,
manifest: options.Manifest,
secrets: secrets,
sshsources: sshsources,
@ -609,6 +611,12 @@ func (b *Executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
}
base := child.Next.Value
if base != "scratch" {
if replaceBuildContext, ok := b.additionalBuildContexts[child.Next.Value]; ok {
if replaceBuildContext.IsImage {
child.Next.Value = replaceBuildContext.Value
base = child.Next.Value
}
}
userArgs := argsMapToSlice(stage.Builder.Args)
baseWithArg, err := imagebuilder.ProcessWord(base, userArgs)
if err != nil {

View File

@ -369,18 +369,73 @@ func (s *StageExecutor) Copy(excludes []string, copies ...imagebuilder.Copy) err
if fromErr != nil {
return errors.Wrapf(fromErr, "unable to resolve argument %q", copy.From)
}
if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
return err
}
if other, ok := s.executor.stages[from]; ok && other.index < s.index {
contextDir = other.mountPoint
idMappingOptions = &other.builder.IDMappingOptions
} else if builder, ok := s.executor.containerMap[copy.From]; ok {
contextDir = builder.MountPoint
idMappingOptions = &builder.IDMappingOptions
var additionalBuildContext *define.AdditionalBuildContext
if foundContext, ok := s.executor.additionalBuildContexts[from]; ok {
additionalBuildContext = foundContext
} else {
return errors.Errorf("the stage %q has not been built", copy.From)
// Maybe index is given in COPY --from=index
// if that's the case check if provided index
// exists and if stage short_name matches any
// additionalContext replace stage with addtional
// build context.
if _, err := strconv.Atoi(from); err == nil {
if stage, ok := s.executor.stages[from]; ok {
if foundContext, ok := s.executor.additionalBuildContexts[stage.name]; ok {
additionalBuildContext = foundContext
}
}
}
}
if additionalBuildContext != nil {
if !additionalBuildContext.IsImage {
contextDir = additionalBuildContext.Value
if additionalBuildContext.IsURL {
// Check if following buildContext was already
// downloaded before in any other RUN step. If not
// download it and populate DownloadCache field for
// future RUN steps.
if additionalBuildContext.DownloadedCache == "" {
// additional context contains a tar file
// so download and explode tar to buildah
// temp and point context to that.
path, subdir, err := define.TempDirForURL(internalUtil.GetTempDir(), internal.BuildahExternalArtifactsDir, additionalBuildContext.Value)
if err != nil {
return errors.Wrapf(err, "unable to download context from external source %q", additionalBuildContext.Value)
}
// point context dir to the extracted path
contextDir = filepath.Join(path, subdir)
// populate cache for next RUN step
additionalBuildContext.DownloadedCache = contextDir
} else {
contextDir = additionalBuildContext.DownloadedCache
}
}
} else {
copy.From = additionalBuildContext.Value
}
}
if additionalBuildContext == nil {
if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
return err
}
if other, ok := s.executor.stages[from]; ok && other.index < s.index {
contextDir = other.mountPoint
idMappingOptions = &other.builder.IDMappingOptions
} else if builder, ok := s.executor.containerMap[copy.From]; ok {
contextDir = builder.MountPoint
idMappingOptions = &builder.IDMappingOptions
} else {
return errors.Errorf("the stage %q has not been built", copy.From)
}
} else if additionalBuildContext.IsImage {
// Image was selected as additionalContext so only process image.
mountPoint, err := s.getImageRootfs(s.ctx, copy.From)
if err != nil {
return err
}
contextDir = mountPoint
}
// Original behaviour of buildah still stays true for COPY irrespective of additional context.
preserveOwnership = true
copyExcludes = excludes
} else {
@ -446,6 +501,55 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
if fromErr != nil {
return nil, errors.Wrapf(fromErr, "unable to resolve argument %q", kv[1])
}
// If additional buildContext contains this
// give priority to that and break if additional
// is not an external image.
if additionalBuildContext, ok := s.executor.additionalBuildContexts[from]; ok {
if additionalBuildContext.IsImage {
mountPoint, err := s.getImageRootfs(s.ctx, additionalBuildContext.Value)
if err != nil {
return nil, errors.Errorf("%s from=%s: image found with that name", flag, from)
}
// The `from` in stageMountPoints should point
// to `mountPoint` replaced from additional
// build-context. Reason: Parser will use this
// `from` to refer from stageMountPoints map later.
stageMountPoints[from] = internal.StageMountDetails{IsStage: false, MountPoint: mountPoint}
break
} else {
// Most likely this points to path on filesystem
// or external tar archive, Treat it as a stage
// nothing is different for this. So process and
// point mountPoint to path on host and it will
// be automatically handled correctly by since
// GetBindMount will honor IsStage:false while
// processing stageMountPoints.
mountPoint := additionalBuildContext.Value
if additionalBuildContext.IsURL {
// Check if following buildContext was already
// downloaded before in any other RUN step. If not
// download it and populate DownloadCache field for
// future RUN steps.
if additionalBuildContext.DownloadedCache == "" {
// additional context contains a tar file
// so download and explode tar to buildah
// temp and point context to that.
path, subdir, err := define.TempDirForURL(internalUtil.GetTempDir(), internal.BuildahExternalArtifactsDir, additionalBuildContext.Value)
if err != nil {
return nil, errors.Wrapf(err, "unable to download context from external source %q", additionalBuildContext.Value)
}
// point context dir to the extracted path
mountPoint = filepath.Join(path, subdir)
// populate cache for next RUN step
additionalBuildContext.DownloadedCache = mountPoint
} else {
mountPoint = additionalBuildContext.DownloadedCache
}
}
stageMountPoints[from] = internal.StageMountDetails{IsStage: true, MountPoint: mountPoint}
break
}
}
// If the source's name corresponds to the
// result of an earlier stage, wait for that
// stage to finish being built.
@ -923,6 +1027,25 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
if fromErr != nil {
return "", nil, errors.Wrapf(fromErr, "unable to resolve argument %q", arr[1])
}
// If additional buildContext contains this
// give priority to that and break if additional
// is not an external image.
if additionalBuildContext, ok := s.executor.additionalBuildContexts[from]; ok {
if !additionalBuildContext.IsImage {
// We don't need to pull this
// since this additional context
// is not an image.
break
} else {
// replace with image set in build context
from = additionalBuildContext.Value
if _, err := s.getImageRootfs(ctx, from); err != nil {
return "", nil, errors.Errorf("%s --from=%s: no stage or image found with that name", command, from)
}
break
}
}
// If the source's name corresponds to the
// result of an earlier stage, wait for that
// stage to finish being built.

View File

@ -309,7 +309,7 @@ func GetCacheMount(args []string, store storage.Store, imageMountLabel string, a
// add subdirectory if specified
// cache parent directory
cacheParent := filepath.Join(getTempDir(), BuildahCacheDir)
cacheParent := filepath.Join(internalUtil.GetTempDir(), BuildahCacheDir)
// create cache on host if not present
err = os.MkdirAll(cacheParent, os.FileMode(0755))
if err != nil {
@ -597,12 +597,3 @@ func GetTmpfsMount(args []string) (specs.Mount, error) {
return newMount, nil
}
/* This is internal function and could be changed at any time */
/* for external usage please refer to buildah/pkg/parse.GetTempDir() */
func getTempDir() string {
if tmpdir, ok := os.LookupEnv("TMPDIR"); ok {
return tmpdir
}
return "/var/tmp"
}

View File

@ -1,5 +1,11 @@
package internal
const (
// Temp directory which stores external artifacts which are download for a build.
// Example: tar files from external sources.
BuildahExternalArtifactsDir = "buildah-external-artifacts"
)
// Types is internal packages are suspected to change with releases avoid using these outside of buildah
// StageMountDetails holds the Stage/Image mountpoint returned by StageExecutor

View File

@ -32,6 +32,14 @@ func LookupImage(ctx *types.SystemContext, store storage.Store, image string) (*
return localImage, nil
}
// GetTempDir returns base for a temporary directory on host.
func GetTempDir() string {
if tmpdir, ok := os.LookupEnv("TMPDIR"); ok {
return tmpdir
}
return "/var/tmp"
}
// ExportFromReader reads bytes from given reader and exports to external tar, directory or stdout.
func ExportFromReader(input io.Reader, opts define.BuildOutputOption) error {
var err error

View File

@ -53,6 +53,7 @@ type BudResults struct {
Annotation []string
Authfile string
BuildArg []string
BuildContext []string
CacheFrom string
CertDir string
Compress bool
@ -192,6 +193,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet {
fs.StringArrayVar(&flags.Annotation, "annotation", []string{}, "set metadata for an image (default [])")
fs.StringVar(&flags.Authfile, "authfile", "", "path of the authentication file.")
fs.StringArrayVar(&flags.BuildArg, "build-arg", []string{}, "`argument=value` to supply to the builder")
fs.StringArrayVar(&flags.BuildContext, "build-context", []string{}, "`argument=value` to supply additional build context to the builder")
fs.StringVar(&flags.CacheFrom, "cache-from", "", "images to utilise as potential cache sources. The build process does not currently support caching so this is a NOOP.")
fs.StringVar(&flags.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry")
fs.BoolVar(&flags.Compress, "compress", false, "this is a legacy option, which has no effect on the image")
@ -267,6 +269,7 @@ func GetBudFlagsCompletions() commonComp.FlagCompletions {
flagCompletion["arch"] = commonComp.AutocompleteNone
flagCompletion["authfile"] = commonComp.AutocompleteDefault
flagCompletion["build-arg"] = commonComp.AutocompleteNone
flagCompletion["build-context"] = commonComp.AutocompleteNone
flagCompletion["cache-from"] = commonComp.AutocompleteNone
flagCompletion["cert-dir"] = commonComp.AutocompleteDefault
flagCompletion["cpp-flag"] = commonComp.AutocompleteNone

View File

@ -175,6 +175,31 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name
return commonOpts, nil
}
// GetAdditionalBuildContext consumes raw string and returns parsed AdditionalBuildContext
func GetAdditionalBuildContext(value string) (define.AdditionalBuildContext, error) {
ret := define.AdditionalBuildContext{IsURL: false, IsImage: false, Value: value}
if strings.HasPrefix(value, "docker-image://") {
ret.IsImage = true
ret.Value = strings.TrimPrefix(value, "docker-image://")
} else if strings.HasPrefix(value, "container-image://") {
ret.IsImage = true
ret.Value = strings.TrimPrefix(value, "container-image://")
} else if strings.HasPrefix(value, "docker://") {
ret.IsImage = true
ret.Value = strings.TrimPrefix(value, "docker://")
} else if strings.HasPrefix(value, "http://") || strings.HasPrefix(value, "https://") {
ret.IsImage = false
ret.IsURL = true
} else {
path, err := filepath.Abs(value)
if err != nil {
return define.AdditionalBuildContext{}, errors.Wrapf(err, "unable to convert additional build-context %q path to absolute", value)
}
ret.Value = path
}
return ret, nil
}
func parseSecurityOpts(securityOpts []string, commonOpts *define.CommonBuildOptions) error {
for _, opt := range securityOpts {
if opt == "no-new-privileges" {

View File

@ -202,6 +202,135 @@ _EOF
expect_output --substring $targetarch
}
# Test pinning image using addtional build context
@test "build-with-additional-build-context and COPY, test pinning image" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM alpine
RUN touch hello
RUN echo world > hello
_EOF
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine
COPY --from=busybox hello .
RUN cat hello
_EOF
# Build a first image which we can use as source
run_buildah build $WITH_POLICY_JSON -t source -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
# Pin upstream busybox to local image source
run_buildah build $WITH_POLICY_JSON --build-context busybox=docker://source -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "world"
}
# Test conflict between stage short name and additional-context conflict
# Buildkit parity give priority to additional-context over stage names.
@test "build-with-additional-build-context and COPY, stagename and additional-context conflict" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM alpine
RUN touch hello
RUN echo world > hello
_EOF
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo world
# hello should get copied since we are giving priority to additional context
COPY --from=some-stage hello .
RUN cat hello
_EOF
# Build a first image which we can use as source
run_buildah build $WITH_POLICY_JSON -t source -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
# Pin upstream busybox to local image source
run_buildah build $WITH_POLICY_JSON --build-context some-stage=docker://source -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "world"
}
# When numeric index of stage is used and stage exists but additional context also exist with name
# same as stage in such situations always use additional context.
@test "build-with-additional-build-context and COPY, additionalContext and numeric value of stage" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM alpine
RUN touch hello
RUN echo override-numeric > hello
_EOF
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo world > hello
# hello should get copied since we are accessing stage from its numeric value and not
# addtional build context where some-stage is docker://alpine
FROM alpine
COPY --from=0 hello .
RUN cat hello
_EOF
# Build a first image which we can use as source
run_buildah build $WITH_POLICY_JSON -t source -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
run_buildah build $WITH_POLICY_JSON --build-context some-stage=docker://source -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "override-numeric"
}
# Test conflict between stage short name and additional-context conflict on FROM
# Buildkit parity give priority to additional-context over stage names.
@test "build-with-additional-build-context and FROM, stagename and additional-context conflict" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM alpine
RUN touch hello
RUN echo world > hello
_EOF
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo world
# hello should be there since we are giving priority to additional context
FROM some-stage
RUN cat hello
_EOF
# Build a first image which we can use as source
run_buildah build $WITH_POLICY_JSON -t source -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
# Second FROM should choose base as `source` instead of local-stage named `some-stage`.
run_buildah build $WITH_POLICY_JSON --build-context some-stage=docker://source -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "world"
}
# Test adding additional build context
@test "build-with-additional-build-context and COPY, additional context from host" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform2
# add file on original context
echo something > ${TEST_SCRATCH_DIR}/bud/platform/somefile
# add file on additional context
echo hello_world > ${TEST_SCRATCH_DIR}/bud/platform2/hello
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile << _EOF
FROM alpine
COPY somefile .
RUN cat somefile
COPY --from=context2 hello .
RUN cat hello
_EOF
# Test additional context
run_buildah build $WITH_POLICY_JSON -t source --build-context context2=${TEST_SCRATCH_DIR}/bud/platform2 ${TEST_SCRATCH_DIR}/bud/platform
expect_output --substring "something"
expect_output --substring "hello_world"
}
@test "build with add resolving to invalid HTTP status code" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
@ -214,6 +343,102 @@ _EOF
expect_output --substring "invalid response status"
}
# Test adding additional build context but download tar
@test "build-with-additional-build-context and COPY, additional context from external URL" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile << _EOF
FROM alpine
COPY --from=crun-context . .
RUN ls crun-1.4.5
_EOF
# Test additional context but download from tar
run_buildah build $WITH_POLICY_JSON -t source --build-context crun-context=https://github.com/containers/crun/releases/download/1.4.5/crun-1.4.5.tar.xz ${TEST_SCRATCH_DIR}/bud/platform
# additional context from tar must show crun binary inside container
expect_output --substring "libcrun"
}
# Test pinning image
@test "build-with-additional-build-context and FROM, pin busybox to alpine" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile << _EOF
FROM busybox
RUN ls /etc/*release
_EOF
# Test additional context but download from tar
# We are pinning busybox to alpine so we must always pull alpine and use that
run_buildah build $WITH_POLICY_JSON -t source --build-context busybox=docker://alpine ${TEST_SCRATCH_DIR}/bud/platform
# We successfully pinned binary cause otherwise busybox should not contain alpine-release binary
expect_output --substring "alpine-release"
}
# Test usage of RUN --mount=from=<name> with additional context and also test conflict with stage-name
@test "build-with-additional-build-context and RUN --mount=from=, additional-context and also test conflict with stagename" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM alpine
RUN touch hello
RUN echo world > hello
_EOF
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo something_random
# hello should get copied since we are giving priority to additional context
FROM alpine
RUN --mount=type=bind,from=some-stage,target=/test cat /test/hello
_EOF
# Build a first image which we can use as source
run_buildah build $WITH_POLICY_JSON -t source -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
# Additional Context for RUN --mount is additional image and it should not conflict with stage
run_buildah build $WITH_POLICY_JSON --build-context some-stage=docker://source -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "world"
}
# Test usage of RUN --mount=from=<name> with additional context and also test conflict with stage-name, when additionalContext is on host
@test "build-with-additional-build-context and RUN --mount=from=, additional-context not image and also test conflict with stagename" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
echo world > ${TEST_SCRATCH_DIR}/bud/platform/hello
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo some_text
# hello should get copied since we are giving priority to additional context
FROM alpine
RUN --mount=type=bind,from=some-stage,target=/test,z cat /test/hello
_EOF
# Addtional context for RUN --mount is file on host
run_buildah build $WITH_POLICY_JSON --build-context some-stage=${TEST_SCRATCH_DIR}/bud/platform -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "world"
}
# Test usage of RUN --mount=from=<name> with additional context is URL and mount source is relative using src
@test "build-with-additional-build-context and RUN --mount=from=, additional-context is URL and mounted from subdir" {
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2 << _EOF
FROM alpine as some-stage
RUN echo world
# hello should get copied since we are giving priority to additional context
FROM alpine
RUN --mount=type=bind,src=crun-1.4.5/src,from=some-stage,target=/test,z ls /test
_EOF
# Addtional context for RUN --mount is file on host
run_buildah build $WITH_POLICY_JSON --build-context some-stage=https://github.com/containers/crun/releases/download/1.4.5/crun-1.4.5.tar.xz -t test -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile2
expect_output --substring "crun.c"
}
@test "bud with --layers and --no-cache flags" {
cp -a $BUDFILES/use-layers ${TEST_SCRATCH_DIR}/use-layers
@ -3876,6 +4101,25 @@ _EOF
[[ "$output" -gt 1 ]] # should at least be more than one entry in there, right?
}
@test "bud-multiple-platform for --all-platform with additional-build-context" {
outputlist=localhost/testlist
mkdir -p ${TEST_SCRATCH_DIR}/bud/platform
cat > ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1 << _EOF
FROM busybox
_EOF
# Pulled images must be ubi since we configured --build-context busybox=docker://registry.access.redhat.com/ubi8-micro
run_buildah build $WITH_POLICY_JSON --all-platforms --build-context busybox=docker://registry.access.redhat.com/ubi8-micro --manifest $outputlist -f ${TEST_SCRATCH_DIR}/bud/platform/Dockerfile1
# must contain pulling logs for ubi8 instead of busybox
expect_output --substring "ubi8"
run_buildah manifest inspect $outputlist
echo "$output"
run jq '.manifests | length' <<< "$output"
echo "$output"
[[ "$output" -eq 4 ]] # should be equal to 4 which is equivalent to images in registry.access.redhat.com/ubi8-micro
}
# * Performs multi-stage build with label1=value1 and verifies
# * Relabels build with label1=value2 and verifies
# * Rebuild with label1=value1 and makes sure everything is used from cache