From 70682b043e784a112229deb5c484a226c1c48109 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Tue, 6 Dec 2022 18:15:09 +0000 Subject: [PATCH 1/2] 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 4143bc0d..96578aaf 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" @@ -1478,26 +1481,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) From 1885e4178994194105d36e1594bf79e32db9b60d Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Thu, 5 Jan 2023 13:47:04 +0000 Subject: [PATCH 2/2] docs: update oci layout with tag resolution Signed-off-by: Justin Chadwell --- docs/reference/buildx_build.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/reference/buildx_build.md b/docs/reference/buildx_build.md index ebc02a4d..91d4ab43 100644 --- a/docs/reference/buildx_build.md +++ b/docs/reference/buildx_build.md @@ -136,10 +136,12 @@ COPY --from=project myfile / #### Source image from OCI layout directory -Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md): +Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md), +either by tag, or by digest: ```console -$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256:abcd12345 . +$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout: +$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256: ``` ```dockerfile @@ -151,14 +153,8 @@ COPY --from=foo myfile / FROM foo ``` -The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md). It looks _solely_ for hashes. It does not -do any form of `image:tag` resolution to find the hash of the manifest; that is up to you. - -The format of the `--build-context` must be: `=oci-layout://@sha256:`, where: - -* `context` is the name of the build context as used in the `Dockerfile`. -* `path-to-local-layout` is the path on the local machine, where you are running `docker build`, to the spec-compliant OCI layout. -* `hash-of-manifest` is the hash of the manifest for the image. It can be a single-architecture manifest or a multi-architecture index. +The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md). +You can reference an image in the layout using either tags, or the exact digest. ### Override the configured builder instance (--builder)