1 package storage
2
3 import (
4 "bytes"
5 "context"
6 "io"
7 "reflect"
8 "testing"
9
10 "github.com/distribution/reference"
11 "github.com/docker/distribution"
12 "github.com/docker/distribution/manifest"
13 "github.com/docker/distribution/manifest/manifestlist"
14 "github.com/docker/distribution/manifest/ocischema"
15 "github.com/docker/distribution/manifest/schema1"
16 "github.com/docker/distribution/registry/storage/cache/memory"
17 "github.com/docker/distribution/registry/storage/driver"
18 "github.com/docker/distribution/registry/storage/driver/inmemory"
19 "github.com/docker/distribution/testutil"
20 "github.com/docker/libtrust"
21 "github.com/opencontainers/go-digest"
22 v1 "github.com/opencontainers/image-spec/specs-go/v1"
23 )
24
25 type manifestStoreTestEnv struct {
26 ctx context.Context
27 driver driver.StorageDriver
28 registry distribution.Namespace
29 repository distribution.Repository
30 name reference.Named
31 tag string
32 }
33
34 func newManifestStoreTestEnv(t *testing.T, name reference.Named, tag string, options ...RegistryOption) *manifestStoreTestEnv {
35 ctx := context.Background()
36 driver := inmemory.New()
37 registry, err := NewRegistry(ctx, driver, options...)
38 if err != nil {
39 t.Fatalf("error creating registry: %v", err)
40 }
41
42 repo, err := registry.Repository(ctx, name)
43 if err != nil {
44 t.Fatalf("unexpected error getting repo: %v", err)
45 }
46
47 return &manifestStoreTestEnv{
48 ctx: ctx,
49 driver: driver,
50 registry: registry,
51 repository: repo,
52 name: name,
53 tag: tag,
54 }
55 }
56
57 func TestManifestStorage(t *testing.T) {
58 k, err := libtrust.GenerateECP256PrivateKey()
59 if err != nil {
60 t.Fatal(err)
61 }
62 testManifestStorage(t, true, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k), EnableSchema1)
63 }
64
65 func TestManifestStorageV1Unsupported(t *testing.T) {
66 k, err := libtrust.GenerateECP256PrivateKey()
67 if err != nil {
68 t.Fatal(err)
69 }
70 testManifestStorage(t, false, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect, Schema1SigningKey(k))
71 }
72
73 func testManifestStorage(t *testing.T, schema1Enabled bool, options ...RegistryOption) {
74 repoName, _ := reference.WithName("foo/bar")
75 env := newManifestStoreTestEnv(t, repoName, "thetag", options...)
76 ctx := context.Background()
77 ms, err := env.repository.Manifests(ctx)
78 if err != nil {
79 t.Fatal(err)
80 }
81
82 m := schema1.Manifest{
83 Versioned: manifest.Versioned{
84 SchemaVersion: 1,
85 },
86 Name: env.name.Name(),
87 Tag: env.tag,
88 }
89
90
91
92 testLayers := map[digest.Digest]io.ReadSeeker{}
93 for i := 0; i < 2; i++ {
94 rs, dgst, err := testutil.CreateRandomTarFile()
95 if err != nil {
96 t.Fatalf("unexpected error generating test layer file")
97 }
98
99 testLayers[dgst] = rs
100 m.FSLayers = append(m.FSLayers, schema1.FSLayer{
101 BlobSum: dgst,
102 })
103 m.History = append(m.History, schema1.History{
104 V1Compatibility: "",
105 })
106
107 }
108
109 pk, err := libtrust.GenerateECP256PrivateKey()
110 if err != nil {
111 t.Fatalf("unexpected error generating private key: %v", err)
112 }
113
114 sm, merr := schema1.Sign(&m, pk)
115 if merr != nil {
116 t.Fatalf("error signing manifest: %v", err)
117 }
118
119 _, err = ms.Put(ctx, sm)
120 if err == nil {
121 t.Fatalf("expected errors putting manifest with full verification")
122 }
123
124
125
126 if !schema1Enabled {
127 if err != distribution.ErrSchemaV1Unsupported {
128 t.Fatalf("got the wrong error when schema1 is disabled: %s", err)
129 }
130 return
131 }
132
133 switch err := err.(type) {
134 case distribution.ErrManifestVerification:
135 if len(err) != 2 {
136 t.Fatalf("expected 2 verification errors: %#v", err)
137 }
138
139 for _, err := range err {
140 if _, ok := err.(distribution.ErrManifestBlobUnknown); !ok {
141 t.Fatalf("unexpected error type: %v", err)
142 }
143 }
144 default:
145 t.Fatalf("unexpected error verifying manifest: %v", err)
146 }
147
148
149 for dgst, rs := range testLayers {
150 wr, err := env.repository.Blobs(env.ctx).Create(env.ctx)
151 if err != nil {
152 t.Fatalf("unexpected error creating test upload: %v", err)
153 }
154
155 if _, err := io.Copy(wr, rs); err != nil {
156 t.Fatalf("unexpected error copying to upload: %v", err)
157 }
158
159 if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
160 t.Fatalf("unexpected error finishing upload: %v", err)
161 }
162 }
163
164 var manifestDigest digest.Digest
165 if manifestDigest, err = ms.Put(ctx, sm); err != nil {
166 t.Fatalf("unexpected error putting manifest: %v", err)
167 }
168
169 exists, err := ms.Exists(ctx, manifestDigest)
170 if err != nil {
171 t.Fatalf("unexpected error checking manifest existence: %#v", err)
172 }
173
174 if !exists {
175 t.Fatalf("manifest should exist")
176 }
177
178 fromStore, err := ms.Get(ctx, manifestDigest)
179 if err != nil {
180 t.Fatalf("unexpected error fetching manifest: %v", err)
181 }
182
183 fetchedManifest, ok := fromStore.(*schema1.SignedManifest)
184 if !ok {
185 t.Fatalf("unexpected manifest type from signedstore")
186 }
187
188 if !bytes.Equal(fetchedManifest.Canonical, sm.Canonical) {
189 t.Fatalf("fetched payload does not match original payload: %q != %q", fetchedManifest.Canonical, sm.Canonical)
190 }
191
192 _, pl, err := fetchedManifest.Payload()
193 if err != nil {
194 t.Fatalf("error getting payload %#v", err)
195 }
196
197 fetchedJWS, err := libtrust.ParsePrettySignature(pl, "signatures")
198 if err != nil {
199 t.Fatalf("unexpected error parsing jws: %v", err)
200 }
201
202 payload, err := fetchedJWS.Payload()
203 if err != nil {
204 t.Fatalf("unexpected error extracting payload: %v", err)
205 }
206
207
208
209
210 dgst := digest.FromBytes(payload)
211 exists, err = ms.Exists(ctx, dgst)
212 if err != nil {
213 t.Fatalf("error checking manifest existence by digest: %v", err)
214 }
215
216 if !exists {
217 t.Fatalf("manifest %s should exist", dgst)
218 }
219
220 fetchedByDigest, err := ms.Get(ctx, dgst)
221 if err != nil {
222 t.Fatalf("unexpected error fetching manifest by digest: %v", err)
223 }
224
225 byDigestManifest, ok := fetchedByDigest.(*schema1.SignedManifest)
226 if !ok {
227 t.Fatalf("unexpected manifest type from signedstore")
228 }
229
230 if !bytes.Equal(byDigestManifest.Canonical, fetchedManifest.Canonical) {
231 t.Fatalf("fetched manifest not equal: %q != %q", byDigestManifest.Canonical, fetchedManifest.Canonical)
232 }
233
234 sigs, err := fetchedJWS.Signatures()
235 if err != nil {
236 t.Fatalf("unable to extract signatures: %v", err)
237 }
238
239 if len(sigs) != 1 {
240 t.Fatalf("unexpected number of signatures: %d != %d", len(sigs), 1)
241 }
242
243
244 pk2, err := libtrust.GenerateECP256PrivateKey()
245 if err != nil {
246 t.Fatalf("unexpected error generating private key: %v", err)
247 }
248
249 sm2, err := schema1.Sign(&m, pk2)
250 if err != nil {
251 t.Fatalf("unexpected error signing manifest: %v", err)
252 }
253 _, pl, err = sm2.Payload()
254 if err != nil {
255 t.Fatalf("error getting payload %#v", err)
256 }
257
258 jws2, err := libtrust.ParsePrettySignature(pl, "signatures")
259 if err != nil {
260 t.Fatalf("error parsing signature: %v", err)
261 }
262
263 sigs2, err := jws2.Signatures()
264 if err != nil {
265 t.Fatalf("unable to extract signatures: %v", err)
266 }
267
268 if len(sigs2) != 1 {
269 t.Fatalf("unexpected number of signatures: %d != %d", len(sigs2), 1)
270 }
271
272 if manifestDigest, err = ms.Put(ctx, sm2); err != nil {
273 t.Fatalf("unexpected error putting manifest: %v", err)
274 }
275
276 fromStore, err = ms.Get(ctx, manifestDigest)
277 if err != nil {
278 t.Fatalf("unexpected error fetching manifest: %v", err)
279 }
280
281 fetched, ok := fromStore.(*schema1.SignedManifest)
282 if !ok {
283 t.Fatalf("unexpected type from signed manifeststore : %T", fetched)
284 }
285
286 if _, err := schema1.Verify(fetched); err != nil {
287 t.Fatalf("unexpected error verifying manifest: %v", err)
288 }
289
290 _, pl, err = fetched.Payload()
291 if err != nil {
292 t.Fatalf("error getting payload %#v", err)
293 }
294
295 receivedJWS, err := libtrust.ParsePrettySignature(pl, "signatures")
296 if err != nil {
297 t.Fatalf("unexpected error parsing jws: %v", err)
298 }
299
300 receivedPayload, err := receivedJWS.Payload()
301 if err != nil {
302 t.Fatalf("unexpected error extracting received payload: %v", err)
303 }
304
305 if !bytes.Equal(receivedPayload, payload) {
306 t.Fatalf("payloads are not equal")
307 }
308
309
310 err = ms.Delete(ctx, dgst)
311 if err != nil {
312 t.Fatalf("unexpected an error deleting manifest by digest: %v", err)
313 }
314
315 exists, err = ms.Exists(ctx, dgst)
316 if err != nil {
317 t.Fatalf("Error querying manifest existence")
318 }
319 if exists {
320 t.Errorf("Deleted manifest should not exist")
321 }
322
323 deletedManifest, err := ms.Get(ctx, dgst)
324 if err == nil {
325 t.Errorf("Unexpected success getting deleted manifest")
326 }
327 switch err.(type) {
328 case distribution.ErrManifestUnknownRevision:
329 break
330 default:
331 t.Errorf("Unexpected error getting deleted manifest: %s", reflect.ValueOf(err).Type())
332 }
333
334 if deletedManifest != nil {
335 t.Errorf("Deleted manifest get returned non-nil")
336 }
337
338
339 _, err = ms.Put(ctx, sm)
340 if err != nil {
341 t.Errorf("Error re-uploading deleted manifest")
342 }
343
344 exists, err = ms.Exists(ctx, dgst)
345 if err != nil {
346 t.Fatalf("Error querying manifest existence")
347 }
348 if !exists {
349 t.Errorf("Restored manifest should exist")
350 }
351
352 deletedManifest, err = ms.Get(ctx, dgst)
353 if err != nil {
354 t.Errorf("Unexpected error getting manifest")
355 }
356 if deletedManifest == nil {
357 t.Errorf("Deleted manifest get returned non-nil")
358 }
359
360 r, err := NewRegistry(ctx, env.driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect)
361 if err != nil {
362 t.Fatalf("error creating registry: %v", err)
363 }
364 repo, err := r.Repository(ctx, env.name)
365 if err != nil {
366 t.Fatalf("unexpected error getting repo: %v", err)
367 }
368 ms, err = repo.Manifests(ctx)
369 if err != nil {
370 t.Fatal(err)
371 }
372 err = ms.Delete(ctx, dgst)
373 if err == nil {
374 t.Errorf("Unexpected success deleting while disabled")
375 }
376 }
377
378 func TestOCIManifestStorage(t *testing.T) {
379 testOCIManifestStorage(t, "includeMediaTypes=true", true)
380 testOCIManifestStorage(t, "includeMediaTypes=false", false)
381 }
382
383 func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes bool) {
384 var imageMediaType string
385 var indexMediaType string
386 if includeMediaTypes {
387 imageMediaType = v1.MediaTypeImageManifest
388 indexMediaType = v1.MediaTypeImageIndex
389 } else {
390 imageMediaType = ""
391 indexMediaType = ""
392 }
393
394 repoName, _ := reference.WithName("foo/bar")
395 env := newManifestStoreTestEnv(t, repoName, "thetag",
396 BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()),
397 EnableDelete, EnableRedirect)
398
399 ctx := context.Background()
400 ms, err := env.repository.Manifests(ctx)
401 if err != nil {
402 t.Fatal(err)
403 }
404
405
406
407 blobStore := env.repository.Blobs(ctx)
408 builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{})
409 err = builder.(*ocischema.Builder).SetMediaType(imageMediaType)
410 if err != nil {
411 t.Fatal(err)
412 }
413
414
415 for i := 0; i < 2; i++ {
416 rs, dgst, err := testutil.CreateRandomTarFile()
417 if err != nil {
418 t.Fatalf("%s: unexpected error generating test layer file", testname)
419 }
420
421 wr, err := env.repository.Blobs(env.ctx).Create(env.ctx)
422 if err != nil {
423 t.Fatalf("%s: unexpected error creating test upload: %v", testname, err)
424 }
425
426 if _, err := io.Copy(wr, rs); err != nil {
427 t.Fatalf("%s: unexpected error copying to upload: %v", testname, err)
428 }
429
430 if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
431 t.Fatalf("%s: unexpected error finishing upload: %v", testname, err)
432 }
433
434 builder.AppendReference(distribution.Descriptor{Digest: dgst})
435 }
436
437 manifest, err := builder.Build(ctx)
438 if err != nil {
439 t.Fatalf("%s: unexpected error generating manifest: %v", testname, err)
440 }
441
442
443
444 if manifest.(*ocischema.DeserializedManifest).Manifest.SchemaVersion != 2 {
445 t.Fatalf("%s: unexpected error generating default version for oci manifest", testname)
446 }
447 manifest.(*ocischema.DeserializedManifest).Manifest.SchemaVersion = 0
448
449 var manifestDigest digest.Digest
450 if manifestDigest, err = ms.Put(ctx, manifest); err != nil {
451 if err.Error() != "unrecognized manifest schema version 0" {
452 t.Fatalf("%s: unexpected error putting manifest: %v", testname, err)
453 }
454 manifest.(*ocischema.DeserializedManifest).Manifest.SchemaVersion = 2
455 if manifestDigest, err = ms.Put(ctx, manifest); err != nil {
456 t.Fatalf("%s: unexpected error putting manifest: %v", testname, err)
457 }
458 }
459
460
461
462 descriptor, err := env.registry.BlobStatter().Stat(ctx, manifestDigest)
463 if err != nil {
464 t.Fatalf("%s: unexpected error getting manifest descriptor", testname)
465 }
466 descriptor.MediaType = v1.MediaTypeImageManifest
467
468 platformSpec := manifestlist.PlatformSpec{
469 Architecture: "atari2600",
470 OS: "CP/M",
471 }
472
473 manifestDescriptors := []manifestlist.ManifestDescriptor{
474 {
475 Descriptor: descriptor,
476 Platform: platformSpec,
477 },
478 }
479
480 imageIndex, err := manifestlist.FromDescriptorsWithMediaType(manifestDescriptors, indexMediaType)
481 if err != nil {
482 t.Fatalf("%s: unexpected error creating image index: %v", testname, err)
483 }
484
485 var indexDigest digest.Digest
486 if indexDigest, err = ms.Put(ctx, imageIndex); err != nil {
487 t.Fatalf("%s: unexpected error putting image index: %v", testname, err)
488 }
489
490
491
492 fromStore, err := ms.Get(ctx, manifestDigest)
493 if err != nil {
494 t.Fatalf("%s: unexpected error fetching manifest: %v", testname, err)
495 }
496
497 fetchedManifest, ok := fromStore.(*ocischema.DeserializedManifest)
498 if !ok {
499 t.Fatalf("%s: unexpected type for fetched manifest", testname)
500 }
501
502 if fetchedManifest.MediaType != imageMediaType {
503 t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType)
504 }
505
506 if fetchedManifest.SchemaVersion != ocischema.SchemaVersion.SchemaVersion {
507 t.Fatalf("%s: unexpected schema version for result, %d", testname, fetchedManifest.SchemaVersion)
508 }
509
510 payloadMediaType, _, err := fromStore.Payload()
511 if err != nil {
512 t.Fatalf("%s: error getting payload %v", testname, err)
513 }
514
515 if payloadMediaType != v1.MediaTypeImageManifest {
516 t.Fatalf("%s: unexpected MediaType for manifest payload, %s", testname, payloadMediaType)
517 }
518
519
520
521 fromStore, err = ms.Get(ctx, indexDigest)
522 if err != nil {
523 t.Fatalf("%s: unexpected error fetching image index: %v", testname, err)
524 }
525
526 fetchedIndex, ok := fromStore.(*manifestlist.DeserializedManifestList)
527 if !ok {
528 t.Fatalf("%s: unexpected type for fetched manifest", testname)
529 }
530
531 if fetchedIndex.MediaType != indexMediaType {
532 t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType)
533 }
534
535 payloadMediaType, _, err = fromStore.Payload()
536 if err != nil {
537 t.Fatalf("%s: error getting payload %v", testname, err)
538 }
539
540 if payloadMediaType != v1.MediaTypeImageIndex {
541 t.Fatalf("%s: unexpected MediaType for index payload, %s", testname, payloadMediaType)
542 }
543
544 }
545
546
547
548 func TestLinkPathFuncs(t *testing.T) {
549 for _, testcase := range []struct {
550 repo string
551 digest digest.Digest
552 linkPathFn linkPathFunc
553 expected string
554 }{
555 {
556 repo: "foo/bar",
557 digest: "sha256:deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
558 linkPathFn: blobLinkPath,
559 expected: "/docker/registry/v2/repositories/foo/bar/_layers/sha256/deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/link",
560 },
561 {
562 repo: "foo/bar",
563 digest: "sha256:deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
564 linkPathFn: manifestRevisionLinkPath,
565 expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/deadbeaf98fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/link",
566 },
567 } {
568 p, err := testcase.linkPathFn(testcase.repo, testcase.digest)
569 if err != nil {
570 t.Fatalf("unexpected error calling linkPathFn(pm, %q, %q): %v", testcase.repo, testcase.digest, err)
571 }
572
573 if p != testcase.expected {
574 t.Fatalf("incorrect path returned: %q != %q", p, testcase.expected)
575 }
576 }
577 }
578
View as plain text