Fix signature handling with GC.
If a schema 1 manifest is uploaded with the `disablesignaturestore` option set to true, then no signatures will exist. Handle this case. If a schema 1 manifest is pushed, deleted, garbage collected and pushed again, the repository will contain signature links from the first version, but the blobs will not exist. Disable the signature store in the garbage-collect command so signatures are not fetched. Signed-off-by: Richard Scothern <richard.scothern@docker.com>
This commit is contained in:
parent
15e3ffb3f2
commit
31ece3d3b6
3 changed files with 40 additions and 21 deletions
|
@ -13,20 +13,18 @@ import (
|
||||||
"github.com/docker/distribution/registry/storage"
|
"github.com/docker/distribution/registry/storage"
|
||||||
"github.com/docker/distribution/registry/storage/driver"
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
"github.com/docker/distribution/registry/storage/driver/factory"
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
||||||
|
"github.com/docker/libtrust"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func emit(ctx context.Context, s string) {
|
func emit(format string, a ...interface{}) {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
context.GetLogger(ctx).Infof("gc: %s", s)
|
fmt.Printf(format, a...)
|
||||||
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error {
|
func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace) error {
|
||||||
registry, err := storage.NewRegistry(ctx, storageDriver)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to construct registry: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
|
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -35,8 +33,8 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error
|
||||||
|
|
||||||
// mark
|
// mark
|
||||||
markSet := make(map[digest.Digest]struct{})
|
markSet := make(map[digest.Digest]struct{})
|
||||||
err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error {
|
err := repositoryEnumerator.Enumerate(ctx, func(repoName string) error {
|
||||||
emit(ctx, fmt.Sprint(repoName))
|
emit(repoName)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
named, err := reference.ParseNamed(repoName)
|
named, err := reference.ParseNamed(repoName)
|
||||||
|
@ -59,8 +57,8 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error
|
||||||
}
|
}
|
||||||
|
|
||||||
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
|
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
|
||||||
// Mark the manifest's blo
|
// Mark the manifest's blob
|
||||||
emit(ctx, fmt.Sprintf("%s: adding manifest %s ", repoName, dgst))
|
emit("%s: marking manifest %s ", repoName, dgst)
|
||||||
markSet[dgst] = struct{}{}
|
markSet[dgst] = struct{}{}
|
||||||
|
|
||||||
manifest, err := manifestService.Get(ctx, dgst)
|
manifest, err := manifestService.Get(ctx, dgst)
|
||||||
|
@ -71,7 +69,7 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error
|
||||||
descriptors := manifest.References()
|
descriptors := manifest.References()
|
||||||
for _, descriptor := range descriptors {
|
for _, descriptor := range descriptors {
|
||||||
markSet[descriptor.Digest] = struct{}{}
|
markSet[descriptor.Digest] = struct{}{}
|
||||||
emit(ctx, fmt.Sprintf("%s: marking blob %v", repoName, descriptor))
|
emit("%s: marking blob %s", repoName, descriptor.Digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch manifest.(type) {
|
switch manifest.(type) {
|
||||||
|
@ -85,13 +83,13 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error
|
||||||
return fmt.Errorf("failed to get signatures for signed manifest: %v", err)
|
return fmt.Errorf("failed to get signatures for signed manifest: %v", err)
|
||||||
}
|
}
|
||||||
for _, signatureDigest := range signatures {
|
for _, signatureDigest := range signatures {
|
||||||
emit(ctx, fmt.Sprintf("%s: marking signature %s", repoName, signatureDigest))
|
emit("%s: marking signature %s", repoName, signatureDigest)
|
||||||
markSet[signatureDigest] = struct{}{}
|
markSet[signatureDigest] = struct{}{}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case *schema2.DeserializedManifest:
|
case *schema2.DeserializedManifest:
|
||||||
config := manifest.(*schema2.DeserializedManifest).Config
|
config := manifest.(*schema2.DeserializedManifest).Config
|
||||||
emit(ctx, fmt.Sprintf("%s: marking configuration %s", repoName, config.Digest))
|
emit("%s: marking configuration %s", repoName, config.Digest)
|
||||||
markSet[config.Digest] = struct{}{}
|
markSet[config.Digest] = struct{}{}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -120,11 +118,12 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver) error
|
||||||
return fmt.Errorf("error enumerating blobs: %v", err)
|
return fmt.Errorf("error enumerating blobs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit("\n%d blobs marked, %d blobs eligible for deletion", len(markSet), len(deleteSet))
|
||||||
// Construct vacuum
|
// Construct vacuum
|
||||||
vacuum := storage.NewVacuum(ctx, storageDriver)
|
vacuum := storage.NewVacuum(ctx, storageDriver)
|
||||||
for dgst := range deleteSet {
|
for dgst := range deleteSet {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
emit(ctx, fmt.Sprintf("deleting %s", dgst))
|
emit("deleting %s", dgst)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = vacuum.RemoveBlob(string(dgst))
|
err = vacuum.RemoveBlob(string(dgst))
|
||||||
|
@ -168,7 +167,19 @@ var GCCmd = &cobra.Command{
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = markAndSweep(ctx, driver)
|
k, err := libtrust.GenerateECP256PrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
registry, err := storage.NewRegistry(ctx, driver, storage.DisableSchema1Signatures, storage.Schema1SigningKey(k))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to construct registry: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = markAndSweep(ctx, driver, registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "failed to garbage collect: %v", err)
|
fmt.Fprintf(os.Stderr, "failed to garbage collect: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -161,7 +161,7 @@ func TestNoDeletionNoEffect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run GC
|
// Run GC
|
||||||
err = markAndSweep(context.Background(), inmemoryDriver)
|
err = markAndSweep(context.Background(), inmemoryDriver, registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ func TestDeletionHasEffect(t *testing.T) {
|
||||||
manifests.Delete(ctx, image3.manifestDigest)
|
manifests.Delete(ctx, image3.manifestDigest)
|
||||||
|
|
||||||
// Run GC
|
// Run GC
|
||||||
err = markAndSweep(context.Background(), inmemoryDriver)
|
err = markAndSweep(context.Background(), inmemoryDriver, registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -327,7 +327,7 @@ func TestOrphanBlobDeleted(t *testing.T) {
|
||||||
uploadRandomSchema2Image(t, repo)
|
uploadRandomSchema2Image(t, repo)
|
||||||
|
|
||||||
// Run GC
|
// Run GC
|
||||||
err = markAndSweep(context.Background(), inmemoryDriver)
|
err = markAndSweep(context.Background(), inmemoryDriver, registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/docker/distribution/manifest/manifestlist"
|
"github.com/docker/distribution/manifest/manifestlist"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
|
"github.com/docker/distribution/registry/storage/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ManifestHandler gets and puts manifests of a particular type.
|
// A ManifestHandler gets and puts manifests of a particular type.
|
||||||
|
@ -161,13 +162,20 @@ func (ms *manifestStore) GetSignatures(ctx context.Context, manifestDigest diges
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var digests []digest.Digest
|
||||||
alg := string(digest.SHA256)
|
alg := string(digest.SHA256)
|
||||||
signaturePaths, err := ms.blobStore.driver.List(ctx, path.Join(signaturesPath, alg))
|
signaturePaths, err := ms.blobStore.driver.List(ctx, path.Join(signaturesPath, alg))
|
||||||
if err != nil {
|
|
||||||
|
switch err.(type) {
|
||||||
|
case nil:
|
||||||
|
break
|
||||||
|
case driver.PathNotFoundError:
|
||||||
|
// Manifest may have been pushed with signature store disabled
|
||||||
|
return digests, nil
|
||||||
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var digests []digest.Digest
|
|
||||||
for _, sigPath := range signaturePaths {
|
for _, sigPath := range signaturePaths {
|
||||||
sigdigest, err := digest.ParseDigest(alg + ":" + path.Base(sigPath))
|
sigdigest, err := digest.ParseDigest(alg + ":" + path.Base(sigPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue