Skip to content
Open
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
23 changes: 18 additions & 5 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 33 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakePrintKeepEscaped,
testBakePrintRemoteContextSubdir,
testBakeLocal,
testBakeNamedContextNormalizedName,
testBakeLocalMulti,
testBakeFileRelativePaths,
testBakeLocalExportDeleteMode,
Expand Down Expand Up @@ -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 = <<EOT
FROM scratch
COPY marker /marker
EOT
}

target "default" {
contexts = {
"library/source" = "target:source"
}
dockerfile-inline = <<EOT
FROM scratch
COPY --from=library/source /marker /marker
EOT
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("marker", []byte("marker"), 0600),
)
dirDest := t.TempDir()

out, err := bakeCmd(sb, withDir(dir), withArgs("--set", "default.output=type=local,dest="+dirDest))
require.NoError(t, err, out)
require.FileExists(t, filepath.Join(dirDest, "marker"))
}

func testBakeLocalMulti(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM scratch
Expand Down
14 changes: 11 additions & 3 deletions util/buildflags/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ func ParseContextNames(values []string) (map[string]string, error) {
if len(kv) != 2 {
return nil, errors.Errorf("invalid context value: %s, expected key=value", value)
}
named, err := reference.ParseNormalizedNamed(kv[0])
name, err := NormalizeContextName(kv[0])
if err != nil {
return nil, errors.Wrapf(err, "invalid context name %s", kv[0])
return nil, err
}
name := strings.TrimSuffix(reference.FamiliarString(named), ":latest")
result[name] = kv[1]
}
return result, nil
}

// NormalizeContextName returns the familiar form of a named build context.
func NormalizeContextName(name string) (string, error) {
named, err := reference.ParseNormalizedNamed(name)
if err != nil {
return "", errors.Wrapf(err, "invalid context name %s", name)
}
return strings.TrimSuffix(reference.FamiliarString(named), ":latest"), nil
}
32 changes: 32 additions & 0 deletions util/buildflags/context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package buildflags

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseContextNames(t *testing.T) {
t.Run("normalize", func(t *testing.T) {
contexts, err := ParseContextNames([]string{
"library/golang=one",
"docker.io/library/golang=two",
"example.com/project/image:v1=three",
})
require.NoError(t, err)
require.Equal(t, map[string]string{
"golang": "two",
"example.com/project/image:v1": "three",
}, contexts)
})

t.Run("invalid value", func(t *testing.T) {
_, err := ParseContextNames([]string{"golang"})
require.EqualError(t, err, "invalid context value: golang, expected key=value")
})

t.Run("invalid name", func(t *testing.T) {
_, err := ParseContextNames([]string{"UPPERCASE=value"})
require.ErrorContains(t, err, "invalid context name UPPERCASE")
})
}
Loading