140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package imageutil
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/images"
|
|
"github.com/containerd/containerd/platforms"
|
|
"github.com/containerd/containerd/reference"
|
|
"github.com/containerd/containerd/remotes"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type IngesterProvider interface {
|
|
content.Ingester
|
|
content.Provider
|
|
}
|
|
|
|
func Config(ctx context.Context, str string, resolver remotes.Resolver, ingester IngesterProvider) (digest.Digest, []byte, error) {
|
|
ref, err := reference.Parse(str)
|
|
if err != nil {
|
|
return "", nil, errors.WithStack(err)
|
|
}
|
|
|
|
dgst := ref.Digest()
|
|
var desc *ocispec.Descriptor
|
|
if dgst != "" {
|
|
ra, err := ingester.ReaderAt(ctx, dgst)
|
|
if err == nil {
|
|
mt, err := DetectManifestMediaType(ra)
|
|
if err == nil {
|
|
desc = &ocispec.Descriptor{
|
|
Size: ra.Size(),
|
|
Digest: dgst,
|
|
MediaType: mt,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if desc == nil {
|
|
_, desc2, err := resolver.Resolve(ctx, ref.String())
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
desc = &desc2
|
|
}
|
|
|
|
fetcher, err := resolver.Fetcher(ctx, ref.String())
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
handlers := []images.Handler{
|
|
remotes.FetchHandler(ingester, fetcher),
|
|
childrenConfigHandler(ingester),
|
|
}
|
|
if err := images.Dispatch(ctx, images.Handlers(handlers...), *desc); err != nil {
|
|
return "", nil, err
|
|
}
|
|
config, err := images.Config(ctx, ingester, *desc, platforms.Default())
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
dt, err := content.ReadBlob(ctx, ingester, config.Digest)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
return desc.Digest, dt, nil
|
|
}
|
|
|
|
func childrenConfigHandler(provider content.Provider) images.HandlerFunc {
|
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
var descs []ocispec.Descriptor
|
|
switch desc.MediaType {
|
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(stevvooe): We just assume oci manifest, for now. There may be
|
|
// subtle differences from the docker version.
|
|
var manifest ocispec.Manifest
|
|
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs = append(descs, manifest.Config)
|
|
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var index ocispec.Index
|
|
if err := json.Unmarshal(p, &index); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs = append(descs, index.Manifests...)
|
|
case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
|
|
// childless data types.
|
|
return nil, nil
|
|
default:
|
|
return nil, errors.Errorf("encountered unknown type %v; children may not be fetched", desc.MediaType)
|
|
}
|
|
|
|
return descs, nil
|
|
}
|
|
}
|
|
|
|
// ocispec.MediaTypeImageManifest, // TODO: detect schema1/manifest-list
|
|
func DetectManifestMediaType(ra content.ReaderAt) (string, error) {
|
|
// TODO: schema1
|
|
|
|
p := make([]byte, ra.Size())
|
|
if _, err := ra.ReadAt(p, 0); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var mfst struct {
|
|
Config json.RawMessage `json:"config"`
|
|
}
|
|
|
|
if err := json.Unmarshal(p, &mfst); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if mfst.Config != nil {
|
|
return images.MediaTypeDockerSchema2Manifest, nil
|
|
}
|
|
return images.MediaTypeDockerSchema2ManifestList, nil
|
|
}
|