Skip to content

Commit c7d0828

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

File tree

3 files changed

+230
-4
lines changed

3 files changed

+230
-4
lines changed

pkg/cmd/dockerregistry/dockerregistry.go

Lines changed: 62 additions & 3 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,8 +21,10 @@ 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"
23-
"github.com/docker/distribution/version"
27+
distversion "github.com/docker/distribution/version"
2428

2529
_ "github.com/docker/distribution/registry/auth/htpasswd"
2630
_ "github.com/docker/distribution/registry/auth/token"
@@ -35,18 +39,73 @@ import (
3539
_ "github.com/docker/distribution/registry/storage/driver/s3-aws"
3640
_ "github.com/docker/distribution/registry/storage/driver/swift"
3741

38-
"strings"
42+
kubeversion "k8s.io/kubernetes/pkg/version"
3943

4044
"github.com/openshift/origin/pkg/cmd/server/crypto"
4145
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
4246
"github.com/openshift/origin/pkg/dockerregistry/server"
4347
"github.com/openshift/origin/pkg/dockerregistry/server/api"
4448
"github.com/openshift/origin/pkg/dockerregistry/server/audit"
4549
registryconfig "github.com/openshift/origin/pkg/dockerregistry/server/configuration"
50+
"github.com/openshift/origin/pkg/version"
4651
)
4752

