github.com/wozhu6104/docker@v20.10.10+incompatible/distribution/manifest.go (about)

     1  package distribution
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"io/ioutil"
     8  
     9  	"github.com/containerd/containerd/content"
    10  	"github.com/containerd/containerd/errdefs"
    11  	"github.com/containerd/containerd/log"
    12  	"github.com/containerd/containerd/remotes"
    13  	"github.com/docker/distribution"
    14  	"github.com/docker/distribution/manifest/schema1"
    15  	digest "github.com/opencontainers/go-digest"
    16  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // This is used by manifestStore to pare down the requirements to implement a
    21  // full distribution.ManifestService, since `Get` is all we use here.
    22  type manifestGetter interface {
    23  	Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error)
    24  }
    25  
    26  type manifestStore struct {
    27  	local  ContentStore
    28  	remote manifestGetter
    29  }
    30  
    31  // ContentStore is the interface used to persist registry blobs
    32  //
    33  // Currently this is only used to persist manifests and manifest lists.
    34  // It is exported because `distribution.Pull` takes one as an argument.
    35  type ContentStore interface {
    36  	content.Ingester
    37  	content.Provider
    38  	Info(ctx context.Context, dgst digest.Digest) (content.Info, error)
    39  	Abort(ctx context.Context, ref string) error
    40  }
    41  
    42  func (m *manifestStore) getLocal(ctx context.Context, desc specs.Descriptor) (distribution.Manifest, error) {
    43  	ra, err := m.local.ReaderAt(ctx, desc)
    44  	if err != nil {
    45  		return nil, errors.Wrap(err, "error getting content store reader")
    46  	}
    47  	defer ra.Close()
    48  
    49  	r := io.NewSectionReader(ra, 0, ra.Size())
    50  	data, err := ioutil.ReadAll(r)
    51  	if err != nil {
    52  		return nil, errors.Wrap(err, "error reading manifest from content store")
    53  	}
    54  
    55  	manifest, _, err := distribution.UnmarshalManifest(desc.MediaType, data)
    56  	if err != nil {
    57  		return nil, errors.Wrap(err, "error unmarshaling manifest from content store")
    58  	}
    59  	return manifest, nil
    60  }
    61  
    62  func (m *manifestStore) getMediaType(ctx context.Context, desc specs.Descriptor) (string, error) {
    63  	ra, err := m.local.ReaderAt(ctx, desc)
    64  	if err != nil {
    65  		return "", errors.Wrap(err, "error getting reader to detect media type")
    66  	}
    67  	defer ra.Close()
    68  
    69  	mt, err := detectManifestMediaType(ra)
    70  	if err != nil {
    71  		return "", errors.Wrap(err, "error detecting media type")
    72  	}
    73  	return mt, nil
    74  }
    75  
    76  func (m *manifestStore) Get(ctx context.Context, desc specs.Descriptor) (distribution.Manifest, error) {
    77  	l := log.G(ctx)
    78  
    79  	if desc.MediaType == "" {
    80  		// When pulling by digest we will not have the media type on the
    81  		// descriptor since we have not made a request to the registry yet
    82  		//
    83  		// We already have the digest, so we only lookup locally... by digest.
    84  		//
    85  		// Let's try to detect the media type so we can have a good ref key
    86  		// here. We may not even have the content locally, and this is fine, but
    87  		// if we do we should determine that.
    88  		mt, err := m.getMediaType(ctx, desc)
    89  		if err != nil && !errdefs.IsNotFound(err) {
    90  			l.WithError(err).Warn("Error looking up media type of content")
    91  		}
    92  		desc.MediaType = mt
    93  	}
    94  
    95  	key := remotes.MakeRefKey(ctx, desc)
    96  
    97  	// Here we open a writer to the requested content. This both gives us a
    98  	// reference to write to if indeed we need to persist it and increments the
    99  	// ref count on the content.
   100  	w, err := m.local.Writer(ctx, content.WithDescriptor(desc), content.WithRef(key))
   101  	if err != nil {
   102  		if errdefs.IsAlreadyExists(err) {
   103  			var manifest distribution.Manifest
   104  			if manifest, err = m.getLocal(ctx, desc); err == nil {
   105  				return manifest, nil
   106  			}
   107  		}
   108  		// always fallback to the remote if there is an error with the local store
   109  	}
   110  	if w != nil {
   111  		defer w.Close()
   112  	}
   113  
   114  	l.WithError(err).Debug("Fetching manifest from remote")
   115  
   116  	manifest, err := m.remote.Get(ctx, desc.Digest)
   117  	if err != nil {
   118  		if err := m.local.Abort(ctx, key); err != nil {
   119  			l.WithError(err).Warn("Error while attempting to abort content ingest")
   120  		}
   121  		return nil, err
   122  	}
   123  
   124  	if w != nil {
   125  		// if `w` is nil here, something happened with the content store, so don't bother trying to persist.
   126  		if err := m.Put(ctx, manifest, desc, w); err != nil {
   127  			if err := m.local.Abort(ctx, key); err != nil {
   128  				l.WithError(err).Warn("error aborting content ingest")
   129  			}
   130  			l.WithError(err).Warn("Error persisting manifest")
   131  		}
   132  	}
   133  	return manifest, nil
   134  }
   135  
   136  func (m *manifestStore) Put(ctx context.Context, manifest distribution.Manifest, desc specs.Descriptor, w content.Writer) error {
   137  	mt, payload, err := manifest.Payload()
   138  	if err != nil {
   139  		return err
   140  	}
   141  	desc.Size = int64(len(payload))
   142  	desc.MediaType = mt
   143  
   144  	if _, err = w.Write(payload); err != nil {
   145  		return errors.Wrap(err, "error writing manifest to content store")
   146  	}
   147  
   148  	if err := w.Commit(ctx, desc.Size, desc.Digest); err != nil {
   149  		return errors.Wrap(err, "error committing manifest to content store")
   150  	}
   151  	return nil
   152  }
   153  
   154  func detectManifestMediaType(ra content.ReaderAt) (string, error) {
   155  	dt := make([]byte, ra.Size())
   156  	if _, err := ra.ReadAt(dt, 0); err != nil {
   157  		return "", err
   158  	}
   159  
   160  	return detectManifestBlobMediaType(dt)
   161  }
   162  
   163  // This is used when the manifest store does not know the media type of a sha it
   164  // was told to get. This would currently only happen when pulling by digest.
   165  // The media type is needed so the blob can be unmarshalled properly.
   166  func detectManifestBlobMediaType(dt []byte) (string, error) {
   167  	var mfst struct {
   168  		MediaType string          `json:"mediaType"`
   169  		Config    json.RawMessage `json:"config"`   // schema2 Manifest
   170  		FSLayers  json.RawMessage `json:"fsLayers"` // schema1 Manifest
   171  	}
   172  
   173  	if err := json.Unmarshal(dt, &mfst); err != nil {
   174  		return "", err
   175  	}
   176  
   177  	// We may have a media type specified in the json, in which case that should be used.
   178  	// Docker types should generally have a media type set.
   179  	// OCI (golang) types do not have a `mediaType` defined, and it is optional in the spec.
   180  	//
   181  	// `distrubtion.UnmarshalManifest`, which is used to unmarshal this for real, checks these media type values.
   182  	// If the specified media type does not match it will error, and in some cases (docker media types) it is required.
   183  	// So pretty much if we don't have a media type we can fall back to OCI.
   184  	// This does have a special fallback for schema1 manifests just because it is easy to detect.
   185  	switch {
   186  	case mfst.MediaType != "":
   187  		return mfst.MediaType, nil
   188  	case mfst.FSLayers != nil:
   189  		return schema1.MediaTypeManifest, nil
   190  	case mfst.Config != nil:
   191  		return specs.MediaTypeImageManifest, nil
   192  	default:
   193  		return specs.MediaTypeImageIndex, nil
   194  	}
   195  }