build: handle at symbols in an oci-layout path

The buildx command line will now handle `@` symbols in the
`oci-layout://` path when used with named contexts. Instead of
assuming the `@` symbol is part of the reference digest, it will first
check that it is a valid reference digest. Otherwise, it will assume
it's part of the file path.

Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
Jonathan A. Sternberg
2026-01-12 11:19:51 -06:00
parent 3c9cd19e54
commit 2e76c05592
2 changed files with 78 additions and 7 deletions
+35 -7
View File
@@ -558,12 +558,8 @@ func loadInputs(ctx context.Context, d *driver.DriverHandle, inp *Inputs, pw pro
// handle OCI layout
if localPath, ok := strings.CutPrefix(v.Path, "oci-layout://"); ok {
localPath, dig, hasDigest := strings.Cut(localPath, "@")
localPath, tag, hasTag := strings.Cut(localPath, ":")
if !hasTag {
tag = "latest"
}
if !hasDigest {
localPath, dig, tag := parseOCILayoutPath(localPath)
if dig == "" {
dig, err = resolveDigest(localPath, tag)
if err != nil {
return nil, errors.Wrapf(err, "oci-layout reference %q could not be resolved", v.Path)
@@ -899,7 +895,7 @@ type lazyFileWriter struct {
func (w *lazyFileWriter) Write(p []byte) (int, error) {
if w.file == nil {
if err := os.MkdirAll(filepath.Dir(w.path), 0755); err != nil {
if err := os.MkdirAll(filepath.Dir(w.path), 0o755); err != nil {
return 0, err
}
f, err := os.Create(w.path)
@@ -1017,3 +1013,35 @@ func isActive(ce *client.CacheOptionsEntry) bool {
}
return ce.Attrs["token"] != "" && (ce.Attrs["url"] != "" || ce.Attrs["url_v2"] != "")
}
// parseOCILayoutPath handles the oci-layout url accepted by buildx.
func parseOCILayoutPath(s string) (localPath, dgst, tag string) {
localPath = s
// Look for the digest reference. There might be multiple @ symbols
// in the path and the @ symbol may be part of the path or part of
// the digest. If we find the @ symbol, verify that it's a valid
// digest reference instead of just assuming it is because it
// might be part of the file path.
if i := strings.LastIndex(localPath, "@"); i >= 0 {
after := localPath[i+1:]
if reference.DigestRegexp.MatchString(after) {
localPath, dgst = localPath[:i], after
}
}
// Do the same with the tag. This isn't as necessary since colons
// aren't valid as file paths on Linux/Unix systems, but they are valid
// on Windows systems so we might as well just be safe.
if i := strings.LastIndex(localPath, ":"); i >= 0 {
after := localPath[i+1:]
if reference.TagRegexp.MatchString(after) {
localPath, tag = localPath[:i], after
}
}
if tag == "" {
tag = "latest"
}
return
}
+43
View File
@@ -5,6 +5,7 @@ import (
"github.com/docker/buildx/util/buildflags"
"github.com/moby/buildkit/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -38,3 +39,45 @@ func TestCacheOptions_DerivedVars(t *testing.T) {
},
}, CreateCaches(cacheFrom))
}
func TestParseOCILayoutPath(t *testing.T) {
for _, tt := range []struct {
s string
path string
dgst string
tag string
}{
{
s: "/path/to/oci/layout",
path: "/path/to/oci/layout",
tag: "latest",
},
{
s: "/path/to/oci/layout:1.3",
path: "/path/to/oci/layout",
tag: "1.3",
},
{
s: "/path/to/oci/layout@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
path: "/path/to/oci/layout",
dgst: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
tag: "latest",
},
{
s: "/path/to/oci/@/layout@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
path: "/path/to/oci/@/layout",
dgst: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
tag: "latest",
},
{
s: "/path/to/oci/@/layout",
path: "/path/to/oci/@/layout",
tag: "latest",
},
} {
path, dgst, tag := parseOCILayoutPath(tt.s)
assert.Equal(t, tt.path, path, "comparing path: %s", tt.s)
assert.Equal(t, tt.dgst, dgst, "comparing digest: %s", tt.s)
assert.Equal(t, tt.tag, tag, "comparing tag: %s", tt.s)
}
}