Skip to content

Commit bb45ac4

Browse files
author
Oleg Bulatov
committed
Add -prune option to dockerregistry
Signed-off-by: Oleg Bulatov <[email protected]>
1 parent 322171b commit bb45ac4

File tree

3 files changed

+161
-3
lines changed

3 files changed

+161
-3
lines changed

pkg/cmd/dockerregistry/dockerregistry.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package dockerregistry
33
import (
44
"crypto/tls"
55
"crypto/x509"
6+
"flag"
67
"fmt"
78
"io"
89
"io/ioutil"
910
"net/http"
1011
"os"
12+
"strings"
1113
"time"
1214

1315
log "github.com/Sirupsen/logrus"
@@ -19,6 +21,8 @@ import (
1921
"github.com/docker/distribution/health"
2022
"github.com/docker/distribution/registry/auth"
2123
"github.com/docker/distribution/registry/handlers"
24+
"github.com/docker/distribution/registry/storage"
25+
"github.com/docker/distribution/registry/storage/driver/factory"
2226
"github.com/docker/distribution/uuid"
2327
"github.com/docker/distribution/version"
2428

@@ -35,8 +39,6 @@ import (
3539
_ "github.com/docker/distribution/registry/storage/driver/s3-aws"
3640
_ "github.com/docker/distribution/registry/storage/driver/swift"
3741

38-
"strings"
39-
4042
"github.com/openshift/origin/pkg/cmd/server/crypto"
4143
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
4244
"github.com/openshift/origin/pkg/dockerregistry/server"
@@ -45,8 +47,45 @@ import (
4547
registryconfig "github.com/openshift/origin/pkg/dockerregistry/server/configuration"
4648
)
4749

50+
var prune = flag.Bool("prune", false, "prune blobs from the storage and exit")
51+
52+
// ExecutePruner runs the pruner.
53+
func ExecutePruner(configFile io.Reader) {
54+
log.Infof("prune version=%s", version.Version)
55+
56+
config, _, err := registryconfig.Parse(configFile)
57+
if err != nil {
58+
log.Fatalf("error parsing configuration file: %s", err)
59+
}
60+
61+
ctx := context.Background()
62+
ctx, err = configureLogging(ctx, config)
63+
if err != nil {
64+
log.Fatalf("error configuring logging: %s", err)
65+
}
66+
67+
registryClient := server.NewRegistryClient(clientcmd.NewConfig().BindToFile())
68+
69+
storageDriver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters())
70+
if err != nil {
71+
log.Fatalf("error creating storage driver: %s", err)
72+
}
73+
74+
registry, err := storage.NewRegistry(ctx, storageDriver, storage.EnableDelete)
75+
if err != nil {
76+
log.Fatalf("error creating registry: %s", err)
77+
}
78+
79+
server.Prune(ctx, storageDriver, registry, registryClient)
80+
}
81+
4882
// Execute runs the Docker registry.
4983
func Execute(configFile io.Reader) {
84+
if *prune {
85+
ExecutePruner(configFile)
86+
return
87+
}
88+
5089
dockerConfig, extraConfig, err := registryconfig.Parse(configFile)
5190
if err != nil {
5291
log.Fatalf("error parsing configuration file: %s", err)

pkg/dockerregistry/server/errorblobstore.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ func (f statCrossMountCreateOptions) Apply(v interface{}) error {
151151
if err != nil {
152152
context.GetLogger(f.ctx).Infof("cannot mount blob %s from repository %s: %v - disabling cross-repo mount",
153153
opts.Mount.From.Digest().String(),
154-
opts.Mount.From.Name())
154+
opts.Mount.From.Name(),
155+
err)
155156
opts.Mount.ShouldMount = false
156157
return nil
157158
}

pkg/dockerregistry/server/prune.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package server
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/docker/distribution"
7+
"github.com/docker/distribution/context"
8+
"github.com/docker/distribution/digest"
9+
"github.com/docker/distribution/manifest/schema2"
10+
"github.com/docker/distribution/reference"
11+
"github.com/docker/distribution/registry/storage"
12+
"github.com/docker/distribution/registry/storage/driver"
13+
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
)
16+
17+
func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace, registryClient RegistryClient) {
18+
logger := context.GetLogger(ctx)
19+
20+
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
21+
if !ok {
22+
logger.Fatal("unable to convert Namespace to RepositoryEnumerator")
23+
}
24+
25+
oc, _, err := registryClient.Clients()
26+
if err != nil {
27+
logger.Fatalf("error getting clients: %s", err)
28+
}
29+
30+
imageList, err := oc.Images().List(metav1.ListOptions{})
31+
if err != nil {
32+
logger.Fatalf("error listing images: %s", err)
33+
}
34+
35+
inuse := make(map[string]string)
36+
for _, image := range imageList.Items {
37+
// Keep the manifest.
38+
inuse[image.Name] = image.DockerImageReference
39+
40+
// Keep the config for a schema 2 manifest.
41+
if image.DockerImageManifestMediaType == schema2.MediaTypeManifest {
42+
inuse[image.DockerImageMetadata.ID] = image.DockerImageReference
43+
}
44+
45+
// Keep image layers.
46+
for _, layer := range image.DockerImageLayers {
47+
inuse[layer.Name] = image.DockerImageReference
48+
}
49+
}
50+
51+
err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error {
52+
logger.Debugln("Processing repository", repoName)
53+
54+
named, err := reference.WithName(repoName)
55+
if err != nil {
56+
return fmt.Errorf("failed to parse repo name %s: %v", repoName, err)
57+
}
58+
59+
repository, err := registry.Repository(ctx, named)
60+
if err != nil {
61+
return err
62+
}
63+
64+
manifestService, err := repository.Manifests(ctx)
65+
if err != nil {
66+
return err
67+
}
68+
69+
manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator)
70+
if !ok {
71+
return fmt.Errorf("unable to convert ManifestService into ManifestEnumerator")
72+
}
73+
74+
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
75+
if imageReference, ok := inuse[string(dgst)]; ok {
76+
logger.Debugf("Keep manifest %s@%s (manifest belongs to image %s)", repoName, dgst, imageReference)
77+
return nil
78+
}
79+
80+
logger.Printf("Deleting manifest: %s@%s", repoName, dgst)
81+
err := manifestService.Delete(ctx, dgst)
82+
if err != nil {
83+
return fmt.Errorf("delete manifest %s: %s", dgst, err)
84+
}
85+
86+
return nil
87+
})
88+
if e, ok := err.(driver.PathNotFoundError); ok {
89+
logger.Warnf("prune manifests in repository %s: %s", repoName, e)
90+
} else if err != nil {
91+
return fmt.Errorf("prune manifests in repository %s: %s", repoName, err)
92+
}
93+
94+
return nil
95+
})
96+
if err != nil {
97+
logger.Fatal(err)
98+
}
99+
100+
logger.Debugln("Processing blobs")
101+
vacuum := storage.NewVacuum(ctx, storageDriver)
102+
err = registry.Blobs().Enumerate(ctx, func(dgst digest.Digest) error {
103+
if imageReference, ok := inuse[string(dgst)]; ok {
104+
logger.Debugf("Keep blob %s (blob belongs to image %s)", dgst, imageReference)
105+
return nil
106+
}
107+
108+
err := vacuum.RemoveBlob(string(dgst))
109+
if err != nil {
110+
return fmt.Errorf("delete blob %s: %s", dgst, err)
111+
}
112+
113+
return nil
114+
})
115+
if err != nil {
116+
logger.Fatal(err)
117+
}
118+
}

0 commit comments

Comments
 (0)