Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ import (
"github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/platformutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/config"
dockeropts "github.com/docker/cli/opts"
hcl "github.com/hashicorp/hcl/v2"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/pkg/errors"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
Expand Down Expand Up @@ -1256,18 +1254,12 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
}

func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
// make sure local credentials are loaded multiple times for different targets
authProvider := authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
ConfigFile: config.LoadDefaultConfigFile(os.Stderr),
})

m2 := make(map[string]build.Options, len(m))
for k, v := range m {
bo, err := toBuildOpt(v, inp)
if err != nil {
return nil, err
}
bo.Session = append(bo.Session, authProvider)
m2[k] = *bo
}
return m2, nil
Expand Down
12 changes: 12 additions & 0 deletions commands/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ import (
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/dockerutil/dockerconfig"
"github.com/docker/buildx/util/osutil"
"github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/tracing"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -252,6 +254,16 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
return err
}

// make sure local credentials aren't loaded multiple times for different targets
authProvider := authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
AuthConfigProvider: dockerconfig.LoadAuthConfig(dockerCli),
})

for k, opt := range bo {
opt.Session = append(opt.Session, authProvider)
bo[k] = opt
}

def := struct {
Group map[string]*bake.Group `json:"group,omitempty"`
Target map[string]*bake.Target `json:"target"`
Expand Down
3 changes: 2 additions & 1 deletion commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/desktop"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/dockerutil/dockerconfig"
"github.com/docker/buildx/util/ioset"
"github.com/docker/buildx/util/metricutil"
"github.com/docker/buildx/util/osutil"
Expand Down Expand Up @@ -1019,7 +1020,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in *BuildOptions, inSt
opts.Platforms = platforms

opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(authprovider.DockerAuthProviderConfig{
ConfigFile: dockerCli.ConfigFile(),
AuthConfigProvider: dockerconfig.LoadAuthConfig(dockerCli),
}))

