add support for pkg build authentication (#4137)

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher 2025-07-02 18:52:05 +03:00 committed by GitHub
parent 940c1b7b3b
commit 2b4687338b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 341 additions and 56 deletions

View File

@ -7,7 +7,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var pkglibConfig pkglib.PkglibConfig var (
pkglibConfig pkglib.PkglibConfig
registryCreds []string
)
func pkgCmd() *cobra.Command { func pkgCmd() *cobra.Command {
var ( var (
@ -96,5 +99,6 @@ func pkgCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar(&dirty, "force-dirty", false, "Force the pkg(s) to be considered dirty") cmd.PersistentFlags().BoolVar(&dirty, "force-dirty", false, "Force the pkg(s) to be considered dirty")
cmd.PersistentFlags().BoolVar(&devMode, "dev", false, "Force org and hash to $USER and \"dev\" respectively") cmd.PersistentFlags().BoolVar(&devMode, "dev", false, "Force org and hash to $USER and \"dev\" respectively")
cmd.PersistentFlags().StringSliceVar(&registryCreds, "registry-creds", nil, "Registry auths to use for building images, format is <registry>=<username>:<password> OR <registry>=<registry-token>. If no username is provided, it is treated as a registry token. <registry> must be a URL, e.g. 'https://index.docker.io/'. May be provided as many times as desired. Will override anything in your default.")
return cmd return cmd
} }

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib" "github.com/linuxkit/linuxkit/src/cmd/linuxkit/pkglib"
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -40,6 +41,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
skipPlatforms string skipPlatforms string
builders string builders string
builderImage string builderImage string
builderConfig string
builderRestart bool builderRestart bool
release string release string
nobuild bool nobuild bool
@ -159,6 +161,13 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
if err != nil { if err != nil {
return fmt.Errorf("error in --builders flag: %w", err) return fmt.Errorf("error in --builders flag: %w", err)
} }
if builderConfig != "" {
if _, err := os.Stat(builderConfig); err != nil {
return fmt.Errorf("error reading builder config file %s: %w", builderConfig, err)
}
opts = append(opts, pkglib.WithBuildBuilderConfig(builderConfig))
}
opts = append(opts, pkglib.WithBuildBuilders(buildersMap)) opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
opts = append(opts, pkglib.WithBuildBuilderImage(builderImage)) opts = append(opts, pkglib.WithBuildBuilderImage(builderImage))
opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart)) opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart))
@ -166,6 +175,46 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
if len(ssh) > 0 { if len(ssh) > 0 {
opts = append(opts, pkglib.WithSSH(ssh)) opts = append(opts, pkglib.WithSSH(ssh))
} }
if len(registryCreds) > 0 {
registryCredMap := make(map[string]spec.RegistryAuth)
for _, cred := range registryCreds {
parts := strings.SplitN(cred, "=", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return fmt.Errorf("invalid registry auth specification '%s'", cred)
}
registryPart := strings.TrimSpace(parts[0])
authPart := strings.TrimSpace(parts[1])
var auth spec.RegistryAuth
// if the auth is a token, we don't need a username
credParts := strings.SplitN(authPart, ":", 2)
var userPart, credPart string
userPart = strings.TrimSpace(credParts[0])
if len(credParts) == 2 {
credPart = strings.TrimSpace(credParts[1])
}
switch {
case len(registryPart) == 0:
return fmt.Errorf("invalid registry auth specification '%s', registry must not be blank", cred)
case len(credParts) == 2 && (len(userPart) == 0 || len(credPart) == 0):
return fmt.Errorf("invalid registry auth specification '%s', username and password must not be blank", cred)
case len(credParts) == 1 && len(userPart) == 0:
return fmt.Errorf("invalid registry auth specification '%s', token must not be blank", cred)
case len(credParts) == 2:
auth = spec.RegistryAuth{
Username: userPart,
Password: credPart,
}
case len(credParts) == 1:
auth = spec.RegistryAuth{
RegistryToken: authPart,
}
default:
return fmt.Errorf("invalid registry auth specification '%s'", cred)
}
registryCredMap[registryPart] = auth
}
opts = append(opts, pkglib.WithRegistryAuth(registryCredMap))
}
for _, p := range pkgs { for _, p := range pkgs {
// things we need our own copies of // things we need our own copies of
@ -224,6 +273,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command {
cmd.Flags().StringVar(&skipPlatforms, "skip-platforms", "", "Platforms that should be skipped, even if present in build.yml") cmd.Flags().StringVar(&skipPlatforms, "skip-platforms", "", "Platforms that should be skipped, even if present in build.yml")
cmd.Flags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes") cmd.Flags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
cmd.Flags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use") cmd.Flags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use")
cmd.Flags().StringVar(&builderConfig, "builder-config", "", "path to buildkit builder config.toml file to use, overrides the default config.toml in the builder image; USE WITH CAUTION")
cmd.Flags().BoolVar(&builderRestart, "builder-restart", false, "force restarting builder, even if container with correct name and image exists") cmd.Flags().BoolVar(&builderRestart, "builder-restart", false, "force restarting builder, even if container with correct name and image exists")
cmd.Flags().Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir)) cmd.Flags().Var(&cacheDir, "cache", fmt.Sprintf("Directory for caching and finding cached image, overrides env var %s", envVarCacheDir))
cmd.Flags().StringVar(&release, "release", "", "Release the given version") cmd.Flags().StringVar(&release, "release", "", "Release the given version")

