From 8b960ededd3981de8699de02d8df73110d97c579 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 6 Dec 2022 18:15:09 +0000 Subject: [PATCH] build: refactor reference parsing for image layouts We allow any valid image reference format for the oci-layout, not just limiting to name@digest, we additionally allow images of the form name:tag@digest now. The name of the reference is used to find the local directory to lookup the store in, while the tag and digest are attached to a random identity to generate the dummy reference sent to the oci-layout context. This separation of the target to replace and the value to replace it with ensures that any tag or digest set in the client is properly sent across to the server. The tag is used when a digest was not specified, and it is resolved in the context of the local directory before being sent, using the same helpers as we use for the local cache expoter. Signed-off-by: Justin Chadwell --- build/build.go | 54 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/build/build.go b/build/build.go index 58b75141..d2a108b5 100644 --- a/build/build.go +++ b/build/build.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "strconv" "strings" @@ -36,8 +37,10 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/client/ociindex" "github.com/moby/buildkit/exporter/containerimage/exptypes" gateway "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/upload/uploadprovider" "github.com/moby/buildkit/solver/errdefs" @@ -1497,26 +1500,59 @@ func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Wr // handle OCI layout if strings.HasPrefix(v.Path, "oci-layout://") { pathAlone := strings.TrimPrefix(v.Path, "oci-layout://") - parts := strings.SplitN(pathAlone, "@", 2) - if len(parts) != 2 { - return nil, errors.Errorf("invalid oci-layout context %s, must be oci-layout:///path/to/layout@sha256:hash", v.Path) + localPath := pathAlone + localPath, dig, hasDigest := strings.Cut(localPath, "@") + localPath, tag, hasTag := strings.Cut(localPath, ":") + if !hasDigest { + indexPath := path.Join(localPath, "index.json") + index, err := ociindex.ReadIndexJSONFileLocked(indexPath) + if err != nil { + return nil, errors.Wrapf(err, "failed to read oci-layout index at %s", indexPath) + } + + if len(index.Manifests) == 1 { + dig = string(index.Manifests[0].Digest) + hasDigest = true + } + + if !hasTag { + tag = "latest" + } + for _, m := range index.Manifests { + if m.Annotations[specs.AnnotationRefName] == tag { + dig = string(m.Digest) + hasDigest = true + break + } + } + } + if !hasDigest { + return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path) } - localPath := parts[0] - dgst, err := digest.Parse(parts[1]) + _, err := digest.Parse(dig) if err != nil { - return nil, errors.Wrapf(err, "invalid oci-layout context %s, does not have proper hash, must be oci-layout:///path/to/layout@sha256:hash", v.Path) + return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig) } + store, err := local.NewStore(localPath) if err != nil { return nil, errors.Wrapf(err, "invalid store at %s", localPath) } - // now we can add it + storeName := identity.NewID() if target.OCIStores == nil { target.OCIStores = map[string]content.Store{} } - target.OCIStores[k] = store + target.OCIStores[storeName] = store + + layout := "oci-layout://" + storeName + if hasTag { + layout += ":" + tag + } + if hasDigest { + layout += "@" + dig + } - target.FrontendAttrs["context:"+k] = fmt.Sprintf("oci-layout:%s@%s", k, dgst.String()) + target.FrontendAttrs["context:"+k] = layout continue } st, err := os.Stat(v.Path)