secrets, err := build.CreateSecrets(in.Secrets)
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ require (
github.com/hashicorp/hcl/v2 v2.24.0
github.com/in-toto/in-toto-golang v0.9.0
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/moby/buildkit v0.26.2
github.com/moby/go-archive v0.1.0
github.com/moby/buildkit v0.26.1-0.20260106154623-ed6dc749ce40 // v0.27.0-dev
github.com/moby/go-archive v0.2.0
github.com/moby/moby/api v1.52.0
github.com/moby/moby/client v0.2.1
github.com/moby/sys/atomicwriter v0.1.0
Expand All @@ -47,7 +47,7 @@ require (
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117
github.com/zclconf/go-cty v1.17.0
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,12 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/moby/buildkit v0.26.2 h1:EIh5j0gzRsCZmQzvgNNWzSDbuKqwUIiBH7ssqLv8RU8=
github.com/moby/buildkit v0.26.2/go.mod h1:ylDa7IqzVJgLdi/wO7H1qLREFQpmhFbw2fbn4yoTw40=
github.com/moby/buildkit v0.26.1-0.20260106154623-ed6dc749ce40 h1:h45o8dTo7Cq1Su49bs9VHP83Fdp3olgMfgo3tZULXUw=
github.com/moby/buildkit v0.26.1-0.20260106154623-ed6dc749ce40/go.mod h1:LtUhyzVLAD0ilniDZRbghEyRq9CgZAY7ywMXlVo9MBI=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
Expand Down Expand Up @@ -319,8 +319,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4=
github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI=
github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA=
github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE=
github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE=
github.com/tonistiigi/jaeger-ui-rest v0.0.0-20250408171107-3dd17559e117 h1:XFwyh2JZwR5aiKLXHX2C1n0v5F11dCJpyGL1W/Cpl3U=
Expand Down
254 changes: 254 additions & 0 deletions util/dockerutil/dockerconfig/configprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package dockerconfig

import (
"cmp"
"context"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"time"

"github.com/docker/buildx/util/confutil"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
"github.com/moby/buildkit/session/auth/authprovider"
)

func LoadAuthConfig(cli command.Cli) authprovider.AuthConfigProvider {
acp := &authConfigProvider{
buildxConfig: confutil.NewConfig(cli),
defaultConfig: cli.ConfigFile(),
authConfigCache: map[string]authConfigCacheEntry{},
}
return acp.load
}

type authConfigProvider struct {
initOnce sync.Once
defaultConfig *configfile.ConfigFile
buildxConfig *confutil.Config
authConfigCache map[string]authConfigCacheEntry
mu sync.Mutex // mutex for authConfigCache
alternativeConfigs []*alternativeConfig
}

func (ap *authConfigProvider) load(ctx context.Context, host string, scopes []string, cacheExpireCheck authprovider.ExpireCachedAuthCheck) (types.AuthConfig, error) {
ap.initOnce.Do(func() {
ap.init()
})

ap.mu.Lock()
defer ap.mu.Unlock()

candidates := []*alternativeConfig{}
parsedScopes := parseScopes(scopes)

if len(parsedScopes) == 1 {
for _, cfg := range ap.alternativeConfigs {
if cfg.host != host {
continue
}
if cfg.matchesScopes(parsedScopes) {
candidates = append(candidates, cfg)
}
}
}
key := host
cfg := ap.defaultConfig
if len(candidates) > 0 {
// matches with repo before those without repo
// matches with scope set sorted before those without scope
slices.SortFunc(candidates, func(a, b *alternativeConfig) int {
return cmp.Or(
strings.Compare(b.repo, a.repo),
cmp.Compare(len(b.scope), len(a.scope)),
)
})
candidates = candidates[:1]
key += "|" + candidates[0].dir
if candidates[0].configFile == nil {
if cfgDir, err := config.Load(candidates[0].dir); err == nil {
cfg = cfgDir
candidates[0].configFile = cfg
}
} else {
cfg = candidates[0].configFile
}
}

entry, exists := ap.authConfigCache[key]
if exists && (cacheExpireCheck == nil || !cacheExpireCheck(entry.Created, key)) {
return *entry.Auth, nil
}

hostKey := host
if host == authprovider.DockerHubRegistryHost {
hostKey = authprovider.DockerHubConfigfileKey
}

ac, err := cfg.GetAuthConfig(hostKey)
if err != nil {
return types.AuthConfig{}, err
}

entry = authConfigCacheEntry{
Created: time.Now(),
Auth: &ac,
}

ap.authConfigCache[key] = entry

return ac, nil
}

func (ap *authConfigProvider) init() error {
base := filepath.Join(ap.buildxConfig.Dir(), "config")
return filepath.WalkDir(base, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if d.Name() != config.ConfigFileName {
return nil
}
dir := filepath.Dir(path)
rdir, err := filepath.Rel(base, dir)
if err != nil {
return err
}
cfg := parseConfigKey(rdir)
cfg.dir = dir
ap.alternativeConfigs = append(ap.alternativeConfigs, &cfg)
return nil
})
}

func parseConfigKey(key string) alternativeConfig {
var out alternativeConfig

var mainPart, scopePart string
if i := strings.IndexByte(key, '@'); i >= 0 {
mainPart = key[:i]
scopePart = key[i+1:]
} else {
mainPart = key
}

if scopePart != "" {
out.scope = make(map[string]struct{})
for s := range strings.SplitSeq(scopePart, ",") {
if s = strings.TrimSpace(s); s != "" {
out.scope[s] = struct{}{}
}
}
}

if mainPart == "" {
return out
}

slash := strings.IndexByte(mainPart, '/')
if slash < 0 {
out.host = mainPart
return out
}

out.host = mainPart[:slash]
out.repo = mainPart[slash+1:]

return out
}

type alternativeConfig struct {
dir string

host string
repo string
scope map[string]struct{}

configFile *configfile.ConfigFile
}

func (a *alternativeConfig) matchesScopes(q scopes) bool {
if a.repo != "" {
if _, ok := q["repository:"+a.repo]; !ok {
return false
}
}

if len(a.scope) > 0 {
if a.repo == "" {
// no repo means one query must match all scopes
for _, scopeActions := range q {
ok := true
for s := range a.scope {
if _, exists := scopeActions[s]; !exists {
ok = false
break
}
}
if ok {
return true
}
}
return false
}
for s := range a.scope {
for k, scopeActions := range q {
if k == "repository:"+a.repo {
if _, ok := scopeActions[s]; !ok {
return false
}
}
}
}
}

return true
}

type authConfigCacheEntry struct {
Created time.Time
Auth *types.AuthConfig
}

type scopes map[string]map[string]struct{}

func parseScopes(s []string) scopes {
// https://distribution.github.io/distribution/spec/auth/scope/
m := map[string]map[string]struct{}{}
for _, scopeStr := range s {
if scopeStr == "" {
return nil
}
// The scopeStr may have strings that contain multiple scopes separated by a space.
for scope := range strings.SplitSeq(scopeStr, " ") {
parts := strings.SplitN(scope, ":", 3)
names := []string{parts[0]}
if len(parts) > 1 {
names = append(names, parts[1])
}
var actions []string
if len(parts) == 3 {
actions = append(actions, strings.Split(parts[2], ",")...)
}
name := strings.Join(names, ":")
ma, ok := m[name]
if !ok {
ma = map[string]struct{}{}
m[name] = ma
}

for _, a := range actions {
ma[a] = struct{}{}
}
}
}
return m
}
Loading