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 }