
- Use lease API (previoisly, GC was not supported) - Refactored interfaces for ease of future Docker v1 importer support For usage, please refer to `ctr images import --help`. Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
189 lines
5.3 KiB
Go
189 lines
5.3 KiB
Go
// Package oci provides the importer and the exporter for OCI Image Spec.
|
|
package oci
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/images"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// V1Importer implements OCI Image Spec v1.
|
|
type V1Importer struct {
|
|
// ImageName is preprended to either `:` + OCI ref name or `@` + digest (for anonymous refs).
|
|
// This field is mandatory atm, but may change in the future. maybe ref map[string]string as in moby/moby#33355
|
|
ImageName string
|
|
}
|
|
|
|
var _ images.Importer = &V1Importer{}
|
|
|
|
// Import implements Importer.
|
|
func (oi *V1Importer) Import(ctx context.Context, store content.Store, reader io.Reader) ([]images.Image, error) {
|
|
if oi.ImageName == "" {
|
|
return nil, errors.New("ImageName not set")
|
|
}
|
|
tr := tar.NewReader(reader)
|
|
var imgrecs []images.Image
|
|
foundIndexJSON := false
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hdr.Typeflag != tar.TypeReg && hdr.Typeflag != tar.TypeRegA {
|
|
continue
|
|
}
|
|
hdrName := path.Clean(hdr.Name)
|
|
if hdrName == "index.json" {
|
|
if foundIndexJSON {
|
|
return nil, errors.New("duplicated index.json")
|
|
}
|
|
foundIndexJSON = true
|
|
imgrecs, err = onUntarIndexJSON(tr, oi.ImageName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
continue
|
|
}
|
|
if strings.HasPrefix(hdrName, "blobs/") {
|
|
if err := onUntarBlob(ctx, tr, store, hdrName, hdr.Size); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if !foundIndexJSON {
|
|
return nil, errors.New("no index.json found")
|
|
}
|
|
for _, img := range imgrecs {
|
|
err := setGCRefContentLabels(ctx, store, img.Target)
|
|
if err != nil {
|
|
return imgrecs, err
|
|
}
|
|
}
|
|
// FIXME(AkihiroSuda): set GC labels for unreferrenced blobs (i.e. with unknown media types)?
|
|
return imgrecs, nil
|
|
}
|
|
|
|
func onUntarIndexJSON(r io.Reader, imageName string) ([]images.Image, error) {
|
|
b, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var idx ocispec.Index
|
|
if err := json.Unmarshal(b, &idx); err != nil {
|
|
return nil, err
|
|
}
|
|
var imgrecs []images.Image
|
|
for _, m := range idx.Manifests {
|
|
ref, err := normalizeImageRef(imageName, m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
imgrecs = append(imgrecs, images.Image{
|
|
Name: ref,
|
|
Target: m,
|
|
})
|
|
}
|
|
return imgrecs, nil
|
|
}
|
|
|
|
func normalizeImageRef(imageName string, manifest ocispec.Descriptor) (string, error) {
|
|
digest := manifest.Digest
|
|
if digest == "" {
|
|
return "", errors.Errorf("manifest with empty digest: %v", manifest)
|
|
}
|
|
ociRef := manifest.Annotations[ocispec.AnnotationRefName]
|
|
if ociRef == "" {
|
|
return imageName + "@" + digest.String(), nil
|
|
}
|
|
return imageName + ":" + ociRef, nil
|
|
}
|
|
|
|
func onUntarBlob(ctx context.Context, r io.Reader, store content.Store, name string, size int64) error {
|
|
// name is like "blobs/sha256/deadbeef"
|
|
split := strings.Split(name, "/")
|
|
if len(split) != 3 {
|
|
return errors.Errorf("unexpected name: %q", name)
|
|
}
|
|
algo := digest.Algorithm(split[1])
|
|
if !algo.Available() {
|
|
return errors.Errorf("unsupported algorithm: %s", algo)
|
|
}
|
|
dgst := digest.NewDigestFromHex(algo.String(), split[2])
|
|
return content.WriteBlob(ctx, store, "unknown-"+dgst.String(), r, size, dgst)
|
|
}
|
|
|
|
// GetChildrenDescriptors returns children blob descriptors for the following supported types:
|
|
// - images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest
|
|
// - images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex
|
|
func GetChildrenDescriptors(r io.Reader, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
switch desc.MediaType {
|
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
var manifest ocispec.Manifest
|
|
if err := json.NewDecoder(r).Decode(&manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
return append([]ocispec.Descriptor{manifest.Config}, manifest.Layers...), nil
|
|
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
var index ocispec.Index
|
|
if err := json.NewDecoder(r).Decode(&index); err != nil {
|
|
return nil, err
|
|
}
|
|
return index.Manifests, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func setGCRefContentLabels(ctx context.Context, store content.Store, desc ocispec.Descriptor) error {
|
|
info, err := store.Info(ctx, desc.Digest)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
// when the archive is created from multi-arch image,
|
|
// it may contain only blobs for a certain platform.
|
|
// So ErrNotFound (on manifest list) is expected here.
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
ra, err := store.ReaderAt(ctx, desc.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer ra.Close()
|
|
r := content.NewReader(ra)
|
|
children, err := GetChildrenDescriptors(r, desc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Labels == nil {
|
|
info.Labels = map[string]string{}
|
|
}
|
|
for i, child := range children {
|
|
// Note: child blob is not guaranteed to be written to the content store. (multi-arch)
|
|
info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = child.Digest.String()
|
|
}
|
|
if _, err := store.Update(ctx, info, "labels"); err != nil {
|
|
return err
|
|
}
|
|
for _, child := range children {
|
|
if err := setGCRefContentLabels(ctx, store, child); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|