add support for specifying dockerfile in build process

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher 2024-02-21 19:53:50 +02:00
parent 2cff5681b5
commit 0c31697e10
18 changed files with 130 additions and 12 deletions

View File

@ -50,6 +50,7 @@ A package source consists of a directory containing at least two files:
- `image` _(string)_: *(mandatory)* The name of the image to build
- `org` _(string)_: The hub/registry organisation to which this package belongs
- `dockerfile` _(string)_: The dockerfile to use to build this package, must be in this directory or below (default: `Dockerfile`)
- `arches` _(list of string)_: The architectures which this package should be built for (valid entries are `GOARCH` names)
- `extra-sources` _(list of strings)_: Additional sources for the package outside the package directory. The format is `src:dst`, where `src` can be relative to the package directory and `dst` is the destination in the build context. This is useful for sharing files, such as vendored go code, between packages.
- `gitrepo` _(string)_: The git repository where the package source is kept.

View File

@ -45,6 +45,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
manifest bool
cacheDir = flagOverEnvVarOverDefaultString{def: defaultLinuxkitCache(), envVar: envVarCacheDir}
sbomScanner string
dockerfile string
)
cmd.RunE = func(cmd *cobra.Command, args []string) error {
@ -92,6 +93,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
if sbomScanner != "false" {
opts = append(opts, pkglib.WithBuildSbomScanner(sbomScanner))
}
opts = append(opts, pkglib.WithDockerfile(dockerfile))
// skipPlatformsMap contains platforms that should be skipped
skipPlatformsMap := make(map[string]bool)
@ -128,12 +130,12 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
// look for builders env var
buildersMap, err = buildPlatformBuildersMap(os.Getenv(buildersEnvVar), buildersMap)
if err != nil {
return fmt.Errorf("error in environment variable %s: %w\n", buildersEnvVar, err)
return fmt.Errorf("error in environment variable %s: %w", buildersEnvVar, err)
}
// any CLI options override env var
buildersMap, err = buildPlatformBuildersMap(builders, buildersMap)
if err != nil {
return fmt.Errorf("error in --builders flag: %w\n", err)
return fmt.Errorf("error in --builders flag: %w", err)
}
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
@ -202,6 +204,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
cmd.Flags().BoolVar(&nobuild, "nobuild", false, "Skip building the image before pushing, conflicts with -force")
cmd.Flags().BoolVar(&manifest, "manifest", true, "Create and push multi-arch manifest")
cmd.Flags().StringVar(&sbomScanner, "sbom-scanner", "", "SBOM scanner to use, must match the buildkit spec; set to blank to use the buildkit default; set to 'false' for no scanning")
cmd.Flags().StringVar(&dockerfile, "dockerfile", "", "Dockerfile to use for building the image, must be in this directory or below, overrides what is in build.yml")
return cmd
}

View File