View File

@ -15,6 +15,7 @@ func pkgBuilderCmd() *cobra.Command {
builders string builders string
platforms string platforms string
builderImage string builderImage string
builderConfigPath string
) )
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "builder", Use: "builder",
@ -40,11 +41,11 @@ func pkgBuilderCmd() *cobra.Command {
platformsToClean := strings.Split(platforms, ",") platformsToClean := strings.Split(platforms, ",")
switch command { switch command {
case "du": case "du":
if err := pkglib.DiskUsage(buildersMap, builderImage, platformsToClean, verbose); err != nil { if err := pkglib.DiskUsage(buildersMap, builderImage, builderConfigPath, platformsToClean, verbose); err != nil {
return fmt.Errorf("unable to print disk usage of builder: %w", err) return fmt.Errorf("unable to print disk usage of builder: %w", err)
} }
case "prune": case "prune":
if err := pkglib.PruneBuilder(buildersMap, builderImage, platformsToClean, verbose); err != nil { if err := pkglib.PruneBuilder(buildersMap, builderImage, builderConfigPath, platformsToClean, verbose); err != nil {
return fmt.Errorf("unable to prune builder: %w", err) return fmt.Errorf("unable to prune builder: %w", err)
} }
default: default:
@ -57,6 +58,7 @@ func pkgBuilderCmd() *cobra.Command {
cmd.PersistentFlags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes") cmd.PersistentFlags().StringVar(&builders, "builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
cmd.PersistentFlags().StringVar(&platforms, "platforms", fmt.Sprintf("linux/%s", runtime.GOARCH), "Which platforms we built images for") cmd.PersistentFlags().StringVar(&platforms, "platforms", fmt.Sprintf("linux/%s", runtime.GOARCH), "Which platforms we built images for")
cmd.PersistentFlags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use") cmd.PersistentFlags().StringVar(&builderImage, "builder-image", defaultBuilderImage, "buildkit builder container image to use")
cmd.Flags().StringVar(&builderConfigPath, "builder-config", "", "path to buildkit builder config.toml file to use, overrides the default config.toml in the builder image; USE WITH CAUTION")
return cmd return cmd
} }

View File

@ -39,6 +39,7 @@ type buildOpts struct {
runner dockerRunner runner dockerRunner
writer io.Writer writer io.Writer
builderImage string builderImage string
builderConfigPath string
builderRestart bool builderRestart bool
sbomScan bool sbomScan bool
sbomScannerImage string sbomScannerImage string
@ -46,6 +47,7 @@ type buildOpts struct {
buildArgs []string buildArgs []string
progress string progress string
ssh []string ssh []string
registryAuth map[string]spec.RegistryAuth
} }
// BuildOpt allows callers to specify options to Build // BuildOpt allows callers to specify options to Build
@ -164,6 +166,14 @@ func WithBuildBuilderImage(image string) BuildOpt {
} }
} }
// WithBuildBuilderConfig set the contents of the
func WithBuildBuilderConfig(builderConfigPath string) BuildOpt {
return func(bo *buildOpts) error {
bo.builderConfigPath = builderConfigPath
return nil
}
}
// WithBuildBuilderRestart restart the builder container even if it already is running with the correct image version // WithBuildBuilderRestart restart the builder container even if it already is running with the correct image version
func WithBuildBuilderRestart(restart bool) BuildOpt { func WithBuildBuilderRestart(restart bool) BuildOpt {
return func(bo *buildOpts) error { return func(bo *buildOpts) error {
@ -223,6 +233,14 @@ func WithSSH(ssh []string) BuildOpt {
} }
} }
// WithRegistryAuth stores registry credentials
func WithRegistryAuth(creds map[string]spec.RegistryAuth) BuildOpt {
return func(bo *buildOpts) error {
bo.registryAuth = creds
return nil
}
}
// Build builds the package // Build builds the package
func (p Pkg) Build(bos ...BuildOpt) error { func (p Pkg) Build(bos ...BuildOpt) error {
var bo buildOpts var bo buildOpts
@ -449,10 +467,11 @@ func (p Pkg) Build(bos ...BuildOpt) error {
} }
imageBuildOpts.SSH = bo.ssh imageBuildOpts.SSH = bo.ssh
imageBuildOpts.RegistryAuths = bo.registryAuth
// build for each arch and save in the linuxkit cache // build for each arch and save in the linuxkit cache
for _, platform := range platformsToBuild { for _, platform := range platformsToBuild {
builtDescs, err := p.buildArch(ctx, d, c, bo.builderImage, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts) builtDescs, err := p.buildArch(ctx, d, c, bo.builderImage, bo.builderConfigPath, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts)
if err != nil { if err != nil {
return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err) return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err)
} }
@ -602,7 +621,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
// C - manifest, saved in cache as is, referenced by the index (E), and returned as a descriptor // C - manifest, saved in cache as is, referenced by the index (E), and returned as a descriptor
// D - attestations (if any), saved in cache as is, referenced by the index (E), and returned as a descriptor // D - attestations (if any), saved in cache as is, referenced by the index (E), and returned as a descriptor
// E - index, saved in cache as is, stored in cache as tag "image:tag-arch", *not* returned as a descriptor // E - index, saved in cache as is, stored in cache as tag "image:tag-arch", *not* returned as a descriptor
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider, builderImage, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts spec.ImageBuildOptions) ([]registry.Descriptor, error) { func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider, builderImage, builderConfigPath, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts spec.ImageBuildOptions) ([]registry.Descriptor, error) {
var ( var (
tagArch string tagArch string
tag = p.FullTag() tag = p.FullTag()
@ -671,7 +690,7 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider
imageBuildOpts.Dockerfile = bo.dockerfile imageBuildOpts.Dockerfile = bo.dockerfile
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil { if err := d.build(ctx, tagArch, p.path, builderName, builderImage, builderConfigPath, platform, restart, passCache, buildCtx.Reader(), stdout, bo.sbomScan, bo.sbomScannerImage, bo.progress, imageBuildOpts); err != nil {
stdoutCloser() stdoutCloser()
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") { 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) 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

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

View File

@ -37,12 +37,16 @@ import (
// golint requires comments on non-main(test) // golint requires comments on non-main(test)
// package for blank import // package for blank import
dockerconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
dockerconfigtypes "github.com/docker/cli/cli/config/types"
_ "github.com/moby/buildkit/client/connhelper/dockercontainer" _ "github.com/moby/buildkit/client/connhelper/dockercontainer"
_ "github.com/moby/buildkit/client/connhelper/ssh" _ "github.com/moby/buildkit/client/connhelper/ssh"
"github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/linter" "github.com/moby/buildkit/frontend/dockerfile/linter"
"github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/moby/buildkit/frontend/dockerfile/shell" "github.com/moby/buildkit/frontend/dockerfile/shell"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/session/sshforward/sshprovider" "github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/session/upload/uploadprovider" "github.com/moby/buildkit/session/upload/uploadprovider"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -54,16 +58,17 @@ const (
buildkitWaitServer = 30 // seconds buildkitWaitServer = 30 // seconds
buildkitCheckInterval = 1 // seconds buildkitCheckInterval = 1 // seconds
sbomFrontEndKey = "attest:sbom" sbomFrontEndKey = "attest:sbom"
buildkitConfigPath = "/etc/buildkit/buildkitd.toml"
) )
type dockerRunner interface { type dockerRunner interface {
tag(ref, tag string) error 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, platformType string, imageBuildOpts spec.ImageBuildOptions) error build(ctx context.Context, tag, pkg, dockerContext, builderImage, builderConfigPath, platform string, restart bool, c spec.CacheProvider, r io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, platformType string, imageBuildOpts spec.ImageBuildOptions) error
save(tgt string, refs ...string) error save(tgt string, refs ...string) error
load(src io.Reader) error load(src io.Reader) error
pull(img string) (bool, error) pull(img string) (bool, error)
contextSupportCheck() error contextSupportCheck() error
builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error) builder(ctx context.Context, dockerContext, builderImage, builderConfigPath, platform string, restart bool) (*buildkitClient.Client, error)
} }
type dockerRunnerImpl struct { type dockerRunnerImpl struct {
@ -218,14 +223,14 @@ func (dr *dockerRunnerImpl) contextSupportCheck() error {
// 1. if dockerContext is provided, try to create a builder with that context; if it succeeds, we are done; if not, return an error. // 1. if dockerContext is provided, try to create a builder with that context; if it succeeds, we are done; if not, return an error.
// 2. try to find an existing named runner with the pattern; if it succeeds, we are done; if not, try next. // 2. try to find an existing named runner with the pattern; if it succeeds, we are done; if not, try next.
// 3. try to create a generic builder using the default context named "linuxkit". // 3. try to create a generic builder using the default context named "linuxkit".
func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error) { func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderImage, builderConfigPath, platform string, restart bool) (*buildkitClient.Client, error) {
// if we were given a context, we must find a builder and use it, or create one and use it // if we were given a context, we must find a builder and use it, or create one and use it
if dockerContext != "" { if dockerContext != "" {
// does the context exist? // does the context exist?
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err != nil { if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err != nil {
return nil, fmt.Errorf("provided docker context '%s' not found", dockerContext) return nil, fmt.Errorf("provided docker context '%s' not found", dockerContext)
} }
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart) client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, platform, dockerContext, restart)
if err != nil { if err != nil {
return nil, fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err) return nil, fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err)
} }
@ -236,13 +241,13 @@ func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderI
dockerContext = fmt.Sprintf("%s-%s", "linuxkit", strings.ReplaceAll(platform, "/", "-")) dockerContext = fmt.Sprintf("%s-%s", "linuxkit", strings.ReplaceAll(platform, "/", "-"))
if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err == nil { if err := dr.command(nil, io.Discard, io.Discard, "context", "inspect", dockerContext); err == nil {
// we found an appropriately named context, so let us try to use it or error out // we found an appropriately named context, so let us try to use it or error out
if client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart); err == nil { if client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, platform, dockerContext, restart); err == nil {
return client, nil return client, nil
} }
} }
// create a generic builder // create a generic builder
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, "", "default", restart) client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, "", "default", restart)
if err != nil { if err != nil {
return nil, fmt.Errorf("error ensuring builder container in default context: %v", err) return nil, fmt.Errorf("error ensuring builder container in default context: %v", err)
} }
@ -254,7 +259,7 @@ func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderI
// but has the wrong version of buildkit, or not running buildkit at all, remove it and create an appropriate // but has the wrong version of buildkit, or not running buildkit at all, remove it and create an appropriate
// one. // one.
// Returns a network connection to the buildkit builder in the container. // Returns a network connection to the buildkit builder in the container.
func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, image, platform, dockerContext string, forceRestart bool) (*buildkitClient.Client, error) { func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, image, configPath, platform, dockerContext string, forceRestart bool) (*buildkitClient.Client, error) {
// if no error, then we have a builder already // if no error, then we have a builder already
// inspect it to make sure it is of the right type // inspect it to make sure it is of the right type
var ( var (
@ -288,6 +293,30 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
cid = containerJSON[0].ID cid = containerJSON[0].ID
existingImage := containerJSON[0].Config.Image existingImage := containerJSON[0].Config.Image
isRunning := containerJSON[0].State.Status == "running" isRunning := containerJSON[0].State.Status == "running"
// need to check for mounts, in case the builder-config is provided
// by default, we assume the configPath is correct
var configPathCorrect = true
if configPath != "" {
// if it is provided, we assume it is false until proven true
configPathCorrect = false
for _, mount := range containerJSON[0].Mounts {
// if this mount is not the buildkit config path, we can ignore it
if mount.Destination != buildkitConfigPath {
continue
}
// if the mount source does not match the provided configPath,
// we should restart it
// Just break. Since configPathCorrect is set to false, the switch statement below
// will catch it
if mount.Source != configPath {
fmt.Printf("existing container %s has config mounted from %s instead of expected %s, replacing\n", name, mount.Source, configPath)
} else {
configPathCorrect = true
}
// no need to cheak any more, we found the specific mount
break
}
}
switch { switch {
case forceRestart: case forceRestart:
@ -308,6 +337,11 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
recreate = true recreate = true
stop = isRunning stop = isRunning
remove = true remove = true
case !configPathCorrect:
fmt.Printf("existing container has wrong configPath mount, restarting")
recreate = true
stop = isRunning
remove = true
case isRunning: case isRunning:
// if already running with the right image and permissions, just use it // if already running with the right image and permissions, just use it
fmt.Printf("using existing container %s\n", name) fmt.Printf("using existing container %s\n", name)
@ -351,7 +385,17 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im
} }
if recreate { if recreate {
// create the builder // create the builder
args := []string{"--context", dockerContext, "container", "run", "-d", "--name", name, "--privileged", image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug"} args := []string{"--context", dockerContext, "container", "run", "-d", "--name", name, "--privileged"}
// was a config file provided?
if configPath != "" {
// if so, we need to pass it as a buildkitd config file
args = append(args, "-v", fmt.Sprintf("%s:%s:ro", configPath, buildkitConfigPath))
}
args = append(args, image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug")
if configPath != "" {
// set the config path explicitly
args = append(args, "--config", buildkitConfigPath)
}
msg := fmt.Sprintf("creating builder container '%s' in context '%s'", name, dockerContext) msg := fmt.Sprintf("creating builder container '%s' in context '%s'", name, dockerContext)
fmt.Println(msg) fmt.Println(msg)
if err := dr.command(nil, nil, io.Discard, args...); err != nil { if err := dr.command(nil, nil, io.Discard, args...); err != nil {
@ -442,9 +486,9 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
return dr.command(nil, nil, nil, "image", "tag", ref, tag) 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, progressType string, imageBuildOpts spec.ImageBuildOptions) error { func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, builderConfigPath, platform string, restart bool, c spec.CacheProvider, stdin io.Reader, stdout io.Writer, sbomScan bool, sbomScannerImage, progressType string, imageBuildOpts spec.ImageBuildOptions) error {
// ensure we have a builder // ensure we have a builder
client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart) client, err := dr.builder(ctx, dockerContext, builderImage, builderConfigPath, platform, restart)
if err != nil { if err != nil {
return fmt.Errorf("unable to ensure builder container: %v", err) return fmt.Errorf("unable to ensure builder container: %v", err)
} }
@ -495,6 +539,7 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
attachable := []session.Attachable{} attachable := []session.Attachable{}
localDirs := map[string]string{} localDirs := map[string]string{}
// Add SSH agent provider if needed
if len(imageBuildOpts.SSH) > 0 { if len(imageBuildOpts.SSH) > 0 {
configs, err := build.ParseSSH(imageBuildOpts.SSH) configs, err := build.ParseSSH(imageBuildOpts.SSH)
if err != nil { if err != nil {
@ -515,8 +560,30 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext,
} else { } else {
localDirs[dockerui.DefaultLocalNameDockerfile] = pkg localDirs[dockerui.DefaultLocalNameDockerfile] = pkg
localDirs[dockerui.DefaultLocalNameContext] = pkg localDirs[dockerui.DefaultLocalNameContext] = pkg
} }
// add credentials
var cf *configfile.ConfigFile
if len(imageBuildOpts.RegistryAuths) > 0 {
// if static ones were provided, use those
cf = configfile.New("custom")
// merge imageBuildOpts.RegistryAuths into dockercfg
for registry, auth := range imageBuildOpts.RegistryAuths {
bareRegistry := strings.TrimPrefix(registry, "https://")
bareRegistry = strings.TrimPrefix(bareRegistry, "http://")
cf.AuthConfigs[bareRegistry] = dockerconfigtypes.AuthConfig{
ServerAddress: bareRegistry,
Username: auth.Username,
Password: auth.Password,
RegistryToken: auth.RegistryToken,
}
}
} else {
// Else use Docker authentication provider so BuildKit can use ~/.docker/config.json or OS-specific credential helpers.
cf = dockerconfig.LoadDefaultConfigFile(io.Discard)
}
attachable = append(attachable,
authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{ConfigFile: cf}),
)
solveOpts := buildkitClient.SolveOpt{ solveOpts := buildkitClient.SolveOpt{
Frontend: "dockerfile.v0", Frontend: "dockerfile.v0",

View File

@ -93,14 +93,14 @@ func printVerbose(tw *tabwriter.Writer, du []*buildkitClient.UsageInfo) {
_ = tw.Flush() _ = tw.Flush()
} }
func getClientForPlatform(ctx context.Context, buildersMap map[string]string, builderImage, platform string) (*buildkitClient.Client, error) { func getClientForPlatform(ctx context.Context, buildersMap map[string]string, builderImage, builderConfigPath, platform string) (*buildkitClient.Client, error) {
p, err := platforms.Parse(platform) p, err := platforms.Parse(platform)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse platform: %s", err) return nil, fmt.Errorf("failed to parse platform: %s", err)
} }
dr := newDockerRunner(false) dr := newDockerRunner(false)
builderName := getBuilderForPlatform(p.Architecture, buildersMap) builderName := getBuilderForPlatform(p.Architecture, buildersMap)
client, err := dr.builder(ctx, builderName, builderImage, platform, false) client, err := dr.builder(ctx, builderName, builderImage, builderConfigPath, platform, false)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to ensure builder container: %v", err) return nil, fmt.Errorf("unable to ensure builder container: %v", err)
} }
@ -108,11 +108,11 @@ func getClientForPlatform(ctx context.Context, buildersMap map[string]string, bu
} }
// DiskUsage of builder // DiskUsage of builder
func DiskUsage(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error { func DiskUsage(buildersMap map[string]string, builderImage, builderConfigPath string, platformsToClean []string, verbose bool) error {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
for _, platform := range platformsToClean { for _, platform := range platformsToClean {
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform) client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
if err != nil { if err != nil {
return fmt.Errorf("cannot get client: %s", err) return fmt.Errorf("cannot get client: %s", err)
} }
@ -143,12 +143,12 @@ func DiskUsage(buildersMap map[string]string, builderImage string, platformsToCl
} }
// PruneBuilder clean build cache of builder // PruneBuilder clean build cache of builder
func PruneBuilder(buildersMap map[string]string, builderImage string, platformsToClean []string, verbose bool) error { func PruneBuilder(buildersMap map[string]string, builderImage, builderConfigPath string, platformsToClean []string, verbose bool) error {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
total := int64(0) total := int64(0)
for _, platform := range platformsToClean { for _, platform := range platformsToClean {
client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform) client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform)
if err != nil { if err != nil {
return fmt.Errorf("cannot get client: %s", err) return fmt.Errorf("cannot get client: %s", err)
} }

View File

@ -1,9 +1,16 @@
package spec package spec
type RegistryAuth struct {
Username string
Password string
RegistryToken string // base64 encoded auth token
}
type ImageBuildOptions struct { type ImageBuildOptions struct {
Labels map[string]string Labels map[string]string
BuildArgs map[string]*string BuildArgs map[string]*string
NetworkMode string NetworkMode string
Dockerfile string Dockerfile string
SSH []string SSH []string
RegistryAuths map[string]RegistryAuth
} }

View File

@ -0,0 +1,3 @@
Dockerfile
buildkitd.toml
docker-config/

View File

@ -0,0 +1,2 @@
FROM alpine:3.21
RUN echo hi

View File

@ -0,0 +1,2 @@
org: linuxkit
image: auth-registry

View File

@ -0,0 +1,129 @@
#!/bin/sh
# SUMMARY: Check that we can access a registry with auth
# LABELS:
set -e
# Source libraries. Uncomment if needed/defined
#. "${RT_LIB}"
. "${RT_PROJECT_ROOT}/_lib/lib.sh"
clean_up() {
docker kill "${REGISTRY_NAME}" || true
DOCKER_CONFIG="${DOCKER_CONFIG}" docker buildx rm "${BUILDKIT_NAME}" || true
[ -n "${CACHDIR}" ] && rm -rf "${CACHDIR}"
[ -n "${DOCKER_CONFIG}" ] && rm -rf "${DOCKER_CONFIG}"
[ -n "${REGISTRY_DIR}" ] && rm -rf "${REGISTRY_DIR}"
}
trap clean_up EXIT
# determine platform
ARCH=$(uname -m)
if [ "${ARCH}" = "x86_64" ]; then
ARCH="amd64"
elif [ "${ARCH}" = "aarch64" ]; then
ARCH="arm64"
fi
PLATFORM="linux/${ARCH}"
# container names
REGISTRY_NAME="test-registry-$$"
BUILDKIT_NAME="test-buildkitd-$$"
# start a registry with auth
REGISTRY_USER="testuser"
REGISTRY_PASS="testpass"
REGISTRY_PORT="5000"
REGISTRY_DIR=$(mktemp -d)
mkdir -p "$REGISTRY_DIR/auth"
docker run --rm \
--entrypoint htpasswd \
httpd:2 -Bbn "${REGISTRY_USER}" "${REGISTRY_PASS}" > "$REGISTRY_DIR/auth/htpasswd"
# Start registry
REGISTRY_CID=$(docker run -d --rm \
-p ":${REGISTRY_PORT}" \
-v "$REGISTRY_DIR/auth:/auth" \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
--name "${REGISTRY_NAME}" \
registry:3)
REGISTRY_IP=$(docker inspect "${REGISTRY_NAME}" \
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
IMAGENAME="${REGISTRY_IP}:${REGISTRY_PORT}/myimage"
# start an insecure buildkit so we can load an image to the registry
cat > buildkitd.toml <<EOF
[registry."${REGISTRY_IP}:${REGISTRY_PORT}"]
insecure = true
http = true
EOF
# save the credentials
credsb64=$(printf "%s" "${REGISTRY_USER}:${REGISTRY_PASS}" | base64)
# DO NOT export DOCKER_CONFIG, as that will cause the thing we are testing to succeed.
# we need to be explicit about it.
DOCKER_CONFIG=$(pwd)/docker-config
rm -rf "${DOCKER_CONFIG}"
mkdir -p "${DOCKER_CONFIG}"
cat > "${DOCKER_CONFIG}/config.json" <<EOF
{
"auths": {
"${REGISTRY_IP}:5000": {
"auth": "${credsb64}"
}
}
}
EOF
DOCKER_CONFIG=${DOCKER_CONFIG} docker buildx create \
--name "${BUILDKIT_NAME}" \
--driver docker-container \
--buildkitd-config "$(pwd)/buildkitd.toml" \
--bootstrap
DOCKER_CONFIG=${DOCKER_CONFIG} docker buildx build \
--builder "${BUILDKIT_NAME}" \
--file Dockerfile.base \
--tag "${IMAGENAME}" \
--push \
--progress plain \
--platform "${PLATFORM}" \
.
# Generate Dockerfile for pkg with FROM
cat > Dockerfile <<EOF
FROM "${IMAGENAME}"
RUN echo SUCCESS
EOF
CACHEDIR=$(mktemp -d)
# 3 tests:
# 1. build a package with no auth - should fail
# 2. build a package with explicit auth - should succeed
# 3. build a package with auth in the config - should succeed
if linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
--builder-config "$(pwd)/buildkitd.toml" --force \
.; then
echo "Test 1 failed: build succeeded without auth"
exit 1
fi
linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
--builder-config "$(pwd)/buildkitd.toml" --force \
--registry-creds "${REGISTRY_IP}:${REGISTRY_PORT}=${REGISTRY_USER}:${REGISTRY_PASS}" \
.
DOCKER_CONFIG=${DOCKER_CONFIG} linuxkit --cache "${CACHEDIR}" pkg build --platforms "${PLATFORM}" \
--builder-config "$(pwd)/buildkitd.toml" --force \
.
exit 0