1
0
mirror of https://github.com/moby/buildkit.git synced 2025-08-01 02:04:26 +03:00
Files
buildkit/source/containerimage/ocilayout.go
Tonis Tiigi f1f56c2b77 golangci-lint: v2 upgrade
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2025-05-02 12:19:17 -07:00

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)
})
}