From adc30fac103ff9e99728cfa42795a9f7f8562cfd Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:26:55 +0200 Subject: [PATCH] bake: normalize named context keys Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- bake/bake.go | 23 ++++++++++++---- bake/bake_test.go | 47 +++++++++++++++++++++++++++++++++ tests/bake.go | 33 +++++++++++++++++++++++ util/buildflags/context.go | 14 +++++++--- util/buildflags/context_test.go | 32 ++++++++++++++++++++++ 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 util/buildflags/context_test.go diff --git a/bake/bake.go b/bake/bake.go index 290e8670d590..cfef773121ea 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -1667,10 +1667,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { dockerfilePath = path.Clean(dockerfilePath) } + namedContexts, err := toNamedContexts(t.Contexts) + if err != nil { + return nil, err + } bi := build.Inputs{ ContextPath: contextPath, DockerfilePath: dockerfilePath, - NamedContexts: toNamedContexts(t.Contexts), + NamedContexts: namedContexts, } if t.DockerfileInline != nil { bi.DockerfileInline = *t.DockerfileInline @@ -1982,12 +1986,21 @@ func isSubset(s1, s2 []string) bool { return true } -func toNamedContexts(m map[string]string) map[string]build.NamedContext { +func toNamedContexts(m map[string]string) (map[string]build.NamedContext, error) { m2 := make(map[string]build.NamedContext, len(m)) - for k, v := range m { - m2[k] = build.NamedContext{Path: v} + rawNames := make(map[string]string, len(m)) + for _, k := range slices.Sorted(maps.Keys(m)) { + name, err := buildflags.NormalizeContextName(k) + if err != nil { + return nil, err + } + if prev, ok := rawNames[name]; ok { + return nil, errors.Errorf("context names %q and %q normalize to the same name %q", prev, k, name) + } + rawNames[name] = k + m2[name] = build.NamedContext{Path: m[k]} } - return m2 + return m2, nil } type arrValue[B any] interface { diff --git a/bake/bake_test.go b/bake/bake_test.go index 10a29f1db334..5b07ea0f1a48 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -1140,6 +1140,53 @@ func TestReadContexts(t *testing.T) { require.Equal(t, "def", ctxs["abc"].Path) } +func TestReadContextsNormalizedNames(t *testing.T) { + for _, name := range []string{"library/golang", "docker.io/library/golang"} { + t.Run(name, func(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: fmt.Appendf(nil, ` + target "app" { + contexts = { + %q = "docker-image://library/golang:1.22" + } + } + `, name), + } + + ctx := context.TODO() + m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil, nil, &EntitlementConf{}) + require.NoError(t, err) + + bo, err := TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Len(t, bo["app"].Inputs.NamedContexts, 1) + require.Equal(t, "docker-image://library/golang:1.22", bo["app"].Inputs.NamedContexts["golang"].Path) + }) + } +} + +func TestReadContextsDuplicateNormalizedNames(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte(` + target "app" { + contexts = { + "golang" = "docker-image://library/golang:1.21" + "library/golang" = "docker-image://library/golang:1.22" + } + } + `), + } + + ctx := context.TODO() + m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil, nil, &EntitlementConf{}) + require.NoError(t, err) + + _, err = TargetsToBuildOpt(m, &Input{}) + require.EqualError(t, err, `context names "golang" and "library/golang" normalize to the same name "golang"`) +} + func TestReadContextFromTargetUnknown(t *testing.T) { fp := File{ Name: "docker-bake.hcl", diff --git a/tests/bake.go b/tests/bake.go index 279bc3c1143e..ec465276d5d7 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -45,6 +45,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakePrintKeepEscaped, testBakePrintRemoteContextSubdir, testBakeLocal, + testBakeNamedContextNormalizedName, testBakeLocalMulti, testBakeFileRelativePaths, testBakeLocalExportDeleteMode, @@ -683,6 +684,38 @@ target "default" { require.FileExists(t, filepath.Join(dirDest, "foo")) } +// https://github.com/docker/buildx/issues/2328 +func testBakeNamedContextNormalizedName(t *testing.T, sb integration.Sandbox) { + bakefile := []byte(` +target "source" { + dockerfile-inline = <