@ -43,6 +43,7 @@ type buildOpts struct {
builderRestart bool
sbomScan bool
sbomScannerImage string
dockerfile string
}
// BuildOpt allows callers to specify options to Build
@ -186,6 +187,14 @@ func WithBuildSbomScanner(scanner string) BuildOpt {
}
}
// WithDockerfile which dockerfile to use when building the package
func WithDockerfile(dockerfile string) BuildOpt {
return func(bo *buildOpts) error {
bo.dockerfile = dockerfile
return nil
}
}
// Build builds the package
func (p Pkg) Build(bos ...BuildOpt) error {
var bo buildOpts
@ -211,6 +220,22 @@ func (p Pkg) Build(bos ...BuildOpt) error {
return err
}
// validate the Dockerfile before bothing to move ahead, because this func call is public, so someone could
// pass something to it as a library call. We also check in the build function, to avoid multiple loops each with an error.
// if the dockerfile override was not set in the build options, i.e. it is empty, use the one from the package,
// which never should be empty. We set it onto the buildOpts, because that is what we use to pass it around to lower-level
// funcs.
if bo.dockerfile == "" {
bo.dockerfile = p.dockerfile
}
if strings.Contains(bo.dockerfile, "..") {
return fmt.Errorf("cannot expand beyond root of context for dockerfile %s", bo.dockerfile)
}
if _, err := os.Stat(filepath.Join(p.path, bo.dockerfile)); err != nil {
return fmt.Errorf("dockerfile %s does not exist or cannot be read in context %s", bo.dockerfile, p.path)
}
// did we have the build cache dir provided?
if bo.cacheDir == "" {
return errors.New("must provide linuxkit build cache directory")
@ -593,7 +618,8 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvi
if bo.ignoreCache {
passCache = nil
}
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, imageBuildOpts); err != nil {
if err := d.build(ctx, tagArch, p.path, bo.dockerfile, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, imageBuildOpts); err != nil {
stdoutCloser()
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
return nil, fmt.Errorf("buildkit was unable to emulate %s. check binfmt has been set up and works for this platform: %v", platform, err)

View File

@ -58,7 +58,7 @@ func (d *dockerMocker) contextSupportCheck() error {
func (d *dockerMocker) builder(_ context.Context, _, _, _ string, _ bool) (*buildkitClient.Client, error) {
return nil, fmt.Errorf("not implemented")
}
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, c lktspec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts dockertypes.ImageBuildOptions) error {
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, builderRestart bool, c lktspec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts dockertypes.ImageBuildOptions) error {
if !d.enableBuild {
return errors.New("build disabled")
}

View File

@ -53,7 +53,7 @@ const (
type dockerRunner interface {
tag(ref, tag string) error
build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts types.ImageBuildOptions) error
build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts types.ImageBuildOptions) error
save(tgt string, refs ...string) error
load(src io.Reader) error
pull(img string) (bool, error)
@ -402,7 +402,7 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
}
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts types.ImageBuildOptions) error {
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerfile, dockerContext, builderImage, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage string, imageBuildOpts types.ImageBuildOptions) error {
// ensure we have a builder
client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart)
if err != nil {
@ -473,26 +473,26 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
solveOpts.Session = append(solveOpts.Session, up)
} else {
solveOpts.LocalDirs = map[string]string{
builder.DefaultLocalNameDockerfile: pkg,
builder.DefaultLocalNameDockerfile: path.Join(pkg, dockerfile),
builder.DefaultLocalNameContext: pkg,
}
}
// go through the dockerfile to see if we have any provided images cached
if c != nil {
dockerfile := path.Join(pkg, "Dockerfile")
f, err := os.Open(dockerfile)
dockerfileRef := path.Join(pkg, dockerfile)
f, err := os.Open(dockerfileRef)
if err != nil {
return fmt.Errorf("error opening dockerfile %s: %v", dockerfile, err)
return fmt.Errorf("error opening dockerfile %s: %v", dockerfileRef, err)
}
defer f.Close()
ast, err := parser.Parse(f)
if err != nil {
return fmt.Errorf("error parsing dockerfile from bytes into AST %s: %v", dockerfile, err)
return fmt.Errorf("error parsing dockerfile from bytes into AST %s: %v", dockerfileRef, err)
}
stages, metaArgs, err := instructions.Parse(ast.AST)
if err != nil {
return fmt.Errorf("error parsing dockerfile from AST into stages %s: %v", dockerfile, err)
return fmt.Errorf("error parsing dockerfile from AST into stages %s: %v", dockerfileRef, err)
}
// fill optMetaArgs with args found while parsing Dockerfile

View File

@ -18,6 +18,7 @@ import (
type pkgInfo struct {
Image string `yaml:"image"`
Org string `yaml:"org"`
Dockerfile string `yaml:"dockerfile"`
Arches []string `yaml:"arches"`
ExtraSources []string `yaml:"extra-sources"`
GitRepo string `yaml:"gitrepo"` // ??
@ -51,6 +52,7 @@ type PkglibConfig struct {
Dev bool
}
// NewPkInfo returns a new pkgInfo with default values
func NewPkgInfo() pkgInfo {
return pkgInfo{
Org: "linuxkit",
@ -58,6 +60,7 @@ func NewPkgInfo() pkgInfo {
GitRepo: "https://github.com/linuxkit/linuxkit",
Network: false,
DisableCache: false,
Dockerfile: "Dockerfile",
}
}
@ -84,6 +87,7 @@ type Pkg struct {
// Internal state
path string
dockerfile string
hash string
dirty bool
commitHash string
@ -265,6 +269,7 @@ func NewFromConfig(cfg PkglibConfig, args ...string) ([]Pkg, error) {
dockerDepends: dockerDepends,
dirty: dirty,
path: pkgPath,
dockerfile: pi.Dockerfile,
git: git,
})
}

View File

@ -0,0 +1 @@
FROM alpine:3.19

View File

@ -0,0 +1,3 @@
org: linuxkit
image: missing-in-yml
dockerfile: Dockerfilenotexist

View File

@ -0,0 +1,21 @@
#!/bin/sh
# SUMMARY: Check that tar output format build is reproducible
# LABELS:
set -e
# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
set +e
linuxkit pkg build --force .
command_status=$?
set -e
if [ $command_status -eq 0 ]; then
echo "Command should have failed"
exit 1
fi
exit 0

View File

@ -0,0 +1 @@
FROM alpine:3.19

View File

@ -0,0 +1,3 @@
org: linuxkit
image: missing-in-yml
dockerfile: Dockerfilenotexist

View File

@ -0,0 +1,13 @@
#!/bin/sh
# SUMMARY: Check that tar output format build is reproducible
# LABELS:
set -e
# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
linuxkit pkg build --force --dockerfile Dockerfile .
exit 0

View File

@ -0,0 +1 @@
FROM alpine:3.19

View File

@ -0,0 +1,3 @@
org: linuxkit
image: missing-in-yml
dockerfile: Dockerfile

View File

@ -0,0 +1,21 @@
#!/bin/sh
# SUMMARY: Check that tar output format build is reproducible
# LABELS:
set -e
# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
set +e
linuxkit pkg build --force --dockerfile nosuchfile .
command_status=$?
set -e
if [ $command_status -eq 0 ]; then
echo "Command should have failed"
exit 1
fi
exit 0

View File

@ -0,0 +1 @@
FROM alpine:3.19

View File

@ -0,0 +1,2 @@
org: linuxkit
image: missing-in-yml

View File

@ -0,0 +1,13 @@
#!/bin/sh
# SUMMARY: Check that tar output format build is reproducible
# LABELS:
set -e
# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
linuxkit pkg build --force .
exit 0