diff --git a/src/cmd/linuxkit/pkg.go b/src/cmd/linuxkit/pkg.go index 33bcaa300..08e8e4c23 100644 --- a/src/cmd/linuxkit/pkg.go +++ b/src/cmd/linuxkit/pkg.go @@ -7,7 +7,10 @@ import ( "github.com/spf13/cobra" ) -var pkglibConfig pkglib.PkglibConfig +var ( + pkglibConfig pkglib.PkglibConfig + registryCreds []string +) func pkgCmd() *cobra.Command { 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(&devMode, "dev", false, "Force org and hash to $USER and \"dev\" respectively") + cmd.PersistentFlags().StringSliceVar(®istryCreds, "registry-creds", nil, "Registry auths to use for building images, format is =: OR =. If no username is provided, it is treated as a registry token. 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 } diff --git a/src/cmd/linuxkit/pkg_build.go b/src/cmd/linuxkit/pkg_build.go index 6c00f92e5..37b386c82 100644 --- a/src/cmd/linuxkit/pkg_build.go +++ b/src/cmd/linuxkit/pkg_build.go @@ -8,6 +8,7 @@ import ( "strings" "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" "github.com/spf13/cobra" ) @@ -40,6 +41,7 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command { skipPlatforms string builders string builderImage string + builderConfig string builderRestart bool release string nobuild bool @@ -159,6 +161,13 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command { if err != nil { 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.WithBuildBuilderImage(builderImage)) opts = append(opts, pkglib.WithBuildBuilderRestart(builderRestart)) @@ -166,6 +175,46 @@ func addCmdRunPkgBuildPush(cmd *cobra.Command, withPush bool) *cobra.Command { if len(ssh) > 0 { 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 { // 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(&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(&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().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") diff --git a/src/cmd/linuxkit/pkg_builder.go b/src/cmd/linuxkit/pkg_builder.go index a37971585..1f3700874 100644 --- a/src/cmd/linuxkit/pkg_builder.go +++ b/src/cmd/linuxkit/pkg_builder.go @@ -12,9 +12,10 @@ import ( func pkgBuilderCmd() *cobra.Command { var ( - builders string - platforms string - builderImage string + builders string + platforms string + builderImage string + builderConfigPath string ) cmd := &cobra.Command{ Use: "builder", @@ -40,11 +41,11 @@ func pkgBuilderCmd() *cobra.Command { platformsToClean := strings.Split(platforms, ",") switch command { 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) } 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) } 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(&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.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 } diff --git a/src/cmd/linuxkit/pkglib/build.go b/src/cmd/linuxkit/pkglib/build.go index 5a6c4e16c..3a471fb4b 100644 --- a/src/cmd/linuxkit/pkglib/build.go +++ b/src/cmd/linuxkit/pkglib/build.go @@ -24,28 +24,30 @@ import ( ) type buildOpts struct { - skipBuild bool - force bool - pull bool - ignoreCache bool - push bool - release string - manifest bool - targetDocker bool - cacheDir string - cacheProvider spec.CacheProvider - platforms []imagespec.Platform - builders map[string]string - runner dockerRunner - writer io.Writer - builderImage string - builderRestart bool - sbomScan bool - sbomScannerImage string - dockerfile string - buildArgs []string - progress string - ssh []string + skipBuild bool + force bool + pull bool + ignoreCache bool + push bool + release string + manifest bool + targetDocker bool + cacheDir string + cacheProvider spec.CacheProvider + platforms []imagespec.Platform + builders map[string]string + runner dockerRunner + writer io.Writer + builderImage string + builderConfigPath string + builderRestart bool + sbomScan bool + sbomScannerImage string + dockerfile string + buildArgs []string + progress string + ssh []string + registryAuth map[string]spec.RegistryAuth } // 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 func WithBuildBuilderRestart(restart bool) BuildOpt { 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 func (p Pkg) Build(bos ...BuildOpt) error { var bo buildOpts @@ -449,10 +467,11 @@ func (p Pkg) Build(bos ...BuildOpt) error { } imageBuildOpts.SSH = bo.ssh + imageBuildOpts.RegistryAuths = bo.registryAuth // build for each arch and save in the linuxkit cache 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 { 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 // 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 -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 ( tagArch string tag = p.FullTag() @@ -671,7 +690,7 @@ func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c spec.CacheProvider 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() 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) diff --git a/src/cmd/linuxkit/pkglib/build_test.go b/src/cmd/linuxkit/pkglib/build_test.go index cc0600a4e..d60a140a3 100644 --- a/src/cmd/linuxkit/pkglib/build_test.go +++ b/src/cmd/linuxkit/pkglib/build_test.go @@ -53,10 +53,10 @@ func (d *dockerMocker) contextSupportCheck() error { } 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") } -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 { return errors.New("build disabled") } diff --git a/src/cmd/linuxkit/pkglib/docker.go b/src/cmd/linuxkit/pkglib/docker.go index 452101626..f6a3e2a9c 100644 --- a/src/cmd/linuxkit/pkglib/docker.go +++ b/src/cmd/linuxkit/pkglib/docker.go @@ -37,12 +37,16 @@ import ( // golint requires comments on non-main(test) // 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/ssh" "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/linter" "github.com/moby/buildkit/frontend/dockerfile/parser" "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/upload/uploadprovider" log "github.com/sirupsen/logrus" @@ -54,16 +58,17 @@ const ( buildkitWaitServer = 30 // seconds buildkitCheckInterval = 1 // seconds sbomFrontEndKey = "attest:sbom" + buildkitConfigPath = "/etc/buildkit/buildkitd.toml" ) 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, 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 load(src io.Reader) error pull(img string) (bool, 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 { @@ -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. // 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". -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 dockerContext != "" { // does the context exist? 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) } - client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart) + client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, builderConfigPath, platform, dockerContext, restart) if err != nil { 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, "/", "-")) 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 - 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 } } // 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 { 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 // one. // 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 // inspect it to make sure it is of the right type var ( @@ -288,6 +293,30 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im cid = containerJSON[0].ID existingImage := containerJSON[0].Config.Image 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 { case forceRestart: @@ -308,6 +337,11 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im recreate = true stop = isRunning remove = true + case !configPathCorrect: + fmt.Printf("existing container has wrong configPath mount, restarting") + recreate = true + stop = isRunning + remove = true case isRunning: // if already running with the right image and permissions, just use it fmt.Printf("using existing container %s\n", name) @@ -351,7 +385,17 @@ func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, im } if recreate { // 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) fmt.Println(msg) 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) } -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 - client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart) + client, err := dr.builder(ctx, dockerContext, builderImage, builderConfigPath, platform, restart) if err != nil { 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{} localDirs := map[string]string{} + // Add SSH agent provider if needed if len(imageBuildOpts.SSH) > 0 { configs, err := build.ParseSSH(imageBuildOpts.SSH) if err != nil { @@ -515,8 +560,30 @@ func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, } else { localDirs[dockerui.DefaultLocalNameDockerfile] = 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{ Frontend: "dockerfile.v0", diff --git a/src/cmd/linuxkit/pkglib/utils.go b/src/cmd/linuxkit/pkglib/utils.go index 29c447b6b..02e2f752b 100644 --- a/src/cmd/linuxkit/pkglib/utils.go +++ b/src/cmd/linuxkit/pkglib/utils.go @@ -93,14 +93,14 @@ func printVerbose(tw *tabwriter.Writer, du []*buildkitClient.UsageInfo) { _ = 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) if err != nil { return nil, fmt.Errorf("failed to parse platform: %s", err) } dr := newDockerRunner(false) 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 { 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 -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()) defer cancel() for _, platform := range platformsToClean { - client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform) + client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform) if err != nil { 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 -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()) defer cancel() total := int64(0) for _, platform := range platformsToClean { - client, err := getClientForPlatform(ctx, buildersMap, builderImage, platform) + client, err := getClientForPlatform(ctx, buildersMap, builderImage, builderConfigPath, platform) if err != nil { return fmt.Errorf("cannot get client: %s", err) } diff --git a/src/cmd/linuxkit/spec/build.go b/src/cmd/linuxkit/spec/build.go index dfdf0b70a..fb6f71cb1 100644 --- a/src/cmd/linuxkit/spec/build.go +++ b/src/cmd/linuxkit/spec/build.go @@ -1,9 +1,16 @@ package spec -type ImageBuildOptions struct { - Labels map[string]string - BuildArgs map[string]*string - NetworkMode string - Dockerfile string - SSH []string +type RegistryAuth struct { + Username string + Password string + RegistryToken string // base64 encoded auth token +} + +type ImageBuildOptions struct { + Labels map[string]string + BuildArgs map[string]*string + NetworkMode string + Dockerfile string + SSH []string + RegistryAuths map[string]RegistryAuth } diff --git a/test/cases/040_packages/025_auth/.gitignore b/test/cases/040_packages/025_auth/.gitignore new file mode 100644 index 000000000..d70b6f8c6 --- /dev/null +++ b/test/cases/040_packages/025_auth/.gitignore @@ -0,0 +1,3 @@ +Dockerfile +buildkitd.toml +docker-config/ diff --git a/test/cases/040_packages/025_auth/Dockerfile.base b/test/cases/040_packages/025_auth/Dockerfile.base new file mode 100644 index 000000000..be773ea87 --- /dev/null +++ b/test/cases/040_packages/025_auth/Dockerfile.base @@ -0,0 +1,2 @@ +FROM alpine:3.21 +RUN echo hi \ No newline at end of file diff --git a/test/cases/040_packages/025_auth/build.yml b/test/cases/040_packages/025_auth/build.yml new file mode 100644 index 000000000..2a6cc20f0 --- /dev/null +++ b/test/cases/040_packages/025_auth/build.yml @@ -0,0 +1,2 @@ +org: linuxkit +image: auth-registry diff --git a/test/cases/040_packages/025_auth/test.sh b/test/cases/040_packages/025_auth/test.sh new file mode 100644 index 000000000..42611053d --- /dev/null +++ b/test/cases/040_packages/025_auth/test.sh @@ -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 < "${DOCKER_CONFIG}/config.json" < Dockerfile <