// MakeSchema1Manifest constructs a schema 1 manifest from a given list of digests and returns // the digest of the manifest func MakeSchema1Manifest(digests []digest.Digest) (distribution.Manifest, error) { manifest := schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: "who", Tag: "cares", } for _, digest := range digests { manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: digest}) manifest.History = append(manifest.History, schema1.History{V1Compatibility: ""}) } pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { return nil, fmt.Errorf("unexpected error generating private key: %v", err) } signedManifest, err := schema1.Sign(&manifest, pk) if err != nil { return nil, fmt.Errorf("error signing manifest: %v", err) } return signedManifest, nil }
// fixManifestLayers removes repeated layers from the manifest and checks the // correctness of the parent chain. func fixManifestLayers(m *schema1.Manifest) error { imgs := make([]*image.V1Image, len(m.FSLayers)) for i := range m.FSLayers { img := &image.V1Image{} if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), img); err != nil { return err } imgs[i] = img if err := v1.ValidateID(img.ID); err != nil { return err } } if imgs[len(imgs)-1].Parent != "" && runtime.GOOS != "windows" { // Windows base layer can point to a base layer parent that is not in manifest. return errors.New("Invalid parent ID in the base layer of the image.") } // check general duplicates to error instead of a deadlock idmap := make(map[string]struct{}) var lastID string for _, img := range imgs { // skip IDs that appear after each other, we handle those later if _, exists := idmap[img.ID]; img.ID != lastID && exists { return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID) } lastID = img.ID idmap[lastID] = struct{}{} } // backwards loop so that we keep the remaining indexes after removing items for i := len(imgs) - 2; i >= 0; i-- { if imgs[i].ID == imgs[i+1].ID { // repeated ID. remove and continue m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...) m.History = append(m.History[:i], m.History[i+1:]...) } else if imgs[i].Parent != imgs[i+1].ID { return fmt.Errorf("Invalid parent ID. Expected %v, got %v.", imgs[i+1].ID, imgs[i].Parent) } } return nil }
func createManifest(name string, tag string, arch string, ordered []*Layer) (*manifest.Manifest, []byte, digest.Digest, error) { var m *manifest.Manifest m = &manifest.Manifest{ Versioned: versioned.Versioned{ SchemaVersion: 1, }, Name: name, Tag: tag, Architecture: arch} for _, l := range ordered { m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: l.BlobSum}) m.History = append(m.History, manifest.History{V1Compatibility: l.Data}) } dgstr := digest.Canonical.New() data, err := json.MarshalIndent(m, "", " ") if err != nil { return nil, nil, "", err } dgstr.Hash().Write(data) return m, data, dgstr.Digest(), nil }
func testManifestStorage(t *testing.T, options ...RegistryOption) { repoName, _ := reference.ParseNamed("foo/bar") env := newManifestStoreTestEnv(t, repoName, "thetag", options...) ctx := context.Background() ms, err := env.repository.Manifests(ctx) if err != nil { t.Fatal(err) } equalSignatures := env.registry.(*registry).schema1SignaturesEnabled m := schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: env.name.Name(), Tag: env.tag, } // Build up some test layers and add them to the manifest, saving the // readseekers for upload later. testLayers := map[digest.Digest]io.ReadSeeker{} for i := 0; i < 2; i++ { rs, ds, err := testutil.CreateRandomTarFile() if err != nil { t.Fatalf("unexpected error generating test layer file") } dgst := digest.Digest(ds) testLayers[digest.Digest(dgst)] = rs m.FSLayers = append(m.FSLayers, schema1.FSLayer{ BlobSum: dgst, }) m.History = append(m.History, schema1.History{ V1Compatibility: "", }) } pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { t.Fatalf("unexpected error generating private key: %v", err) } sm, merr := schema1.Sign(&m, pk) if merr != nil { t.Fatalf("error signing manifest: %v", err) } _, err = ms.Put(ctx, sm) if err == nil { t.Fatalf("expected errors putting manifest with full verification") } switch err := err.(type) { case distribution.ErrManifestVerification: if len(err) != 2 { t.Fatalf("expected 2 verification errors: %#v", err) } for _, err := range err { if _, ok := err.(distribution.ErrManifestBlobUnknown); !ok { t.Fatalf("unexpected error type: %v", err) } } default: t.Fatalf("unexpected error verifying manifest: %v", err) } // Now, upload the layers that were missing! for dgst, rs := range testLayers { wr, err := env.repository.Blobs(env.ctx).Create(env.ctx) if err != nil { t.Fatalf("unexpected error creating test upload: %v", err) } if _, err := io.Copy(wr, rs); err != nil { t.Fatalf("unexpected error copying to upload: %v", err) } if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil { t.Fatalf("unexpected error finishing upload: %v", err) } } var manifestDigest digest.Digest if manifestDigest, err = ms.Put(ctx, sm); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } exists, err := ms.Exists(ctx, manifestDigest) if err != nil { t.Fatalf("unexpected error checking manifest existence: %#v", err) } if !exists { t.Fatalf("manifest should exist") } fromStore, err := ms.Get(ctx, manifestDigest) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } fetchedManifest, ok := fromStore.(*schema1.SignedManifest) if !ok { t.Fatalf("unexpected manifest type from signedstore") } if !bytes.Equal(fetchedManifest.Canonical, sm.Canonical) { t.Fatalf("fetched payload does not match original payload: %q != %q", fetchedManifest.Canonical, sm.Canonical) } if equalSignatures { if !reflect.DeepEqual(fetchedManifest, sm) { t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest.Manifest, sm.Manifest) } } _, pl, err := fetchedManifest.Payload() if err != nil { t.Fatalf("error getting payload %#v", err) } fetchedJWS, err := libtrust.ParsePrettySignature(pl, "signatures") if err != nil { t.Fatalf("unexpected error parsing jws: %v", err) } payload, err := fetchedJWS.Payload() if err != nil { t.Fatalf("unexpected error extracting payload: %v", err) } // Now that we have a payload, take a moment to check that the manifest is // return by the payload digest. dgst := digest.FromBytes(payload) exists, err = ms.Exists(ctx, dgst) if err != nil { t.Fatalf("error checking manifest existence by digest: %v", err) } if !exists { t.Fatalf("manifest %s should exist", dgst) } fetchedByDigest, err := ms.Get(ctx, dgst) if err != nil { t.Fatalf("unexpected error fetching manifest by digest: %v", err) } byDigestManifest, ok := fetchedByDigest.(*schema1.SignedManifest) if !ok { t.Fatalf("unexpected manifest type from signedstore") } if !bytes.Equal(byDigestManifest.Canonical, fetchedManifest.Canonical) { t.Fatalf("fetched manifest not equal: %q != %q", byDigestManifest.Canonical, fetchedManifest.Canonical) } if equalSignatures { if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) { t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedByDigest, fetchedManifest) } } sigs, err := fetchedJWS.Signatures() if err != nil { t.Fatalf("unable to extract signatures: %v", err) } if len(sigs) != 1 { t.Fatalf("unexpected number of signatures: %d != %d", len(sigs), 1) } // Now, push the same manifest with a different key pk2, err := libtrust.GenerateECP256PrivateKey() if err != nil { t.Fatalf("unexpected error generating private key: %v", err) } sm2, err := schema1.Sign(&m, pk2) if err != nil { t.Fatalf("unexpected error signing manifest: %v", err) } _, pl, err = sm2.Payload() if err != nil { t.Fatalf("error getting payload %#v", err) } jws2, err := libtrust.ParsePrettySignature(pl, "signatures") if err != nil { t.Fatalf("error parsing signature: %v", err) } sigs2, err := jws2.Signatures() if err != nil { t.Fatalf("unable to extract signatures: %v", err) } if len(sigs2) != 1 { t.Fatalf("unexpected number of signatures: %d != %d", len(sigs2), 1) } if manifestDigest, err = ms.Put(ctx, sm2); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } fromStore, err = ms.Get(ctx, manifestDigest) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } fetched, ok := fromStore.(*schema1.SignedManifest) if !ok { t.Fatalf("unexpected type from signed manifeststore : %T", fetched) } if _, err := schema1.Verify(fetched); err != nil { t.Fatalf("unexpected error verifying manifest: %v", err) } // Assemble our payload and two signatures to get what we expect! expectedJWS, err := libtrust.NewJSONSignature(payload, sigs[0], sigs2[0]) if err != nil { t.Fatalf("unexpected error merging jws: %v", err) } expectedSigs, err := expectedJWS.Signatures() if err != nil { t.Fatalf("unexpected error getting expected signatures: %v", err) } _, pl, err = fetched.Payload() if err != nil { t.Fatalf("error getting payload %#v", err) } receivedJWS, err := libtrust.ParsePrettySignature(pl, "signatures") if err != nil { t.Fatalf("unexpected error parsing jws: %v", err) } receivedPayload, err := receivedJWS.Payload() if err != nil { t.Fatalf("unexpected error extracting received payload: %v", err) } if !bytes.Equal(receivedPayload, payload) { t.Fatalf("payloads are not equal") } if equalSignatures { receivedSigs, err := receivedJWS.Signatures() if err != nil { t.Fatalf("error getting signatures: %v", err) } for i, sig := range receivedSigs { if !bytes.Equal(sig, expectedSigs[i]) { t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i])) } } } // Test deleting manifests err = ms.Delete(ctx, dgst) if err != nil { t.Fatalf("unexpected an error deleting manifest by digest: %v", err) } exists, err = ms.Exists(ctx, dgst) if err != nil { t.Fatalf("Error querying manifest existence") } if exists { t.Errorf("Deleted manifest should not exist") } deletedManifest, err := ms.Get(ctx, dgst) if err == nil { t.Errorf("Unexpected success getting deleted manifest") } switch err.(type) { case distribution.ErrManifestUnknownRevision: break default: t.Errorf("Unexpected error getting deleted manifest: %s", reflect.ValueOf(err).Type()) } if deletedManifest != nil { t.Errorf("Deleted manifest get returned non-nil") } // Re-upload should restore manifest to a good state _, err = ms.Put(ctx, sm) if err != nil { t.Errorf("Error re-uploading deleted manifest") } exists, err = ms.Exists(ctx, dgst) if err != nil { t.Fatalf("Error querying manifest existence") } if !exists { t.Errorf("Restored manifest should exist") } deletedManifest, err = ms.Get(ctx, dgst) if err != nil { t.Errorf("Unexpected error getting manifest") } if deletedManifest == nil { t.Errorf("Deleted manifest get returned non-nil") } r, err := NewRegistry(ctx, env.driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) if err != nil { t.Fatalf("error creating registry: %v", err) } repo, err := r.Repository(ctx, env.name) if err != nil { t.Fatalf("unexpected error getting repo: %v", err) } ms, err = repo.Manifests(ctx) if err != nil { t.Fatal(err) } err = ms.Delete(ctx, dgst) if err == nil { t.Errorf("Unexpected success deleting while disabled") } }
// checkExerciseRegistry takes the registry through all of its operations, // carrying out generic checks. func checkExerciseRepository(t *testing.T, repository distribution.Repository) { // TODO(stevvooe): This would be a nice testutil function. Basically, it // takes the registry through a common set of operations. This could be // used to make cross-cutting updates by changing internals that affect // update counts. Basically, it would make writing tests a lot easier. ctx := context.Background() tag := "thetag" // todo: change this to use Builder m := schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: repository.Named().Name(), Tag: tag, } blobs := repository.Blobs(ctx) for i := 0; i < 2; i++ { rs, ds, err := testutil.CreateRandomTarFile() if err != nil { t.Fatalf("error creating test layer: %v", err) } dgst := digest.Digest(ds) wr, err := blobs.Create(ctx) if err != nil { t.Fatalf("error creating layer upload: %v", err) } // Use the resumes, as well! wr, err = blobs.Resume(ctx, wr.ID()) if err != nil { t.Fatalf("error resuming layer upload: %v", err) } io.Copy(wr, rs) if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}); err != nil { t.Fatalf("unexpected error finishing upload: %v", err) } m.FSLayers = append(m.FSLayers, schema1.FSLayer{ BlobSum: dgst, }) m.History = append(m.History, schema1.History{ V1Compatibility: "", }) // Then fetch the blobs if rc, err := blobs.Open(ctx, dgst); err != nil { t.Fatalf("error fetching layer: %v", err) } else { defer rc.Close() } } pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { t.Fatalf("unexpected error generating key: %v", err) } sm, err := schema1.Sign(&m, pk) if err != nil { t.Fatalf("unexpected error signing manifest: %v", err) } manifests, err := repository.Manifests(ctx) if err != nil { t.Fatal(err.Error()) } var digestPut digest.Digest if digestPut, err = manifests.Put(ctx, sm); err != nil { t.Fatalf("unexpected error putting the manifest: %v", err) } dgst := digest.FromBytes(sm.Canonical) if dgst != digestPut { t.Fatalf("mismatching digest from payload and put") } _, err = manifests.Get(ctx, dgst) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } }
func storeTestImage( ctx context.Context, reg distribution.Namespace, imageReference reference.NamedTagged, schemaVersion int, managedByOpenShift bool, ) (*imageapi.Image, error) { repo, err := reg.Repository(ctx, imageReference) if err != nil { return nil, fmt.Errorf("unexpected error getting repo %q: %v", imageReference.Name(), err) } var ( m distribution.Manifest m1 schema1.Manifest ) switch schemaVersion { case 1: m1 = schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: imageReference.Name(), Tag: imageReference.Tag(), } case 2: // TODO fallthrough default: return nil, fmt.Errorf("unsupported manifest version %d", schemaVersion) } for i := 0; i < testImageLayerCount; i++ { rs, ds, err := registrytest.CreateRandomTarFile() if err != nil { return nil, fmt.Errorf("unexpected error generating test layer file: %v", err) } dgst := digest.Digest(ds) wr, err := repo.Blobs(ctx).Create(ctx) if err != nil { return nil, fmt.Errorf("unexpected error creating test upload: %v", err) } defer wr.Close() n, err := io.Copy(wr, rs) if err != nil { return nil, fmt.Errorf("unexpected error copying to upload: %v", err) } if schemaVersion == 1 { m1.FSLayers = append(m1.FSLayers, schema1.FSLayer{BlobSum: dgst}) m1.History = append(m1.History, schema1.History{V1Compatibility: fmt.Sprintf(`{"size":%d}`, n)}) } // TODO v2 if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst, MediaType: schema1.MediaTypeManifestLayer}); err != nil { return nil, fmt.Errorf("unexpected error finishing upload: %v", err) } } var dgst digest.Digest var payload []byte if schemaVersion == 1 { pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { return nil, fmt.Errorf("unexpected error generating private key: %v", err) } m, err = schema1.Sign(&m1, pk) if err != nil { return nil, fmt.Errorf("error signing manifest: %v", err) } _, payload, err = m.Payload() if err != nil { return nil, fmt.Errorf("error getting payload %#v", err) } dgst = digest.FromBytes(payload) } //TODO v2 image := &imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dgst.String(), }, DockerImageManifest: string(payload), DockerImageReference: imageReference.Name() + "@" + dgst.String(), } if managedByOpenShift { image.Annotations = map[string]string{imageapi.ManagedByOpenShiftAnnotation: "true"} } if schemaVersion == 1 { signedManifest := m.(*schema1.SignedManifest) signatures, err := signedManifest.Signatures() if err != nil { return nil, err } for _, signDigest := range signatures { image.DockerImageSignatures = append(image.DockerImageSignatures, signDigest) } } err = imageapi.ImageWithMetadata(image) if err != nil { return nil, fmt.Errorf("failed to fill image with metadata: %v", err) } return image, nil }
func outputManifestFor(target string) { var pkey trust.PrivateKey if key != "" { var err error pkey, err = trust.LoadKeyFile(key) if err != nil { fmt.Printf("error loading key: %s\n", err.Error()) return } } if verbose { fmt.Errorf("signing with: %s\n", pkey.KeyID()) } f, err := os.Open(target) if err != nil { fmt.Printf("error opening file: %s\n", err.Error()) return } defer func() { if err := f.Close(); err != nil { panic(err) } }() var ( repo, tag string ) layers := LayerMap{} t := tar.NewReader(bufio.NewReader(f)) for { hdr, err := t.Next() if err == io.EOF { break } if strings.HasSuffix(hdr.Name, "layer.tar") { id := getLayerPrefix(hdr.Name) sum, _ := blobSumLayer(t) if _, ok := layers[id]; !ok { layers[id] = &Layer{Id: id} } else { layers[id].BlobSum = sum } } if strings.HasSuffix(hdr.Name, "json") { data, _ := ioutil.ReadAll(t) parent, id, _ := getLayerInfo(data) if _, ok := layers[id]; !ok { layers[id] = &Layer{Id: id, Parent: parent} } else { layers[id].Parent = parent } var img image.Image json.Unmarshal(data, &img) b, _ := json.Marshal(img) layers[id].Data = string(b) + "\n" } if hdr.Name == "repositories" { r, _ := ioutil.ReadAll(t) var raw map[string]interface{} if err := json.Unmarshal(r, &raw); err != nil { return } repo, tag = getRepoInfo(raw) if !strings.Contains(repo, "/") { repo = "library/" + repo } } } m := manifest.Manifest{ Versioned: versioned.Versioned{ SchemaVersion: 1, }, Name: repo, Tag: tag, Architecture: "amd64"} ll := getLayersFromMap(layers) for _, l := range getLayersInOrder(ll) { m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: l.BlobSum}) m.History = append(m.History, manifest.History{V1Compatibility: l.Data}) } var x []byte if pkey != nil { var sm *manifest.SignedManifest sm, err = manifest.Sign(&m, pkey) x, err = sm.MarshalJSON() } else { x, err = json.MarshalIndent(m, "", " ") } if print_digest { dgstr, _ := digest.FromBytes(x) fmt.Println(string(dgstr)) } fmt.Println(string(x)) }
func TestManifestStorage(t *testing.T) { env := newManifestStoreTestEnv(t, "foo/bar", "thetag") ctx := context.Background() ms, err := env.repository.Manifests(ctx) if err != nil { t.Fatal(err) } exists, err := ms.ExistsByTag(env.tag) if err != nil { t.Fatalf("unexpected error checking manifest existence: %v", err) } if exists { t.Fatalf("manifest should not exist") } dgsts, err := ms.Enumerate() if err != nil { t.Errorf("unexpected error enumerating manifest revisions: %v", err) } else if len(dgsts) != 0 { t.Errorf("expected exactly 0 manifests, not %d", len(dgsts)) } if _, err := ms.GetByTag(env.tag); true { switch err.(type) { case distribution.ErrManifestUnknown: break default: t.Fatalf("expected manifest unknown error: %#v", err) } } m := schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: env.name, Tag: env.tag, } // Build up some test layers and add them to the manifest, saving the // readseekers for upload later. testLayers := map[digest.Digest]io.ReadSeeker{} for i := 0; i < 2; i++ { rs, ds, err := testutil.CreateRandomTarFile() if err != nil { t.Fatalf("unexpected error generating test layer file") } dgst := digest.Digest(ds) testLayers[digest.Digest(dgst)] = rs m.FSLayers = append(m.FSLayers, schema1.FSLayer{ BlobSum: dgst, }) m.History = append(m.History, schema1.History{ V1Compatibility: "", }) } pk, err := libtrust.GenerateECP256PrivateKey() if err != nil { t.Fatalf("unexpected error generating private key: %v", err) } sm, merr := schema1.Sign(&m, pk) if merr != nil { t.Fatalf("error signing manifest: %v", err) } err = ms.Put(sm) if err == nil { t.Fatalf("expected errors putting manifest with full verification") } switch err := err.(type) { case distribution.ErrManifestVerification: if len(err) != 2 { t.Fatalf("expected 2 verification errors: %#v", err) } for _, err := range err { if _, ok := err.(distribution.ErrManifestBlobUnknown); !ok { t.Fatalf("unexpected error type: %v", err) } } default: t.Fatalf("unexpected error verifying manifest: %v", err) } // Now, upload the layers that were missing! for dgst, rs := range testLayers { wr, err := env.repository.Blobs(env.ctx).Create(env.ctx) if err != nil { t.Fatalf("unexpected error creating test upload: %v", err) } if _, err := io.Copy(wr, rs); err != nil { t.Fatalf("unexpected error copying to upload: %v", err) } if _, err := wr.Commit(env.ctx, distribution.Descriptor{Digest: dgst}); err != nil { t.Fatalf("unexpected error finishing upload: %v", err) } } if err = ms.Put(sm); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } exists, err = ms.ExistsByTag(env.tag) if err != nil { t.Fatalf("unexpected error checking manifest existence: %v", err) } if !exists { t.Fatalf("manifest should exist") } fetchedManifest, err := ms.GetByTag(env.tag) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } if !reflect.DeepEqual(fetchedManifest, sm) { t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedManifest, sm) } fetchedJWS, err := libtrust.ParsePrettySignature(fetchedManifest.Raw, "signatures") if err != nil { t.Fatalf("unexpected error parsing jws: %v", err) } payload, err := fetchedJWS.Payload() if err != nil { t.Fatalf("unexpected error extracting payload: %v", err) } // Now that we have a payload, take a moment to check that the manifest is // return by the payload digest. dgst, err := digest.FromBytes(payload) if err != nil { t.Fatalf("error getting manifest digest: %v", err) } exists, err = ms.Exists(dgst) if err != nil { t.Fatalf("error checking manifest existence by digest: %v", err) } if !exists { t.Fatalf("manifest %s should exist", dgst) } fetchedByDigest, err := ms.Get(dgst) if err != nil { t.Fatalf("unexpected error fetching manifest by digest: %v", err) } if !reflect.DeepEqual(fetchedByDigest, fetchedManifest) { t.Fatalf("fetched manifest not equal: %#v != %#v", fetchedByDigest, fetchedManifest) } sigs, err := fetchedJWS.Signatures() if err != nil { t.Fatalf("unable to extract signatures: %v", err) } if len(sigs) != 1 { t.Fatalf("unexpected number of signatures: %d != %d", len(sigs), 1) } // Enumerate only valid manifest revision digests dgsts, err = ms.Enumerate() if err != nil { t.Errorf("unexpected error enumerating manifest revisions: %v", err) } else if len(dgsts) != 1 { t.Errorf("expected exactly 1 manifest, not %d", len(dgsts)) } else if dgsts[0] != dgst { t.Errorf("got unexpected digest manifest (%s != %s)", dgsts[0], dgst) } // Enumerate all digests if err := EnumerateAllDigests(ms); err != nil { t.Fatalf("failed to configure enumeration of all digests: %v", err) } dgsts, err = ms.Enumerate() if err != nil { t.Errorf("unexpected error enumerating manifest revisions: %v", err) } else { // _layers contain 2 links per one tarsum blob expCount := 1 + len(testLayers)*2 if len(dgsts) != expCount { t.Errorf("unexpected number of returned digests (%d != %d)", len(dgsts), expCount) } received := make(map[digest.Digest]struct{}) for _, dgst := range dgsts { received[dgst] = struct{}{} } if _, exists := received[dgst]; !exists { t.Errorf("expected manifest revision %s to be returned", dgst.String()) } for dgst := range testLayers { if _, exists := received[dgst]; !exists { t.Errorf("expected layer blob %s to be returned", dgst.String()) } } } // Grabs the tags and check that this tagged manifest is present tags, err := ms.Tags() if err != nil { t.Fatalf("unexpected error fetching tags: %v", err) } if len(tags) != 1 { t.Fatalf("unexpected tags returned: %v", tags) } if tags[0] != env.tag { t.Fatalf("unexpected tag found in tags: %v != %v", tags, []string{env.tag}) } // Now, push the same manifest with a different key pk2, err := libtrust.GenerateECP256PrivateKey() if err != nil { t.Fatalf("unexpected error generating private key: %v", err) } sm2, err := schema1.Sign(&m, pk2) if err != nil { t.Fatalf("unexpected error signing manifest: %v", err) } jws2, err := libtrust.ParsePrettySignature(sm2.Raw, "signatures") if err != nil { t.Fatalf("error parsing signature: %v", err) } sigs2, err := jws2.Signatures() if err != nil { t.Fatalf("unable to extract signatures: %v", err) } if len(sigs2) != 1 { t.Fatalf("unexpected number of signatures: %d != %d", len(sigs2), 1) } if err = ms.Put(sm2); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } fetched, err := ms.GetByTag(env.tag) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } if _, err := schema1.Verify(fetched); err != nil { t.Fatalf("unexpected error verifying manifest: %v", err) } // Assemble our payload and two signatures to get what we expect! expectedJWS, err := libtrust.NewJSONSignature(payload, sigs[0], sigs2[0]) if err != nil { t.Fatalf("unexpected error merging jws: %v", err) } expectedSigs, err := expectedJWS.Signatures() if err != nil { t.Fatalf("unexpected error getting expected signatures: %v", err) } receivedJWS, err := libtrust.ParsePrettySignature(fetched.Raw, "signatures") if err != nil { t.Fatalf("unexpected error parsing jws: %v", err) } receivedPayload, err := receivedJWS.Payload() if err != nil { t.Fatalf("unexpected error extracting received payload: %v", err) } if !bytes.Equal(receivedPayload, payload) { t.Fatalf("payloads are not equal") } receivedSigs, err := receivedJWS.Signatures() if err != nil { t.Fatalf("error getting signatures: %v", err) } for i, sig := range receivedSigs { if !bytes.Equal(sig, expectedSigs[i]) { t.Fatalf("mismatched signatures from remote: %v != %v", string(sig), string(expectedSigs[i])) } } // Test deleting manifests err = ms.Delete(dgst) if err != nil { t.Fatalf("unexpected an error deleting manifest by digest: %v", err) } exists, err = ms.Exists(dgst) if err != nil { t.Fatalf("Error querying manifest existence") } if exists { t.Errorf("Deleted manifest should not exist") } deletedManifest, err := ms.Get(dgst) if err == nil { t.Errorf("Unexpected success getting deleted manifest") } switch err.(type) { case distribution.ErrManifestUnknownRevision: break default: t.Errorf("Unexpected error getting deleted manifest: %s", reflect.ValueOf(err).Type()) } if deletedManifest != nil { t.Errorf("Deleted manifest get returned non-nil") } // Re-upload should restore manifest to a good state err = ms.Put(sm) if err != nil { t.Errorf("Error re-uploading deleted manifest") } exists, err = ms.Exists(dgst) if err != nil { t.Fatalf("Error querying manifest existence") } if !exists { t.Errorf("Restored manifest should exist") } deletedManifest, err = ms.Get(dgst) if err != nil { t.Errorf("Unexpected error getting manifest") } if deletedManifest == nil { t.Errorf("Deleted manifest get returned non-nil") } r, err := NewRegistry(ctx, env.driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) if err != nil { t.Fatalf("error creating registry: %v", err) } repo, err := r.Repository(ctx, env.name) if err != nil { t.Fatalf("unexpected error getting repo: %v", err) } ms, err = repo.Manifests(ctx) if err != nil { t.Fatal(err) } err = ms.Delete(dgst) if err == nil { t.Errorf("Unexpected success deleting while disabled") } }