mirror of
https://github.com/moby/buildkit.git
synced 2025-08-01 02:04:26 +03:00
141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
package containerimage
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/v2/core/content"
|
|
"github.com/containerd/containerd/v2/core/remotes"
|
|
"github.com/containerd/containerd/v2/pkg/reference"
|
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
|
"github.com/moby/buildkit/session"
|
|
sessioncontent "github.com/moby/buildkit/session/content"
|
|
"github.com/moby/buildkit/util/imageutil"
|
|
"github.com/moby/buildkit/util/iohelper"
|
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
maxReadSize = 4 * 1024 * 1024
|
|
)
|
|
|
|
// getOCILayoutResolver gets a resolver to an OCI layout for a specified store from the client using the given session.
|
|
func getOCILayoutResolver(store sourceresolver.ResolveImageConfigOptStore, sm *session.Manager, g session.Group) *ociLayoutResolver {
|
|
r := &ociLayoutResolver{
|
|
store: store,
|
|
sm: sm,
|
|
g: g,
|
|
}
|
|
return r
|
|
}
|
|
|
|
type ociLayoutResolver struct {
|
|
remotes.Resolver
|
|
store sourceresolver.ResolveImageConfigOptStore
|
|
sm *session.Manager
|
|
g session.Group
|
|
}
|
|
|
|
// Fetcher returns a new fetcher for the provided reference.
|
|
func (r *ociLayoutResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) {
|
|
return r, nil
|
|
}
|
|
|
|
// Fetch get an io.ReadCloser for the specific content
|
|
func (r *ociLayoutResolver) Fetch(ctx context.Context, desc ocispecs.Descriptor) (io.ReadCloser, error) {
|
|
var rc io.ReadCloser
|
|
err := r.withCaller(ctx, func(ctx context.Context, caller session.Caller) error {
|
|
store := sessioncontent.NewCallerStore(caller, "oci:"+r.store.StoreID)
|
|
readerAt, err := store.ReaderAt(ctx, desc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rc = iohelper.ReadCloser(readerAt)
|
|
return nil
|
|
})
|
|
return rc, err
|
|
}
|
|
|
|
// Resolve attempts to resolve the reference into a name and descriptor.
|
|
// OCI Layout does not (yet) support tag name references, but does support hash references.
|
|
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 %q", refString)
|
|
}
|
|
dgst := ref.Digest()
|
|
if dgst == "" {
|
|
return "", ocispecs.Descriptor{}, errors.Errorf("reference %q must have digest", refString)
|
|
}
|
|
|
|
info, err := r.info(ctx, ref)
|
|
if err != nil {
|
|
return "", ocispecs.Descriptor{}, errors.Wrap(err, "unable to get info about digest")
|
|
}
|
|
|
|
// Create the descriptor, then use that to read the actual root manifest/
|
|
// 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: info.Digest,
|
|
Size: info.Size,
|
|
}
|
|
rc, err := r.Fetch(ctx, desc)
|
|
if err != nil {
|
|
return "", ocispecs.Descriptor{}, errors.Wrap(err, "unable to get root manifest")
|
|
}
|
|
b, err := io.ReadAll(io.LimitReader(rc, maxReadSize))
|
|
if err != nil {
|
|
return "", ocispecs.Descriptor{}, errors.Wrap(err, "unable to read root manifest")
|
|
}
|
|
|
|
mediaType, err := imageutil.DetectManifestBlobMediaType(b)
|
|
if err != nil {
|
|
return "", ocispecs.Descriptor{}, errors.Wrapf(err, "reference %q contains neither an index nor a manifest", refString)
|
|
}
|
|
desc.MediaType = mediaType
|
|
|
|
return refString, desc, nil
|
|
}
|
|
|
|
func (r *ociLayoutResolver) info(ctx context.Context, ref reference.Spec) (content.Info, error) {
|
|
var info *content.Info
|
|
err := r.withCaller(ctx, func(ctx context.Context, caller session.Caller) error {
|
|
store := sessioncontent.NewCallerStore(caller, "oci:"+r.store.StoreID)
|
|
|
|
dgst := ref.Digest()
|
|
if dgst == "" {
|
|
return errors.Errorf("reference %q does not contain a digest", ref.String())
|
|
}
|
|
in, err := store.Info(ctx, dgst)
|
|
info = &in
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return content.Info{}, err
|
|
}
|
|
if info == nil {
|
|
return content.Info{}, errors.Errorf("reference %q did not match any content", ref.String())
|
|
}
|
|
return *info, nil
|
|
}
|
|
|
|
func (r *ociLayoutResolver) withCaller(ctx context.Context, f func(context.Context, session.Caller) error) error {
|
|
if r.store.SessionID != "" {
|
|
timeoutCtx, cancel := context.WithCancelCause(ctx)
|
|
timeoutCtx, _ = context.WithTimeoutCause(timeoutCtx, 5*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet
|
|
defer func() { cancel(errors.WithStack(context.Canceled)) }()
|
|
|
|
caller, err := r.sm.Get(timeoutCtx, r.store.SessionID, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return f(ctx, caller)
|
|
}
|
|
return r.sm.Any(ctx, r.g, func(ctx context.Context, _ string, caller session.Caller) error {
|
|
return f(ctx, caller)
|
|
})
|
|
}
|