Skip to content

Commit 7c1e883

Browse files
authored
Merge pull request #3245 from msg555/master
Compute manifest metadata when not provided
2 parents 5cf1356 + ee902af commit 7c1e883

2 files changed

Lines changed: 74 additions & 25 deletions

File tree

remotes/docker/resolver.go

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@ package docker
1818

1919
import (
2020
"context"
21+
"io"
2122
"net/http"
2223
"net/url"
2324
"path"
24-
"strconv"
2525
"strings"
2626

2727
"github.com/containerd/containerd/errdefs"
2828
"github.com/containerd/containerd/images"
2929
"github.com/containerd/containerd/log"
3030
"github.com/containerd/containerd/reference"
3131
"github.com/containerd/containerd/remotes"
32+
"github.com/containerd/containerd/remotes/docker/schema1"
3233
"github.com/containerd/containerd/version"
3334
digest "github.com/opencontainers/go-digest"
3435
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -150,6 +151,32 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
150151
}
151152
}
152153

154+
func getManifestMediaType(resp *http.Response) string {
155+
// Strip encoding data (manifests should always be ascii JSON)
156+
contentType := resp.Header.Get("Content-Type")
157+
if sp := strings.IndexByte(contentType, ';'); sp != -1 {
158+
contentType = contentType[0:sp]
159+
}
160+
161+
// As of Apr 30 2019 the registry.access.redhat.com registry does not specify
162+
// the content type of any data but uses schema1 manifests.
163+
if contentType == "text/plain" {
164+
contentType = images.MediaTypeDockerSchema1Manifest
165+
}
166+
return contentType
167+
}
168+
169+
type countingReader struct {
170+
reader io.Reader
171+
bytesRead int64
172+
}
173+
174+
func (r *countingReader) Read(p []byte) (int, error) {
175+
n, err := r.reader.Read(p)
176+
r.bytesRead += int64(n)
177+
return n, err
178+
}
179+
153180
var _ remotes.Resolver = &dockerResolver{}
154181

155182
func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
@@ -220,40 +247,56 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
220247
}
221248
return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
222249
}
250+
size := resp.ContentLength
223251

224252
// this is the only point at which we trust the registry. we use the
225253
// content headers to assemble a descriptor for the name. when this becomes
226254
// more robust, we mostly get this information from a secure trust store.
227255
dgstHeader := digest.Digest(resp.Header.Get("Docker-Content-Digest"))
256+
contentType := getManifestMediaType(resp)
228257

229-
if dgstHeader != "" {
258+
if dgstHeader != "" && size != -1 {
230259
if err := dgstHeader.Validate(); err != nil {
231260
return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
232261
}
233262
dgst = dgstHeader
234-
}
235-
236-
if dgst == "" {
237-
return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref)
238-
}
263+
} else {
264+
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
239265

240-
var (
241-
size int64
242-
sizeHeader = resp.Header.Get("Content-Length")
243-
)
244-
245-
size, err = strconv.ParseInt(sizeHeader, 10, 64)
246-
if err != nil {
266+
req, err := http.NewRequest(http.MethodGet, u, nil)
267+
if err != nil {
268+
return "", ocispec.Descriptor{}, err
269+
}
270+
req.Header = r.headers
247271

248-
return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader)
249-
}
250-
if size < 0 {
251-
return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader)
272+
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
273+
if err != nil {
274+
return "", ocispec.Descriptor{}, err
275+
}
276+
defer resp.Body.Close()
277+
278+
bodyReader := countingReader{reader: resp.Body}
279+
280+
contentType = getManifestMediaType(resp)
281+
if contentType == images.MediaTypeDockerSchema1Manifest {
282+
b, err := schema1.ReadStripSignature(&bodyReader)
283+
if err != nil {
284+
return "", ocispec.Descriptor{}, err
285+
}
286+
287+
dgst = digest.FromBytes(b)
288+
} else {
289+
dgst, err = digest.FromReader(&bodyReader)
290+
if err != nil {
291+
return "", ocispec.Descriptor{}, err
292+
}
293+
}
294+
size = bodyReader.bytesRead
252295
}
253296

254297
desc := ocispec.Descriptor{
255298
Digest: dgst,
256-
MediaType: resp.Header.Get("Content-Type"), // need to strip disposition?
299+
MediaType: contentType,
257300
Size: size,
258301
}
259302

remotes/docker/schema1/converter.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,17 @@ func (c *Converter) Convert(ctx context.Context, opts ...ConvertOpt) (ocispec.De
227227
return desc, nil
228228
}
229229

230+
// ReadStripSignature reads in a schema1 manifest and returns a byte array
231+
// with the "signatures" field stripped
232+
func ReadStripSignature(schema1Blob io.Reader) ([]byte, error) {
233+
b, err := ioutil.ReadAll(io.LimitReader(schema1Blob, manifestSizeLimit)) // limit to 8MB
234+
if err != nil {
235+
return nil, err
236+
}
237+
238+
return stripSignature(b)
239+
}
240+
230241
func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor) error {
231242
log.G(ctx).Debug("fetch schema 1")
232243

@@ -235,17 +246,12 @@ func (c *Converter) fetchManifest(ctx context.Context, desc ocispec.Descriptor)
235246
return err
236247
}
237248

238-
b, err := ioutil.ReadAll(io.LimitReader(rc, manifestSizeLimit)) // limit to 8MB
249+
b, err := ReadStripSignature(rc)
239250
rc.Close()
240251
if err != nil {
241252
return err
242253
}
243254

244-
b, err = stripSignature(b)
245-
if err != nil {
246-
return err
247-
}
248-
249255
var m manifest
250256
if err := json.Unmarshal(b, &m); err != nil {
251257
return err

0 commit comments

Comments
 (0)