Skip to content

Commit 6ef0529

Browse files
authored
Merge pull request #3591 from thaJeztah/1.2_backport_compute_manifest_metadata
[release/1.2 backport] Compute manifest metadata when not provided.
2 parents ad5af8a + 0d6d883 commit 6ef0529

2 files changed

Lines changed: 98 additions & 32 deletions

File tree

remotes/docker/resolver.go

Lines changed: 86 additions & 26 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"
@@ -83,6 +84,9 @@ type ResolverOptions struct {
8384
// Host provides the hostname given a namespace.
8485
Host func(string) (string, error)
8586

87+
// Headers are the HTTP request header fields sent by the resolver
88+
Headers http.Header
89+
8690
// PlainHTTP specifies to use plain http and not https
8791
PlainHTTP bool
8892

@@ -106,6 +110,7 @@ func DefaultHost(ns string) (string, error) {
106110
type dockerResolver struct {
107111
auth Authorizer
108112
host func(string) (string, error)
113+
headers http.Header
109114
uagent string
110115
plainHTTP bool
111116
client *http.Client
@@ -120,21 +125,65 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
120125
if options.Host == nil {
121126
options.Host = DefaultHost
122127
}
123-
ua := "containerd/" + version.Version
128+
if options.Headers == nil {
129+
options.Headers = make(http.Header)
130+
}
131+
if _, ok := options.Headers["Accept"]; !ok {
132+
// set headers for all the types we support for resolution.
133+
options.Headers.Set("Accept", strings.Join([]string{
134+
images.MediaTypeDockerSchema2Manifest,
135+
images.MediaTypeDockerSchema2ManifestList,
136+
ocispec.MediaTypeImageManifest,
137+
ocispec.MediaTypeImageIndex, "*"}, ", "))
138+
}
139+
ua := options.Headers.Get("User-Agent")
140+
if ua != "" {
141+
options.Headers.Del("User-Agent")
142+
} else {
143+
ua = "containerd/" + version.Version
144+
}
145+
124146
if options.Authorizer == nil {
125147
options.Authorizer = NewAuthorizer(options.Client, options.Credentials)
126148
options.Authorizer.(*dockerAuthorizer).ua = ua
127149
}
128150
return &dockerResolver{
129151
auth: options.Authorizer,
130152
host: options.Host,
153+
headers: options.Headers,
131154
uagent: ua,
132155
plainHTTP: options.PlainHTTP,
133156
client: options.Client,
134157
tracker: options.Tracker,
135158
}
136159
}
137160

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

140189
func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocispec.Descriptor, error) {
@@ -187,12 +236,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
187236
return "", ocispec.Descriptor{}, err
188237
}
189238

190-
// set headers for all the types we support for resolution.
191-
req.Header.Set("Accept", strings.Join([]string{
192-
images.MediaTypeDockerSchema2Manifest,
193-
images.MediaTypeDockerSchema2ManifestList,
194-
ocispec.MediaTypeImageManifest,
195-
ocispec.MediaTypeImageIndex, "*"}, ", "))
239+
req.Header = r.headers
196240

197241
log.G(ctx).Debug("resolving")
198242
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
@@ -210,40 +254,56 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
210254
}
211255
return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)
212256
}
257+
size := resp.ContentLength
213258

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

219-
if dgstHeader != "" {
265+
if dgstHeader != "" && size != -1 {
220266
if err := dgstHeader.Validate(); err != nil {
221267
return "", ocispec.Descriptor{}, errors.Wrapf(err, "%q in header not a valid digest", dgstHeader)
222268
}
223269
dgst = dgstHeader
224-
}
270+
} else {
271+
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
225272

226-
if dgst == "" {
227-
return "", ocispec.Descriptor{}, errors.Errorf("could not resolve digest for %v", ref)
228-
}
229-
230-
var (
231-
size int64
232-
sizeHeader = resp.Header.Get("Content-Length")
233-
)
234-
235-
size, err = strconv.ParseInt(sizeHeader, 10, 64)
236-
if err != nil {
273+
req, err := http.NewRequest(http.MethodGet, u, nil)
274+
if err != nil {
275+
return "", ocispec.Descriptor{}, err
276+
}
277+
req.Header = r.headers
237278

238-
return "", ocispec.Descriptor{}, errors.Wrapf(err, "invalid size header: %q", sizeHeader)
239-
}
240-
if size < 0 {
241-
return "", ocispec.Descriptor{}, errors.Errorf("%q in header not a valid size", sizeHeader)
279+
resp, err := fetcher.doRequestWithRetries(ctx, req, nil)
280+
if err != nil {
281+
return "", ocispec.Descriptor{}, err
282+
}
283+
defer resp.Body.Close()
284+
285+
bodyReader := countingReader{reader: resp.Body}
286+
287+
contentType = getManifestMediaType(resp)
288+
if contentType == images.MediaTypeDockerSchema1Manifest {
289+
b, err := schema1.ReadStripSignature(&bodyReader)
290+
if err != nil {
291+
return "", ocispec.Descriptor{}, err
292+
}
293+
294+
dgst = digest.FromBytes(b)
295+
} else {
296+
dgst, err = digest.FromReader(&bodyReader)
297+
if err != nil {
298+
return "", ocispec.Descriptor{}, err
299+
}
300+
}
301+
size = bodyReader.bytesRead
242302
}
243303

244304
desc := ocispec.Descriptor{
245305
Digest: dgst,
246-
MediaType: resp.Header.Get("Content-Type"), // need to strip disposition?
306+
MediaType: contentType,
247307
Size: size,
248308
}
249309

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)