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:
parent
a25837194a
commit
d64f253500
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
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 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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" {
|
||||
|
|
244
tests/bud.bats
244
tests/bud.bats
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue