mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-08-23 09:08:25 +00:00
add support for custom build args (#4155)
Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
parent
3d9bb9a128
commit
1caf2feffc
@ -57,7 +57,7 @@ A package source consists of a directory containing at least two files:
|
|||||||
- `gitrepo` _(string)_: The git repository where the package source is kept.
|
- `gitrepo` _(string)_: The git repository where the package source is kept.
|
||||||
- `network` _(bool)_: Allow network access during the package build (default: no)
|
- `network` _(bool)_: Allow network access during the package build (default: no)
|
||||||
- `disable-cache` _(bool)_: Disable build cache for this package (default: no)
|
- `disable-cache` _(bool)_: Disable build cache for this package (default: no)
|
||||||
- `buildArgs` will forward a list of build arguments down to docker. As if `--build-arg` was specified during `docker build`
|
- `buildArgs` will forward a list of build arguments down to docker. As if `--build-arg` was specified during `docker build`. See [BuildArgs][BuildArgs] for more information.
|
||||||
- `config`: _(struct `github.com/moby/tool/src/moby.ImageConfig`)_: Image configuration, marshalled to JSON and added as `org.mobyproject.config` label on image (default: no label)
|
- `config`: _(struct `github.com/moby/tool/src/moby.ImageConfig`)_: Image configuration, marshalled to JSON and added as `org.mobyproject.config` label on image (default: no label)
|
||||||
- `depends`: Contains information on prerequisites which must be satisfied in order to build the package. Has subfields:
|
- `depends`: Contains information on prerequisites which must be satisfied in order to build the package. Has subfields:
|
||||||
- `docker-images`: Docker images to be made available (as `tar` files via `docker image save`) within the package build context. Contains the following nested fields:
|
- `docker-images`: Docker images to be made available (as `tar` files via `docker image save`) within the package build context. Contains the following nested fields:
|
||||||
@ -382,6 +382,37 @@ ARG all_proxy
|
|||||||
LinuxKit does not judge between lower-cased or upper-cased variants of these options, e.g. `http_proxy` vs `HTTP_PROXY`,
|
LinuxKit does not judge between lower-cased or upper-cased variants of these options, e.g. `http_proxy` vs `HTTP_PROXY`,
|
||||||
as `docker build` does not either. It just passes them through "as-is".
|
as `docker build` does not either. It just passes them through "as-is".
|
||||||
|
|
||||||
|
## Build Args
|
||||||
|
|
||||||
|
`linuxkit` does not support passing random CLI flags for build arguments when building packages.
|
||||||
|
This is inline with its philosophy, of having as reproducible builds as possible, which requires
|
||||||
|
everything to be available on disk and in the repository.
|
||||||
|
|
||||||
|
It is possible to bypass this, but this is not recommended.
|
||||||
|
|
||||||
|
As described in [Preset build arguments][Preset build arguments], linuxkit automatically sets some build arguments
|
||||||
|
when building packages. However, you can also set your own build arguments, which will be passed to the
|
||||||
|
`docker build` command.
|
||||||
|
You can include your own build args in several ways.
|
||||||
|
|
||||||
|
* `build.yml` - you can add a `buildArgs` field to the `build.yml` file, which will be passed as `--build-arg` to `docker build`.
|
||||||
|
* `linuxkit pkg build` - you can pass the `--build-arg-file <file>` flag, with one `<key>=<value>` pair per line, which will be passed as `--build-arg` to `docker build`.
|
||||||
|
|
||||||
|
When parsing for build args, whether from `build.yml`'s `buildArgs` field or from the `--build-arg-file`,
|
||||||
|
linuxkit has support for certain calculated build args for the value of the arg. You can set these using the following syntax.
|
||||||
|
|
||||||
|
All calculated build args are prefixed with `@lkt:`.
|
||||||
|
|
||||||
|
* `@lkt:pkg:<path>` - the linuxkit package hash of the path, as determined by `linuxkit pkg show-tag <path>`. The `<path>` can be absolute, or if provided as a relative path, it is relative to the working directory of the file. For example, if provided in the `buildArgs` section of `build.yml`, it is relative to the package directory; if provided in `--build-arg-file <file>`, it is relative to the directory in which <file> exists.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
buildArgs:
|
||||||
|
- DEP_HASH=@lkt:pkg:/usr/local/foo # will be replaced with the value of `linuxkit pkg show-tag /usr/local/foo`
|
||||||
|
- REL_HASH=@lkt:pkg:foo # will be replaced with the value of `linuxkit pkg show-tag foo` relative to this build.yml file
|
||||||
|
```
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Normally, whenever a package is updated, CI will build and push the package to Docker Hub by calling `linuxkit pkg push`.
|
Normally, whenever a package is updated, CI will build and push the package to Docker Hub by calling `linuxkit pkg push`.
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
const (
|
import (
|
||||||
defaultPkgBuildYML = "build.yml"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib"
|
||||||
defaultPkgCommit = "HEAD"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultPkgBuildYML = pkglib.DefaultPkgBuildYML
|
||||||
|
defaultPkgCommit = pkglib.DefaultPkgCommit
|
||||||
)
|
)
|
||||||
|
@ -122,7 +122,14 @@ func pkgBuildCmd() *cobra.Command {
|
|||||||
defer func() { _ = f.Close() }()
|
defer func() { _ = f.Close() }()
|
||||||
scanner := bufio.NewScanner(f)
|
scanner := bufio.NewScanner(f)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
buildArgs = append(buildArgs, scanner.Text())
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
// check if the value is a special linuxkit value
|
||||||
|
buildArg, err := pkglib.TransformBuildArgValue(line, filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error transforming build arg %s: %v", line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildArgs = append(buildArgs, buildArg)
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
return fmt.Errorf("error reading build args file %s: %w", filename, err)
|
return fmt.Errorf("error reading build args file %s: %w", filename, err)
|
||||||
@ -130,6 +137,13 @@ func pkgBuildCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
opts = append(opts, pkglib.WithBuildArgs(buildArgs))
|
opts = append(opts, pkglib.WithBuildArgs(buildArgs))
|
||||||
|
|
||||||
|
// also need to parse the build args from the build.yml file for any special linuxkit values
|
||||||
|
for _, p := range pkgs {
|
||||||
|
if err := p.ProcessBuildArgs(); err != nil {
|
||||||
|
return fmt.Errorf("error processing build args for package %q: %w", p.Tag(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// skipPlatformsMap contains platforms that should be skipped
|
// skipPlatformsMap contains platforms that should be skipped
|
||||||
skipPlatformsMap := make(map[string]bool)
|
skipPlatformsMap := make(map[string]bool)
|
||||||
if skipPlatforms != "" {
|
if skipPlatforms != "" {
|
||||||
|
55
src/cmd/linuxkit/pkglib/buildarg.go
Normal file
55
src/cmd/linuxkit/pkglib/buildarg.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package pkglib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
buildArgSpecialPrefix = "@lkt:"
|
||||||
|
buildArgPkgPrefix = "pkg:"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransformBuildArgValue transforms a build arg pair whose value starts with the special linuxkit prefix.
|
||||||
|
func TransformBuildArgValue(line, anchorFile string) (string, error) {
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", fmt.Errorf("invalid build-arg, must be in format 'arg=value': %s", line)
|
||||||
|
}
|
||||||
|
key := parts[0]
|
||||||
|
val := parts[1]
|
||||||
|
// check if the value is a special linuxkit value
|
||||||
|
if !strings.HasPrefix(val, buildArgSpecialPrefix) {
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
stripped := strings.TrimPrefix(val, buildArgSpecialPrefix)
|
||||||
|
var final string
|
||||||
|
// see if we know what kind of value it is
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(stripped, buildArgPkgPrefix):
|
||||||
|
pkgPath := strings.TrimPrefix(stripped, buildArgPkgPrefix)
|
||||||
|
// see if it is an absolute or relative path
|
||||||
|
if !strings.HasPrefix(pkgPath, "/") {
|
||||||
|
anchorDir, err := filepath.Abs(filepath.Dir(anchorFile))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error getting absolute path for anchor file %q: %v", anchorFile, err)
|
||||||
|
}
|
||||||
|
pkgPath = filepath.Clean(filepath.Join(anchorDir, pkgPath))
|
||||||
|
}
|
||||||
|
pkgs, err := NewFromConfig(PkglibConfig{BuildYML: DefaultPkgBuildYML, HashCommit: DefaultPkgCommit}, pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
return "", fmt.Errorf("no package found at path %q", pkgPath)
|
||||||
|
}
|
||||||
|
p := pkgs[0]
|
||||||
|
tag := p.Tag()
|
||||||
|
final = tag
|
||||||
|
default:
|
||||||
|
// something unknown
|
||||||
|
return "", fmt.Errorf("unknown linuxkit build arg value %q", val)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s=%s", key, final), nil
|
||||||
|
}
|
8
src/cmd/linuxkit/pkglib/const.go
Normal file
8
src/cmd/linuxkit/pkglib/const.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package pkglib
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultPkgBuildYML is the default name of the package build file
|
||||||
|
DefaultPkgBuildYML = "build.yml"
|
||||||
|
// DefaultPkgCommit is the default commit to use for packages
|
||||||
|
DefaultPkgCommit = "HEAD"
|
||||||
|
)
|
@ -92,6 +92,7 @@ type Pkg struct {
|
|||||||
|
|
||||||
// Internal state
|
// Internal state
|
||||||
path string
|
path string
|
||||||
|
buildYML string // full path to the build.yml file, not just relative to path
|
||||||
dockerfile string
|
dockerfile string
|
||||||
hash string
|
hash string
|
||||||
tag string
|
tag string
|
||||||
@ -150,7 +151,8 @@ func NewFromConfig(cfg PkglibConfig, args ...string) ([]Pkg, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := os.ReadFile(filepath.Join(pkgPath, cfg.BuildYML))
|
buildYmlFile := filepath.Join(pkgPath, cfg.BuildYML)
|
||||||
|
b, err := os.ReadFile(buildYmlFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -292,6 +294,7 @@ func NewFromConfig(cfg PkglibConfig, args ...string) ([]Pkg, error) {
|
|||||||
dockerDepends: dockerDepends,
|
dockerDepends: dockerDepends,
|
||||||
dirty: dirty,
|
dirty: dirty,
|
||||||
path: pkgPath,
|
path: pkgPath,
|
||||||
|
buildYML: buildYmlFile,
|
||||||
dockerfile: pi.Dockerfile,
|
dockerfile: pi.Dockerfile,
|
||||||
git: git,
|
git: git,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
@ -363,6 +366,20 @@ func (p Pkg) cleanForBuild() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Pkg) ProcessBuildArgs() error {
|
||||||
|
if p.buildArgs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for i, arg := range *p.buildArgs {
|
||||||
|
(*p.buildArgs)[i], err = TransformBuildArgValue(arg, p.buildYML)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error processing build arg %q: %v", arg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Expands path from relative to abs against base, ensuring the result is within base, but is not base itself. Field is the fieldname, to be used for constructing the error.
|
// Expands path from relative to abs against base, ensuring the result is within base, but is not base itself. Field is the fieldname, to be used for constructing the error.
|
||||||
func makeAbsSubpath(field, base, path string) (string, error) {
|
func makeAbsSubpath(field, base, path string) (string, error) {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
1
test/cases/000_build/056_build_args/005_build_arg_special_yaml/.gitignore
vendored
Normal file
1
test/cases/000_build/056_build_args/005_build_arg_special_yaml/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo/
|
@ -0,0 +1,5 @@
|
|||||||
|
FROM alpine:3.21
|
||||||
|
ARG HASH1
|
||||||
|
ARG HASH2
|
||||||
|
RUN printf '%s\n' "${HASH1}" > /var/hash1
|
||||||
|
RUN printf '%s\n' "${HASH2}" > /var/hash2
|
@ -0,0 +1,5 @@
|
|||||||
|
org: linuxkit
|
||||||
|
image: hashes-in-build-args
|
||||||
|
buildArgs:
|
||||||
|
- HASH1=@lkt:pkg:foo/
|
||||||
|
- HASH2=@lkt:pkg:/tmp/bar12345/
|
@ -0,0 +1,77 @@
|
|||||||
|
#!/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"
|
||||||
|
|
||||||
|
# need to build the special dir /tmp/bar12345 first
|
||||||
|
TMPDIR1=/tmp/bar12345
|
||||||
|
TMPDIR2=./foo/
|
||||||
|
TMPEXPORT=$(mktemp -d)
|
||||||
|
CACHE_DIR=$(mktemp -d)
|
||||||
|
|
||||||
|
clean_up() {
|
||||||
|
rm -rf ${TMPDIR1} ${TMPDIR2} ${CACHE_DIR} ${TMPEXPORT}
|
||||||
|
}
|
||||||
|
trap clean_up EXIT
|
||||||
|
|
||||||
|
# to be clear
|
||||||
|
pwd
|
||||||
|
ls -la .
|
||||||
|
|
||||||
|
for i in "${TMPDIR1}" "${TMPDIR2}"; do
|
||||||
|
rm -rf "${i}"
|
||||||
|
mkdir -p "${i}"
|
||||||
|
echo "This is a test file for the special build arg" > "${i}/test"
|
||||||
|
cat > "${i}/build.yml" <<EOF
|
||||||
|
org: linuxkit
|
||||||
|
image: hashes-in-build-args
|
||||||
|
EOF
|
||||||
|
git -C "${i}" init
|
||||||
|
git -C "${i}" config user.email "you@example.com"
|
||||||
|
git -C "${i}" config user.name "Your Name"
|
||||||
|
git -C "${i}" add .
|
||||||
|
git -C "${i}" commit -m "Initial commit for special build arg test"
|
||||||
|
done
|
||||||
|
|
||||||
|
# print it out for the logs
|
||||||
|
echo "Building packages with special build args from ${TMPDIR1} and ${TMPDIR2}"
|
||||||
|
linuxkit --cache ${CACHE_DIR} pkg show-tag "${TMPDIR1}"
|
||||||
|
linuxkit --cache ${CACHE_DIR} pkg show-tag "${TMPDIR2}"
|
||||||
|
|
||||||
|
logs=$(linuxkit --cache ${CACHE_DIR} pkg build --force . 2>&1)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Build failed with logs:"
|
||||||
|
echo "${logs}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected1=$(linuxkit pkg show-tag "${TMPDIR1}")
|
||||||
|
expected2=$(linuxkit pkg show-tag "${TMPDIR2}")
|
||||||
|
current=$(linuxkit pkg show-tag .)
|
||||||
|
|
||||||
|
# dump it to a filesystem
|
||||||
|
linuxkit --cache ${CACHE_DIR} cache export --format filesystem --outfile - "${current}" | tar -C "${TMPEXPORT}" -xvf -
|
||||||
|
# for extra debugging
|
||||||
|
find "${TMPEXPORT}" -type f -exec ls -la {} \;
|
||||||
|
actual1=$(cat ${TMPEXPORT}/var/hash1)
|
||||||
|
actual2=$(cat ${TMPEXPORT}/var/hash2)
|
||||||
|
|
||||||
|
|
||||||
|
if [ "${expected1}" != "${actual1}" ]; then
|
||||||
|
echo "Expected HASH1: ${expected1}, but got: ${actual1}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${expected2}" != "${actual2}" ]; then
|
||||||
|
echo "Expected HASH2: ${expected2}, but got: ${actual2}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check that the build args were correctly transformed
|
||||||
|
|
||||||
|
exit 0
|
@ -0,0 +1,5 @@
|
|||||||
|
FROM alpine:3.21
|
||||||
|
ARG HASH1
|
||||||
|
ARG HASH2
|
||||||
|
RUN printf '%s\n' "${HASH1}" > /var/hash1
|
||||||
|
RUN printf '%s\n' "${HASH2}" > /var/hash2
|
@ -0,0 +1,2 @@
|
|||||||
|
HASH1=@lkt:pkg:foo/
|
||||||
|
HASH2=@lkt:pkg:/tmp/bar67890/
|
@ -0,0 +1,2 @@
|
|||||||
|
org: linuxkit
|
||||||
|
image: test-image-in-yaml-build-args
|
@ -0,0 +1,76 @@
|
|||||||
|
#!/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"
|
||||||
|
|
||||||
|
# need to build the special dir /tmp/bar12345 first
|
||||||
|
TMPDIR1=/tmp/bar67890
|
||||||
|
TMPDIR2=./foo/
|
||||||
|
TMPEXPORT=$(mktemp -d)
|
||||||
|
CACHE_DIR=$(mktemp -d)
|
||||||
|
|
||||||
|
clean_up() {
|
||||||
|
rm -rf ${TMPDIR1} ${TMPDIR2} ${CACHE_DIR} ${TMPEXPORT}
|
||||||
|
}
|
||||||
|
trap clean_up EXIT
|
||||||
|
|
||||||
|
# to be clear
|
||||||
|
pwd
|
||||||
|
ls -la .
|
||||||
|
|
||||||
|
for i in "${TMPDIR1}" "${TMPDIR2}"; do
|
||||||
|
rm -rf "${i}"
|
||||||
|
mkdir -p "${i}"
|
||||||
|
echo "This is a test file for the special build arg" > "${i}/test"
|
||||||
|
cat > "${i}/build.yml" <<EOF
|
||||||
|
org: linuxkit
|
||||||
|
image: hashes-in-build-args
|
||||||
|
EOF
|
||||||
|
git -C "${i}" init
|
||||||
|
git -C "${i}" config user.email "you@example.com"
|
||||||
|
git -C "${i}" config user.name "Your Name"
|
||||||
|
git -C "${i}" add .
|
||||||
|
git -C "${i}" commit -m "Initial commit for special build arg test"
|
||||||
|
done
|
||||||
|
|
||||||
|
# print it out for the logs
|
||||||
|
echo "Building packages with special build args from ${TMPDIR1} and ${TMPDIR2}"
|
||||||
|
linuxkit --cache ${CACHE_DIR} pkg show-tag "${TMPDIR1}"
|
||||||
|
linuxkit --cache ${CACHE_DIR} pkg show-tag "${TMPDIR2}"
|
||||||
|
|
||||||
|
logs=$(linuxkit --cache ${CACHE_DIR} pkg build --force --build-arg-file build-args . 2>&1)
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Build failed with logs:"
|
||||||
|
echo "${logs}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
expected1=$(linuxkit pkg show-tag "${TMPDIR1}")
|
||||||
|
expected2=$(linuxkit pkg show-tag "${TMPDIR2}")
|
||||||
|
current=$(linuxkit pkg show-tag .)
|
||||||
|
|
||||||
|
# dump it to a filesystem
|
||||||
|
linuxkit --cache ${CACHE_DIR} cache export --format filesystem --outfile - "${current}" | tar -C "${TMPEXPORT}" -xvf -
|
||||||
|
# for extra debugging
|
||||||
|
find "${TMPEXPORT}" -type f -exec ls -la {} \;
|
||||||
|
actual1=$(cat ${TMPEXPORT}/var/hash1)
|
||||||
|
actual2=$(cat ${TMPEXPORT}/var/hash2)
|
||||||
|
|
||||||
|
if [ "${expected1}" != "${actual1}" ]; then
|
||||||
|
echo "Expected HASH1: ${expected1}, but got: ${actual1}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${expected2}" != "${actual2}" ]; then
|
||||||
|
echo "Expected HASH2: ${expected2}, but got: ${actual2}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check that the build args were correctly transformed
|
||||||
|
|
||||||
|
exit 0
|
Loading…
Reference in New Issue
Block a user