...

Source file src/github.com/docker/distribution/registry/storage/manifeststore_test.go

Documentation: github.com/docker/distribution/registry/storage

     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  	// Build up some test layers and add them to the manifest, saving the
    91  	// readseekers for upload later.
    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  	// If schema1 is not enabled, do a short version of this test, just checking
   125  	// if we get the right error when we Put
   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  	// Now, upload the layers that were missing!
   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  	// Now that we have a payload, take a moment to check that the manifest is
   208  	// return by the payload digest.
   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  	// Now, push the same manifest with a different key
   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  	// Test deleting manifests
   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  	// Re-upload should restore manifest to a good state
   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  	// Build a manifest and store it and its layers in the registry
   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  	// Add some layers
   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  	// before putting the manifest test for proper handling of SchemaVersion
   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  	// Also create an image index that contains the manifest
   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  	// Now check that we can retrieve the manifest
   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  	// and the image index
   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  // TestLinkPathFuncs ensures that the link path functions behavior are locked
   547  // down and implemented as expected.
   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