53+
var prune = flag.Bool("prune", false, "prune blobs from the storage and exit")
54+
55+
func versionFields() log.Fields {
56+
return log.Fields{
57+
"distribution_version": distversion.Version,
58+
"kubernetes_version": kubeversion.Get(),
59+
"openshift_version": version.Get(),
60+
}
61+
}
62+
63+
// ExecutePruner runs the pruner.
64+
func ExecutePruner(configFile io.Reader) {
65+
log.WithFields(versionFields()).Info("start prune")
66+
67+
config, _, err := registryconfig.Parse(configFile)
68+
if err != nil {
69+
log.Fatalf("error parsing configuration file: %s", err)
70+
}
71+
72+
// A lot of installations have the 'debug' log level in their config files,
73+
// but it's too verbose for pruning. Therefore we ignore it, but we still
74+
// respect overrides using environment variables.
75+
config.Loglevel = ""
76+
config.Log.Level = configuration.Loglevel(os.Getenv("REGISTRY_LOG_LEVEL"))
77+
if config.Log.Level == "" {
78+
config.Log.Level = "info"
79+
}
80+
81+
ctx := context.Background()
82+
ctx, err = configureLogging(ctx, config)
83+
if err != nil {
84+
log.Fatalf("error configuring logging: %s", err)
85+
}
86+
87+
registryClient := server.NewRegistryClient(clientcmd.NewConfig().BindToFile())
88+
89+
storageDriver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters())
90+
if err != nil {
91+
log.Fatalf("error creating storage driver: %s", err)
92+
}
93+
94+
registry, err := storage.NewRegistry(ctx, storageDriver, storage.EnableDelete)
95+
if err != nil {
96+
log.Fatalf("error creating registry: %s", err)
97+
}
98+
99+
server.Prune(ctx, storageDriver, registry, registryClient)
100+
}
101+
48102
// Execute runs the Docker registry.
49103
func Execute(configFile io.Reader) {
104+
if *prune {
105+
ExecutePruner(configFile)
106+
return
107+
}
108+
50109
dockerConfig, extraConfig, err := registryconfig.Parse(configFile)
51110
if err != nil {
52111
log.Fatalf("error parsing configuration file: %s", err)
@@ -64,7 +123,7 @@ func Execute(configFile io.Reader) {
64123
registryClient := server.NewRegistryClient(clientcmd.NewConfig().BindToFile())
65124
ctx = server.WithRegistryClient(ctx, registryClient)
66125

67-
log.Infof("version=%s", version.Version)
126+
log.WithFields(versionFields()).Info("start registry")
68127
// inject a logger into the uuid library. warns us if there is a problem
69128
// with uuid generation under low entropy.
70129
uuid.Loggerf = context.GetLogger(ctx).Warnf

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: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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+
kerrors "k8s.io/apimachinery/pkg/api/errors"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
17+
imageapi "github.com/openshift/origin/pkg/image/api"
18+
)
19+
20+
func imageStreamHasManifestDigest(is *imageapi.ImageStream, dgst digest.Digest) bool {
21+
for _, tagEventList := range is.Status.Tags {
22+
for _, tagEvent := range tagEventList.Items {
23+
if tagEvent.Image == string(dgst) {
24+
return true
25+
}
26+
}
27+
}
28+
return false
29+
}
30+
31+
// Prune removes blobs which are not used by Images in OpenShift.
32+
//
33+
// TODO(dmage): remove layer links to a blob if the blob is removed or it doesn't belong to the ImageStream.
34+
// TODO(dmage): keep young blobs (docker/distribution#2297).
35+
func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace, registryClient RegistryClient) {
36+
logger := context.GetLogger(ctx)
37+
38+
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
39+
if !ok {
40+
logger.Fatal("unable to convert Namespace to RepositoryEnumerator")
41+
}
42+
43+
oc, _, err := registryClient.Clients()
44+
if err != nil {
45+
logger.Fatalf("error getting clients: %s", err)
46+
}
47+
48+
imageList, err := oc.Images().List(metav1.ListOptions{})
49+
if err != nil {
50+
logger.Fatalf("error listing images: %s", err)
51+
}
52+
53+
inuse := make(map[string]string)
54+
for _, image := range imageList.Items {
55+
// Keep the manifest.
56+
inuse[image.Name] = image.DockerImageReference
57+
58+
// Keep the config for a schema 2 manifest.
59+
if image.DockerImageManifestMediaType == schema2.MediaTypeManifest {
60+
inuse[image.DockerImageMetadata.ID] = image.DockerImageReference
61+
}
62+
63+
// Keep image layers.
64+
for _, layer := range image.DockerImageLayers {
65+
inuse[layer.Name] = image.DockerImageReference
66+
}
67+
}
68+
69+
var reposToDelete []string
70+
err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error {
71+
logger.Debugln("Processing repository", repoName)
72+
73+
named, err := reference.WithName(repoName)
74+
if err != nil {
75+
return fmt.Errorf("failed to parse the repo name %s: %v", repoName, err)
76+
}
77+
78+
ref, err := imageapi.ParseDockerImageReference(repoName)
79+
if err != nil {
80+
return fmt.Errorf("failed to parse the image reference %s: %v", repoName, err)
81+
}
82+
83+
is, err := oc.ImageStreams(ref.Namespace).Get(ref.Name, metav1.GetOptions{})
84+
if kerrors.IsNotFound(err) {
85+
logger.Printf("The image stream %s/%s is not found, will remove the whole repository", ref.Namespace, ref.Name)
86+
87+
// We cannot delete the repository at this point, because it would break Enumerate.
88+
reposToDelete = append(reposToDelete, repoName)
89+
90+
return nil
91+
} else if err != nil {
92+
return fmt.Errorf("failed to get the image stream %s: %v", repoName, err)
93+
}
94+
95+
repository, err := registry.Repository(ctx, named)
96+
if err != nil {
97+
return err
98+
}
99+
100+
manifestService, err := repository.Manifests(ctx)
101+
if err != nil {
102+
return err
103+
}
104+
105+
manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator)
106+
if !ok {
107+
return fmt.Errorf("unable to convert ManifestService into ManifestEnumerator")
108+
}
109+
110+
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
111+
if imageReference, ok := inuse[string(dgst)]; ok && imageStreamHasManifestDigest(is, dgst) {
112+
logger.Debugf("Keeping the manifest %s@%s (it belongs to the image %s)", repoName, dgst, imageReference)
113+
return nil
114+
}
115+
116+
logger.Printf("Deleting the manifest: %s@%s", repoName, dgst)
117+
err = manifestService.Delete(ctx, dgst)
118+
if err != nil {
119+
return fmt.Errorf("failed to delete the manifest %s: %s", dgst, err)
120+
}
121+
122+
return nil
123+
})
124+
if e, ok := err.(driver.PathNotFoundError); ok {
125+
logger.Printf("Skipped manifests pruning for the repository %s: %s", repoName, e)
126+
} else if err != nil {
127+
return fmt.Errorf("failed to prune manifests in the repository %s: %s", repoName, err)
128+
}
129+
130+
return nil
131+
})
132+
if e, ok := err.(driver.PathNotFoundError); ok {
133+
logger.Warnf("No repositories found: %s", e)
134+
return
135+
} else if err != nil {
136+
logger.Fatal(err)
137+
}
138+
139+
vacuum := storage.NewVacuum(ctx, storageDriver)
140+
141+
logger.Debugln("Removing repositories")
142+
for _, repoName := range reposToDelete {
143+
err = vacuum.RemoveRepository(repoName)
144+
if err != nil {
145+
logger.Fatal("Failed to remove the repository %s: %v", repoName, err)
146+
}
147+
}
148+
149+
logger.Debugln("Processing blobs")
150+
err = registry.Blobs().Enumerate(ctx, func(dgst digest.Digest) error {
151+
if imageReference, ok := inuse[string(dgst)]; ok {
152+
logger.Debugf("Keeping the blob %s (it belongs to the image %s)", dgst, imageReference)
153+
return nil
154+
}
155+
156+
err := vacuum.RemoveBlob(string(dgst))
157+
if err != nil {
158+
return fmt.Errorf("failed to delete the blob %s: %s", dgst, err)
159+
}
160+
161+
return nil
162+
})
163+
if err != nil {
164+
logger.Fatal(err)
165+
}
166+
}

0 commit comments

Comments
 (0)