mirror of
https://github.com/moby/buildkit.git
synced 2026-08-02 22:09:32 +00:00
source: refactor reference parsing for oci-layout
Instead of using custom parsing mechansisms for references in oci-layout, we use containerd's reference.Parse or docker distribution's reference.Parse (depending on where we do the parsing, and what's consistent with the file where it's already done). These operations are neater than manually parsing, and have hopefully more consistent error messages, and better handling of labels (for if/when those are introduced). Signed-off-by: Justin Chadwell <me@jedevc.com>
This commit is contained in:
@@ -455,7 +455,7 @@ func Differ(t DiffType, required bool) LocalOption {
|
||||
})
|
||||
}
|
||||
|
||||
func OCILayout(contentStoreID string, dig digest.Digest, opts ...OCILayoutOption) State {
|
||||
func OCILayout(store string, digest digest.Digest, opts ...OCILayoutOption) State {
|
||||
gi := &OCILayoutInfo{}
|
||||
|
||||
for _, o := range opts {
|
||||
@@ -474,7 +474,7 @@ func OCILayout(contentStoreID string, dig digest.Digest, opts ...OCILayoutOption
|
||||
|
||||
addCap(&gi.Constraints, pb.CapSourceOCILayout)
|
||||
|
||||
source := NewSource(fmt.Sprintf("oci-layout://%s@%s", contentStoreID, dig), attrs, gi.Constraints)
|
||||
source := NewSource(fmt.Sprintf("oci-layout://%s@%s", store, digest), attrs, gi.Constraints)
|
||||
return NewState(source.Output())
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/util/gitutil"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -956,30 +955,30 @@ func contextByName(ctx context.Context, c client.Client, sessionID, name string,
|
||||
}
|
||||
return st, nil, nil
|
||||
case "oci-layout":
|
||||
ref := strings.TrimPrefix(vv[1], "//")
|
||||
// expected format is storeID@hash
|
||||
parts := strings.SplitN(ref, "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, nil, errors.Errorf("invalid oci-layout format '%s', must be oci-layout:///content-store@sha256:digest", vv[1])
|
||||
}
|
||||
storeID := parts[0]
|
||||
dig, err := digest.Parse(parts[1])
|
||||
refSpec := strings.TrimPrefix(vv[1], "//")
|
||||
ref, err := reference.Parse(refSpec)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Errorf("invalid digest format '%s', must be oci-layout:///content-store@sha256:digest", vv[1])
|
||||
return nil, nil, errors.Wrapf(err, "could not parse oci-layout reference %q", refSpec)
|
||||
}
|
||||
named, ok := ref.(reference.Named)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q has no name", ref.String())
|
||||
}
|
||||
if reference.Domain(named) != "" {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q has domain", ref.String())
|
||||
}
|
||||
if strings.Contains(reference.Path(named), "/") {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q name is multi-part", ref.String())
|
||||
}
|
||||
digested, ok := ref.(reference.Digested)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("oci-layout reference %q does not have digest", ref.String())
|
||||
}
|
||||
|
||||
// the ref now is "content-store@sha256:digest"
|
||||
// ResolveImageConfig will try to treat that as a valid reference,
|
||||
// to be parsed with https://pkg.go.dev/github.com/containerd/containerd@v1.6.6/reference#Parse
|
||||
// That will fail, because it will think that "content-store@sha256" is a host and "digest" is a port.
|
||||
// To get it to pass, we need to jury-rig it with a host, so that it is a legitimate ref and can
|
||||
// be processed.
|
||||
// A reasonable format is storeID as host, so
|
||||
// storeID/image@digest
|
||||
// We do not support any image lookup for now, so any image will do; it is ignored.
|
||||
|
||||
usableRef := fmt.Sprintf("%s/%s@%s", storeID, "image", dig)
|
||||
_, data, err := c.ResolveImageConfig(ctx, usableRef, llb.ResolveImageConfigOpt{
|
||||
// We use store id as the host here, the image name will be ignored
|
||||
// (since image lookup is not currently supported)
|
||||
id := fmt.Sprintf("%s/image@%s", named.Name(), digested.Digest())
|
||||
_, data, err := c.ResolveImageConfig(ctx, id, llb.ResolveImageConfigOpt{
|
||||
Platform: platform,
|
||||
ResolveMode: resolveMode,
|
||||
LogName: fmt.Sprintf("[context %s] load metadata for %s", name, ref),
|
||||
@@ -991,10 +990,12 @@ func contextByName(ctx context.Context, c client.Client, sessionID, name string,
|
||||
|
||||
var img dockerfile2llb.Image
|
||||
if err := json.Unmarshal(data, &img); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, errors.Wrap(err, "could not parse oci-layout image config")
|
||||
}
|
||||
|
||||
st := llb.OCILayout(storeID, dig,
|
||||
st := llb.OCILayout(
|
||||
named.Name(),
|
||||
digested.Digest(),
|
||||
llb.WithCustomName("[context "+name+"] OCI load from client"),
|
||||
llb.OCISessionID(c.BuildOpts().SessionID),
|
||||
)
|
||||
|
||||
@@ -63,12 +63,11 @@ func (r *ociLayoutResolver) Fetch(ctx context.Context, desc ocispecs.Descriptor)
|
||||
func (r *ociLayoutResolver) Resolve(ctx context.Context, refString string) (string, ocispecs.Descriptor, error) {
|
||||
ref, err := reference.Parse(refString)
|
||||
if err != nil {
|
||||
return "", ocispecs.Descriptor{}, errors.Wrapf(err, "invalid reference '%s'", refString)
|
||||
return "", ocispecs.Descriptor{}, errors.Wrapf(err, "invalid reference %q", refString)
|
||||
}
|
||||
|
||||
dig := ref.Digest()
|
||||
if dig == "" {
|
||||
return "", ocispecs.Descriptor{}, errors.Errorf("reference must have format @sha256:<hash>: %s", refString)
|
||||
dgst := ref.Digest()
|
||||
if dgst == "" {
|
||||
return "", ocispecs.Descriptor{}, errors.Errorf("reference %q must have digest", refString)
|
||||
}
|
||||
|
||||
info, err := r.info(ctx, ref)
|
||||
@@ -80,7 +79,7 @@ func (r *ociLayoutResolver) Resolve(ctx context.Context, refString string) (stri
|
||||
// This is necessary because we do not know the media-type of the descriptor,
|
||||
// and there are descriptor processing elements that expect it.
|
||||
desc := ocispecs.Descriptor{
|
||||
Digest: dig,
|
||||
Digest: info.Digest,
|
||||
Size: info.Size,
|
||||
}
|
||||
rc, err := r.Fetch(ctx, desc)
|
||||
@@ -91,12 +90,13 @@ func (r *ociLayoutResolver) Resolve(ctx context.Context, refString string) (stri
|
||||
if err != nil {
|
||||
return "", ocispecs.Descriptor{}, errors.Wrap(err, "unable to read root manifest")
|
||||
}
|
||||
// try it first as an index, then as a manifest
|
||||
|
||||
mediaType, err := imageutil.DetectManifestBlobMediaType(b)
|
||||
if err != nil {
|
||||
return "", ocispecs.Descriptor{}, errors.Wrapf(err, "reference %s contains neither an index nor a manifest", refString)
|
||||
return "", ocispecs.Descriptor{}, errors.Wrapf(err, "reference %q contains neither an index nor a manifest", refString)
|
||||
}
|
||||
desc.MediaType = mediaType
|
||||
|
||||
return refString, desc, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -106,19 +106,13 @@ func (is *Source) ResolveImageConfig(ctx context.Context, ref string, opt llb.Re
|
||||
}
|
||||
rslvr = resolver.DefaultPool.GetResolver(is.RegistryHosts, ref, "pull", sm, g).WithImageStore(is.ImageStore, rm)
|
||||
case ResolverTypeOCILayout:
|
||||
// with OCI layout, we always just "pull"
|
||||
rm = source.ResolveModeForcePull
|
||||
// get the content store ID from the ref
|
||||
|
||||
parsed, err := reference.Parse(ref)
|
||||
if err != nil {
|
||||
return "", nil, errors.Errorf("invalid oci-layout ref format '%s', must be content-store/image@sha256:digest", ref)
|
||||
return "", nil, err
|
||||
}
|
||||
if parsed.Digest() == "" {
|
||||
return "", nil, errors.Errorf("oci-layout ref format '%s' missing digest, must be content-store/image@sha256:digest", ref)
|
||||
}
|
||||
storeID := parsed.Hostname()
|
||||
|
||||
rslvr = getOCILayoutResolver(storeID, sm, "", g)
|
||||
rslvr = getOCILayoutResolver(parsed.Hostname(), sm, "", g)
|
||||
}
|
||||
key += rm.String()
|
||||
res, err := is.g.Do(ctx, key, func(ctx context.Context) (interface{}, error) {
|
||||
@@ -172,7 +166,7 @@ func (is *Source) Resolve(ctx context.Context, id source.Identifier, sm *session
|
||||
}
|
||||
mode = source.ResolveModeForcePull // with OCI layout, we always just "pull"
|
||||
sessionID = ociIdentifier.SessionID
|
||||
ref = reference.Spec{Locator: "oci-layout/dummy", Object: "@" + ociIdentifier.Manifest.String()}
|
||||
ref = reference.Spec{Locator: "oci-layout/dummy", Object: "@" + ociIdentifier.Digest.String()}
|
||||
storeID = ociIdentifier.Name
|
||||
layerLimit = ociIdentifier.LayerLimit
|
||||
default:
|
||||
|
||||
@@ -292,23 +292,22 @@ func (*HTTPIdentifier) ID() string {
|
||||
|
||||
type OCIIdentifier struct {
|
||||
Name string
|
||||
Manifest digest.Digest
|
||||
Digest digest.Digest
|
||||
Platform *ocispecs.Platform
|
||||
SessionID string
|
||||
LayerLimit *int
|
||||
}
|
||||
|
||||
func NewOCIIdentifier(str string) (*OCIIdentifier, error) {
|
||||
// OCI identifier arg is of the format: path@hash
|
||||
parts := strings.SplitN(str, "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("OCI must be in format of storeID@manifest-hash")
|
||||
store, digSrc, found := strings.Cut(str, "@")
|
||||
if !found {
|
||||
return nil, errors.Errorf("invalid OCI identifier %s", str)
|
||||
}
|
||||
dig, err := digest.Parse(parts[1])
|
||||
dig, err := digest.Parse(digSrc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "OCI must be in format of storeID@manifest-hash, invalid digest")
|
||||
return nil, errors.Wrapf(err, "invalid digest in OCI identifier %s", str)
|
||||
}
|
||||
return &OCIIdentifier{Name: parts[0], Manifest: dig}, nil
|
||||
return &OCIIdentifier{Name: store, Digest: dig}, nil
|
||||
}
|
||||
|
||||
func (*OCIIdentifier) ID() string {
|
||||
|
||||
Reference in New Issue
Block a user