Browse code

Image pruning

Remove fronting mux router and use route and auth extensions added
upstream in registry.

Move admin registry handlers into pkg/dockerregistry/server.

Rename --older-than to --keep-younger-than and make it a time.Duration.

Andy Goldstein authored on 2015/05/09 01:51:36
Showing 7 changed files
... ...
@@ -3,7 +3,6 @@ package dockerregistry
3 3
 import (
4 4
 	"crypto/tls"
5 5
 	"crypto/x509"
6
-	"encoding/json"
7 6
 	"fmt"
8 7
 	"io"
9 8
 	"io/ioutil"
... ...
@@ -13,174 +12,15 @@ import (
13 13
 	log "github.com/Sirupsen/logrus"
14 14
 	"github.com/docker/distribution/configuration"
15 15
 	"github.com/docker/distribution/context"
16
-	"github.com/docker/distribution/health"
16
+	"github.com/docker/distribution/registry/auth"
17 17
 	"github.com/docker/distribution/registry/handlers"
18 18
 	_ "github.com/docker/distribution/registry/storage/driver/filesystem"
19 19
 	_ "github.com/docker/distribution/registry/storage/driver/s3"
20 20
 	"github.com/docker/distribution/version"
21 21
 	gorillahandlers "github.com/gorilla/handlers"
22
-	"github.com/gorilla/mux"
23
-	_ "github.com/openshift/origin/pkg/dockerregistry/server"
22
+	"github.com/openshift/origin/pkg/dockerregistry/server"
24 23
 )
25 24
 
26
-func newOpenShiftHandler(app *handlers.App) http.Handler {
27
-	router := mux.NewRouter()
28
-	router.HandleFunc("/healthz", health.StatusHandler)
29
-	// TODO add https scheme
30
-	router.HandleFunc("/admin/layers", deleteLayerFunc(app)).Methods("DELETE")
31
-	//router.HandleFunc("/admin/manifests", deleteManifestFunc(app)).Methods("DELETE")
32
-	// delegate to the registry if it's not 1 of the OpenShift routes
33
-	router.NotFoundHandler = app
34
-
35
-	return router
36
-}
37
-
38
-// DeleteLayersRequest is a mapping from layers to the image repositories that
39
-// reference them. Below is a sample request:
40
-//
41
-// {
42
-//   "layer1": ["repo1", "repo2"],
43
-// 	 "layer2": ["repo1", "repo3"],
44
-// 	 ...
45
-// }
46
-type DeleteLayersRequest map[string][]string
47
-
48
-// AddLayer adds a layer to the request if it doesn't already exist.
49
-func (r DeleteLayersRequest) AddLayer(layer string) {
50
-	if _, ok := r[layer]; !ok {
51
-		r[layer] = []string{}
52
-	}
53
-}
54
-
55
-// AddStream adds an image stream reference to the layer.
56
-func (r DeleteLayersRequest) AddStream(layer, stream string) {
57
-	r[layer] = append(r[layer], stream)
58
-}
59
-
60
-type DeleteLayersResponse struct {
61
-	Result string
62
-	Errors map[string][]string
63
-}
64
-
65
-// deleteLayerFunc returns an http.HandlerFunc that is able to fully delete a
66
-// layer from storage.
67
-func deleteLayerFunc(app *handlers.App) http.HandlerFunc {
68
-	return func(w http.ResponseWriter, req *http.Request) {
69
-		defer req.Body.Close()
70
-		log.Infof("deleteLayerFunc invoked")
71
-
72
-		//TODO verify auth
73
-
74
-		body, err := ioutil.ReadAll(req.Body)
75
-		if err != nil {
76
-			//TODO
77
-			log.Errorf("Error reading body: %v", err)
78
-			w.WriteHeader(http.StatusInternalServerError)
79
-			return
80
-		}
81
-
82
-		deletions := DeleteLayersRequest{}
83
-		err = json.Unmarshal(body, &deletions)
84
-		if err != nil {
85
-			//TODO
86
-			log.Errorf("Error unmarshaling body: %v", err)
87
-			w.WriteHeader(http.StatusInternalServerError)
88
-			return
89
-		}
90
-
91
-		adminService := app.Registry().AdminService()
92
-		errs := map[string][]error{}
93
-		for layer, repos := range deletions {
94
-			log.Infof("Deleting layer=%q, repos=%v", layer, repos)
95
-			errs[layer] = adminService.DeleteLayer(layer, repos)
96
-		}
97
-
98
-		log.Infof("errs=%v", errs)
99
-
100
-		var result string
101
-		switch len(errs) {
102
-		case 0:
103
-			result = "success"
104
-		default:
105
-			result = "failure"
106
-		}
107
-
108
-		response := DeleteLayersResponse{
109
-			Result: result,
110
-			Errors: map[string][]string{},
111
-		}
112
-
113
-		for layer, layerErrors := range errs {
114
-			response.Errors[layer] = []string{}
115
-			for _, err := range layerErrors {
116
-				response.Errors[layer] = append(response.Errors[layer], err.Error())
117
-			}
118
-		}
119
-
120
-		buf, err := json.Marshal(&response)
121
-		if err != nil {
122
-			w.Write([]byte(fmt.Sprintf("Error marshaling response: %v", err)))
123
-			w.WriteHeader(http.StatusInternalServerError)
124
-			return
125
-		}
126
-
127
-		w.Write(buf)
128
-		w.WriteHeader(http.StatusOK)
129
-	}
130
-}
131
-
132
-/*
133
-type DeleteManifestsRequest map[string][]string
134
-
135
-func (r *DeleteManifestsRequest) AddManifest(revision string) {
136
-	if _, ok := r[revision]; !ok {
137
-		r[revision] = []string{}
138
-	}
139
-}
140
-
141
-func (r *DeleteManifestsRequest) AddStream(revision, stream string) {
142
-	r[revision] = append(r[revision], stream)
143
-}
144
-
145
-func deleteManifestsFunc(app *handlers.App) http.HandlerFunc {
146
-	return func(w http.ResponseWriter, req *http.Request) {
147
-		defer req.Body.Close()
148
-
149
-		//TODO verify auth
150
-
151
-		body, err := ioutil.ReadAll(req.Body)
152
-		if err != nil {
153
-			//TODO
154
-			log.Errorf("Error reading body: %v", err)
155
-			w.WriteHeader(http.StatusInternalServerError)
156
-			return
157
-		}
158
-
159
-		deletions := DeleteManifestsRequest{}
160
-		err = json.Unmarshal(body, &deletions)
161
-		if err != nil {
162
-			//TODO
163
-			log.Errorf("Error unmarshaling body: %v", err)
164
-			w.WriteHeader(http.StatusInternalServerError)
165
-			return
166
-		}
167
-
168
-		adminService := app.Registry().AdminService()
169
-		errs := []error{}
170
-		for revision, repos := range deletions {
171
-			log.Infof("Deleting manifest revision=%q, repos=%v", revision, repos)
172
-			manifestErrs := adminService.DeleteManifest(revision, repos)
173
-			errs = append(errs, manifestErrs...)
174
-		}
175
-
176
-		log.Infof("errs=%v", errs)
177
-
178
-		//TODO write response
179
-		w.WriteHeader(http.StatusOK)
180
-	}
181
-}
182
-*/
183
-
184 25
 // Execute runs the Docker registry.
185 26
 func Execute(configFile io.Reader) {
186 27
 	config, err := configuration.Parse(configFile)
... ...
@@ -199,8 +39,49 @@ func Execute(configFile io.Reader) {
199 199
 	ctx := context.Background()
200 200
 
201 201
 	app := handlers.NewApp(ctx, *config)
202
-	handler := newOpenShiftHandler(app)
203
-	handler = gorillahandlers.CombinedLoggingHandler(os.Stdout, handler)
202
+
203
+	// register OpenShift routes
204
+	app.RegisterRoute(app.NewRoute().Path("/healthz"), server.HealthzHandler, handlers.NameNotRequired, handlers.NoCustomAccessRecords)
205
+
206
+	// TODO add https scheme
207
+	adminRouter := app.NewRoute().PathPrefix("/admin/").Subrouter()
208
+
209
+	pruneAccessRecords := func(*http.Request) []auth.Access {
210
+		return []auth.Access{
211
+			{
212
+				Resource: auth.Resource{
213
+					Type: "admin",
214
+				},
215
+				Action: "prune",
216
+			},
217
+		}
218
+	}
219
+
220
+	app.RegisterRoute(
221
+		// DELETE /admin/layers
222
+		adminRouter.Path("/layers").Methods("DELETE"),
223
+		// handler
224
+		server.DeleteLayersHandler(app.Registry().AdminService()),
225
+		// repo name not required in url
226
+		handlers.NameNotRequired,
227
+		// custom access records
228
+		pruneAccessRecords,
229
+	)
230
+
231
+	app.RegisterRoute(
232
+		// DELETE /admin/manifests
233
+		adminRouter.Path("/manifests").Methods("DELETE"),
234
+		// handler
235
+		server.DeleteManifestsHandler(app.Registry().AdminService()),
236
+		// repo name not required in url
237
+		handlers.NameNotRequired,
238
+		// custom access records
239
+		pruneAccessRecords,
240
+	)
241
+
242
+	//app.RegisterRoute(app.NewRoute().Path("/admin/repositories/{repository}/").Methods("DELETE"), server.DeleteRepositoryHandler(app.Registry().AdminService()), func(*http.Request) bool { return true })
243
+
244
+	handler := gorillahandlers.CombinedLoggingHandler(os.Stdout, app)
204 245
 
205 246
 	if config.HTTP.TLS.Certificate == "" {
206 247
 		context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr)
... ...
@@ -4,13 +4,14 @@ import (
4 4
 	"fmt"
5 5
 	"io"
6 6
 	"net/http"
7
+	"time"
7 8
 
8 9
 	"github.com/golang/glog"
10
+	"github.com/openshift/origin/pkg/dockerregistry/server"
9 11
 	imageapi "github.com/openshift/origin/pkg/image/api"
10 12
 	"github.com/openshift/origin/pkg/image/prune"
11 13
 	"github.com/spf13/cobra"
12 14
 
13
-	"github.com/openshift/origin/pkg/cmd/dockerregistry"
14 15
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
15 16
 )
16 17
 
... ...
@@ -18,16 +19,16 @@ const longDesc = `
18 18
 `
19 19
 
20 20
 type config struct {
21
-	DryRun                    bool
22
-	MinimumResourcePruningAge int
23
-	TagRevisionsToKeep        int
21
+	DryRun             bool
22
+	KeepYoungerThan    time.Duration
23
+	TagRevisionsToKeep int
24 24
 }
25 25
 
26 26
 func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command {
27 27
 	cfg := &config{
28
-		DryRun: true,
29
-		MinimumResourcePruningAge: 60,
30
-		TagRevisionsToKeep:        3,
28
+		DryRun:             true,
29
+		KeepYoungerThan:    60 * time.Minute,
30
+		TagRevisionsToKeep: 3,
31 31
 	}
32 32
 
33 33
 	cmd := &cobra.Command{
... ...
@@ -45,7 +46,7 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri
45 45
 				glog.Fatalf("Error getting client: %v", err)
46 46
 			}
47 47
 
48
-			pruner, err := prune.NewImagePruner(cfg.MinimumResourcePruningAge, cfg.TagRevisionsToKeep, osClient, osClient, kClient, kClient, osClient, osClient, osClient)
48
+			pruner, err := prune.NewImagePruner(cfg.KeepYoungerThan, cfg.TagRevisionsToKeep, osClient, osClient, kClient, kClient, osClient, osClient, osClient)
49 49
 			if err != nil {
50 50
 				glog.Fatalf("Error creating image pruner: %v", err)
51 51
 			}
... ...
@@ -64,7 +65,7 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri
64 64
 					prune.DescribingImagePruneFunc(out)(image, referencedStreams)
65 65
 					return prune.DeletingImagePruneFunc(osClient.Images(), osClient)(image, referencedStreams)
66 66
 				}
67
-				layerPruneFunc = func(registryURL string, req dockerregistry.DeleteLayersRequest) (error, map[string][]error) {
67
+				layerPruneFunc = func(registryURL string, req server.DeleteLayersRequest) (error, map[string][]error) {
68 68
 					prune.DescribingLayerPruneFunc(out)(registryURL, req)
69 69
 					return prune.DeletingLayerPruneFunc(http.DefaultClient)(registryURL, req)
70 70
 				}
... ...
@@ -79,7 +80,7 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri
79 79
 	}
80 80
 
81 81
 	cmd.Flags().BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "Perform an image pruning dry-run, displaying what would be deleted but not actually deleting anything (default=true).")
82
-	cmd.Flags().IntVar(&cfg.MinimumResourcePruningAge, "older-than", cfg.MinimumResourcePruningAge, "Specify the minimum age for an image to be prunable, as well as the minimum age for an image stream or pod that references an image to be prunable.")
82
+	cmd.Flags().DurationVar(&cfg.KeepYoungerThan, "keep-younger-than", cfg.KeepYoungerThan, "Specify the minimum age for an image to be prunable, as well as the minimum age for an image stream or pod that references an image to be prunable.")
83 83
 	cmd.Flags().IntVar(&cfg.TagRevisionsToKeep, "keep-tag-revisions", cfg.TagRevisionsToKeep, "Specify the number of image revisions for a tag in an image stream that will be preserved.")
84 84
 
85 85
 	return cmd
86 86
new file mode 100644
... ...
@@ -0,0 +1,188 @@
0
+package server
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"net/http"
6
+
7
+	log "github.com/Sirupsen/logrus"
8
+	"github.com/docker/distribution"
9
+	"github.com/docker/distribution/digest"
10
+	"github.com/docker/distribution/registry/handlers"
11
+)
12
+
13
+// DeleteLayersRequest is a mapping from layers to the image repositories that
14
+// reference them. Below is a sample request:
15
+//
16
+// {
17
+//   "layer1": ["repo1", "repo2"],
18
+//   "layer2": ["repo1", "repo3"],
19
+//   ...
20
+// }
21
+type DeleteLayersRequest map[string][]string
22
+
23
+// AddLayer adds a layer to the request if it doesn't already exist.
24
+func (r DeleteLayersRequest) AddLayer(layer string) {
25
+	if _, ok := r[layer]; !ok {
26
+		r[layer] = []string{}
27
+	}
28
+}
29
+
30
+// AddStream adds an image stream reference to the layer.
31
+func (r DeleteLayersRequest) AddStream(layer, stream string) {
32
+	r[layer] = append(r[layer], stream)
33
+}
34
+
35
+type DeleteLayersResponse struct {
36
+	Result string
37
+	Errors map[string][]string
38
+}
39
+
40
+// deleteLayerFunc returns an http.HandlerFunc that is able to fully delete a
41
+// layer from storage.
42
+func DeleteLayersHandler(adminService distribution.AdminService) func(ctx *handlers.Context, r *http.Request) http.Handler {
43
+	return func(ctx *handlers.Context, r *http.Request) http.Handler {
44
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
45
+			defer req.Body.Close()
46
+			log.Infof("deleteLayerFunc invoked")
47
+
48
+			decoder := json.NewDecoder(req.Body)
49
+			deletions := DeleteLayersRequest{}
50
+			if err := decoder.Decode(&deletions); err != nil {
51
+				//TODO
52
+				log.Errorf("Error unmarshaling body: %v", err)
53
+				w.WriteHeader(http.StatusInternalServerError)
54
+				return
55
+			}
56
+
57
+			errs := map[string][]error{}
58
+			for layer, repos := range deletions {
59
+				log.Infof("Deleting layer=%q, repos=%v", layer, repos)
60
+				errs[layer] = adminService.DeleteLayer(layer, repos)
61
+			}
62
+
63
+			log.Infof("errs=%v", errs)
64
+
65
+			var result string
66
+			switch len(errs) {
67
+			case 0:
68
+				result = "success"
69
+			default:
70
+				result = "failure"
71
+			}
72
+
73
+			response := DeleteLayersResponse{
74
+				Result: result,
75
+				Errors: map[string][]string{},
76
+			}
77
+
78
+			for layer, layerErrors := range errs {
79
+				response.Errors[layer] = []string{}
80
+				for _, err := range layerErrors {
81
+					response.Errors[layer] = append(response.Errors[layer], err.Error())
82
+				}
83
+			}
84
+
85
+			w.Header().Set("Content-Type", "application/json; charset=utf-8")
86
+			encoder := json.NewEncoder(w)
87
+			if err := encoder.Encode(&response); err != nil {
88
+				w.Write([]byte(fmt.Sprintf("Error marshaling response: %v", err)))
89
+				w.WriteHeader(http.StatusInternalServerError)
90
+				return
91
+			}
92
+
93
+			w.WriteHeader(http.StatusOK)
94
+		})
95
+	}
96
+}
97
+
98
+type DeleteManifestsRequest map[string][]string
99
+
100
+func (r DeleteManifestsRequest) AddManifest(revision string) {
101
+	if _, ok := r[revision]; !ok {
102
+		r[revision] = []string{}
103
+	}
104
+}
105
+
106
+func (r DeleteManifestsRequest) AddStream(revision, stream string) {
107
+	r[revision] = append(r[revision], stream)
108
+}
109
+
110
+type DeleteManifestsResponse struct {
111
+	Result string
112
+	Errors map[string][]string
113
+}
114
+
115
+func DeleteManifestsHandler(adminService distribution.AdminService) func(ctx *handlers.Context, r *http.Request) http.Handler {
116
+	return func(ctx *handlers.Context, r *http.Request) http.Handler {
117
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
118
+			defer req.Body.Close()
119
+
120
+			decoder := json.NewDecoder(req.Body)
121
+			deletions := DeleteManifestsRequest{}
122
+			if err := decoder.Decode(&deletions); err != nil {
123
+				//TODO
124
+				log.Errorf("Error unmarshaling body: %v", err)
125
+				w.WriteHeader(http.StatusInternalServerError)
126
+				return
127
+			}
128
+
129
+			errs := map[string][]error{}
130
+			for revision, repos := range deletions {
131
+				log.Infof("Deleting manifest revision=%q, repos=%v", revision, repos)
132
+				dgst, err := digest.ParseDigest(revision)
133
+				if err != nil {
134
+					errs[revision] = []error{fmt.Errorf("Error parsing revision %q: %v", revision, err)}
135
+					continue
136
+				}
137
+				errs[revision] = adminService.DeleteManifest(dgst, repos)
138
+			}
139
+
140
+			log.Infof("errs=%v", errs)
141
+
142
+			var result string
143
+			switch len(errs) {
144
+			case 0:
145
+				result = "success"
146
+			default:
147
+				result = "failure"
148
+			}
149
+
150
+			response := DeleteManifestsResponse{
151
+				Result: result,
152
+				Errors: map[string][]string{},
153
+			}
154
+
155
+			for revision, revisionErrors := range errs {
156
+				response.Errors[revision] = []string{}
157
+				for _, err := range revisionErrors {
158
+					response.Errors[revision] = append(response.Errors[revision], err.Error())
159
+				}
160
+			}
161
+
162
+			w.Header().Set("Content-Type", "application/json; charset=utf-8")
163
+			encoder := json.NewEncoder(w)
164
+			if err := encoder.Encode(&response); err != nil {
165
+				w.Write([]byte(fmt.Sprintf("Error marshaling response: %v", err)))
166
+				w.WriteHeader(http.StatusInternalServerError)
167
+				return
168
+			}
169
+
170
+			w.WriteHeader(http.StatusOK)
171
+		})
172
+	}
173
+}
174
+
175
+func DeleteRepositoryHandler(adminService distribution.AdminService) func(ctx *handlers.Context, r *http.Request) http.Handler {
176
+	return func(ctx *handlers.Context, r *http.Request) http.Handler {
177
+		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
178
+			defer req.Body.Close()
179
+
180
+			if err := adminService.DeleteRepository(ctx.Repository.Name()); err != nil {
181
+				w.Write([]byte(fmt.Sprintf("Error deleting repository %q: %v", ctx.Repository.Name(), err)))
182
+			}
183
+
184
+			w.WriteHeader(http.StatusNoContent)
185
+		})
186
+	}
187
+}
... ...
@@ -132,7 +132,7 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
132 132
 
133 133
 		// In case of docker login, hits endpoint /v2
134 134
 		if len(accessRecords) == 0 {
135
-			err = VerifyOpenShiftUser(user, client)
135
+			err = verifyOpenShiftUser(user, client)
136 136
 			if err != nil {
137 137
 				challenge.err = err
138 138
 				return nil, challenge
... ...
@@ -143,37 +143,46 @@ func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...reg
143 143
 	for _, access := range accessRecords {
144 144
 		log.Debugf("%s:%s:%s", access.Resource.Type, access.Resource.Name, access.Action)
145 145
 
146
-		if access.Resource.Type != "repository" {
147
-			continue
148
-		}
149
-
150
-		repoParts := strings.SplitN(access.Resource.Name, "/", 2)
151
-		if len(repoParts) != 2 {
152
-			challenge.err = ErrNamespaceRequired
153
-			return nil, challenge
154
-		}
146
+		switch access.Resource.Type {
147
+		case "repository":
148
+			repoParts := strings.SplitN(access.Resource.Name, "/", 2)
149
+			if len(repoParts) != 2 {
150
+				challenge.err = ErrNamespaceRequired
151
+				return nil, challenge
152
+			}
155 153
 
156
-		verb := ""
157
-		switch access.Action {
158
-		case "push":
159
-			verb = "update"
160
-		case "pull":
161
-			verb = "get"
162
-		default:
163
-			challenge.err = fmt.Errorf("Unknown action: %s", access.Action)
164
-			return nil, challenge
165
-		}
154
+			verb := ""
155
+			switch access.Action {
156
+			case "push":
157
+				verb = "update"
158
+			case "pull":
159
+				verb = "get"
160
+			default:
161
+				challenge.err = fmt.Errorf("Unknown action: %s", access.Action)
162
+				return nil, challenge
163
+			}
166 164
 
167
-		err = VerifyOpenShiftAccess(repoParts[0], repoParts[1], verb, client)
168
-		if err != nil {
169
-			challenge.err = err
170
-			return nil, challenge
165
+			if err := verifyImageStreamAccess(repoParts[0], repoParts[1], verb, client); err != nil {
166
+				challenge.err = err
167
+				return nil, challenge
168
+			}
169
+		case "admin":
170
+			switch access.Action {
171
+			case "prune":
172
+				if err := verifyPruneAccess(client); err != nil {
173
+					challenge.err = err
174
+					return nil, challenge
175
+				}
176
+			default:
177
+				challenge.err = fmt.Errorf("Unknown action: %s", access.Action)
178
+				return nil, challenge
179
+			}
171 180
 		}
172 181
 	}
173 182
 	return WithUserClient(ctx, client), nil
174 183
 }
175 184
 
176
-func VerifyOpenShiftUser(user string, client *client.Client) error {
185
+func verifyOpenShiftUser(user string, client *client.Client) error {
177 186
 	userObj, err := client.Users().Get("~")
178 187
 	if err != nil {
179 188
 		log.Errorf("Get user failed with error: %s", err)
... ...
@@ -186,7 +195,7 @@ func VerifyOpenShiftUser(user string, client *client.Client) error {
186 186
 	return nil
187 187
 }
188 188
 
189
-func VerifyOpenShiftAccess(namespace, imageRepo, verb string, client *client.Client) error {
189
+func verifyImageStreamAccess(namespace, imageRepo, verb string, client *client.Client) error {
190 190
 	sar := authorizationapi.SubjectAccessReview{
191 191
 		Verb:         verb,
192 192
 		Resource:     "imageStreams",
... ...
@@ -203,3 +212,20 @@ func VerifyOpenShiftAccess(namespace, imageRepo, verb string, client *client.Cli
203 203
 	}
204 204
 	return nil
205 205
 }
206
+
207
+func verifyPruneAccess(client *client.Client) error {
208
+	sar := authorizationapi.SubjectAccessReview{
209
+		Verb:     "delete",
210
+		Resource: "images",
211
+	}
212
+	response, err := client.ClusterSubjectAccessReviews().Create(&sar)
213
+	if err != nil {
214
+		log.Errorf("OpenShift client error: %s", err)
215
+		return ErrOpenShiftAccessDenied
216
+	}
217
+	if !response.Allowed {
218
+		log.Errorf("OpenShift access denied: %s", response.Reason)
219
+		return ErrOpenShiftAccessDenied
220
+	}
221
+	return nil
222
+}
206 223
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package server
1
+
2
+import (
3
+	"net/http"
4
+
5
+	"github.com/docker/distribution/health"
6
+	"github.com/docker/distribution/registry/handlers"
7
+)
8
+
9
+func HealthzHandler(ctx *handlers.Context, r *http.Request) http.Handler {
10
+	return http.HandlerFunc(health.StatusHandler)
11
+}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"io"
9 9
 	"io/ioutil"
10 10
 	"net/http"
11
+	"time"
11 12
 
12 13
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
13 14
 	kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
... ...
@@ -20,8 +21,8 @@ import (
20 20
 	buildapi "github.com/openshift/origin/pkg/build/api"
21 21
 	buildutil "github.com/openshift/origin/pkg/build/util"
22 22
 	"github.com/openshift/origin/pkg/client"
23
-	"github.com/openshift/origin/pkg/cmd/dockerregistry"
24 23
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
24
+	"github.com/openshift/origin/pkg/dockerregistry/server"
25 25
 	imageapi "github.com/openshift/origin/pkg/image/api"
26 26
 	"github.com/openshift/origin/pkg/image/registry/imagestreamimage"
27 27
 )
... ...
@@ -29,8 +30,8 @@ import (
29 29
 // pruneAlgorithm contains the various settings to use when evaluating images
30 30
 // and layers for pruning.
31 31
 type pruneAlgorithm struct {
32
-	minimumAgeInMinutesToPrune int
33
-	tagRevisionsToKeep         int
32
+	keepYoungerThan    time.Duration
33
+	tagRevisionsToKeep int
34 34
 }
35 35
 
36 36
 // ImagePruneFunc is a function that is invoked for each image that is
... ...
@@ -40,7 +41,7 @@ type ImagePruneFunc func(image *imageapi.Image, streams []*imageapi.ImageStream)
40 40
 // LayerPruneFunc is a function that is invoked for each registry, along with
41 41
 // a DeleteLayersRequest that contains the layers that can be pruned and the
42 42
 // image stream names that reference each layer.
43
-type LayerPruneFunc func(registryURL string, req dockerregistry.DeleteLayersRequest) (requestError error, layerErrors map[string][]error)
43
+type LayerPruneFunc func(registryURL string, req server.DeleteLayersRequest) (requestError error, layerErrors map[string][]error)
44 44
 
45 45
 // ImagePruner knows how to prune images and layers.
46 46
 type ImagePruner interface {
... ...
@@ -59,10 +60,10 @@ var _ ImagePruner = &imagePruner{}
59 59
 /*
60 60
 NewImagePruner creates a new ImagePruner.
61 61
 
62
-minimumAgeInMinutesToPrune is the minimum age, in minutes, that a resource
63
-must be in order for the image it references (or an image itself) to be a
64
-candidate for pruning. For example, if minimumAgeInMinutesToPrune is 60, and
65
-an ImageStream is only 59 minutes old, none of the images it references are
62
+Images younger than keepYoungerThan and images referenced by image streams
63
+and/or pods younger than keepYoungerThan are preserved. All other images are
64
+candidates for pruning. For example, if keepYoungerThan is 60m, and an
65
+ImageStream is only 59 minutes old, none of the images it references are
66 66
 eligible for pruning.
67 67
 
68 68
 tagRevisionsToKeep is the number of revisions per tag in an image stream's
... ...
@@ -92,7 +93,7 @@ ImageStreams having a reference to the image in `status.tags`.
92 92
 Also automatically remove any image layer that is no longer referenced by any
93 93
 images.
94 94
 */
95
-func NewImagePruner(minimumAgeInMinutesToPrune int, tagRevisionsToKeep int, images client.ImagesInterfacer, streams client.ImageStreamsNamespacer, pods kclient.PodsNamespacer, rcs kclient.ReplicationControllersNamespacer, bcs client.BuildConfigsNamespacer, builds client.BuildsNamespacer, dcs client.DeploymentConfigsNamespacer) (ImagePruner, error) {
95
+func NewImagePruner(keepYoungerThan time.Duration, tagRevisionsToKeep int, images client.ImagesInterfacer, streams client.ImageStreamsNamespacer, pods kclient.PodsNamespacer, rcs kclient.ReplicationControllersNamespacer, bcs client.BuildConfigsNamespacer, builds client.BuildsNamespacer, dcs client.DeploymentConfigsNamespacer) (ImagePruner, error) {
96 96
 	allImages, err := images.Images().List(labels.Everything(), fields.Everything())
97 97
 	if err != nil {
98 98
 		return nil, fmt.Errorf("Error listing images: %v", err)
... ...
@@ -128,18 +129,18 @@ func NewImagePruner(minimumAgeInMinutesToPrune int, tagRevisionsToKeep int, imag
128 128
 		return nil, fmt.Errorf("Error listing deployment configs: %v", err)
129 129
 	}
130 130
 
131
-	return newImagePruner(minimumAgeInMinutesToPrune, tagRevisionsToKeep, allImages, allStreams, allPods, allRCs, allBCs, allBuilds, allDCs), nil
131
+	return newImagePruner(keepYoungerThan, tagRevisionsToKeep, allImages, allStreams, allPods, allRCs, allBCs, allBuilds, allDCs), nil
132 132
 }
133 133
 
134 134
 // newImagePruner creates a new ImagePruner.
135
-func newImagePruner(minimumAgeInMinutesToPrune int, tagRevisionsToKeep int, images *imageapi.ImageList, streams *imageapi.ImageStreamList, pods *kapi.PodList, rcs *kapi.ReplicationControllerList, bcs *buildapi.BuildConfigList, builds *buildapi.BuildList, dcs *deployapi.DeploymentConfigList) ImagePruner {
135
+func newImagePruner(keepYoungerThan time.Duration, tagRevisionsToKeep int, images *imageapi.ImageList, streams *imageapi.ImageStreamList, pods *kapi.PodList, rcs *kapi.ReplicationControllerList, bcs *buildapi.BuildConfigList, builds *buildapi.BuildList, dcs *deployapi.DeploymentConfigList) ImagePruner {
136 136
 	g := graph.New()
137 137
 
138
-	glog.V(1).Infof("Creating image pruner with minimumAgeInMinutesToPrune=%d, tagRevisionsToKeep=%d", minimumAgeInMinutesToPrune, tagRevisionsToKeep)
138
+	glog.V(1).Infof("Creating image pruner with keepYoungerThan=%v, tagRevisionsToKeep=%d", keepYoungerThan, tagRevisionsToKeep)
139 139
 
140 140
 	algorithm := pruneAlgorithm{
141
-		minimumAgeInMinutesToPrune: minimumAgeInMinutesToPrune,
142
-		tagRevisionsToKeep:         tagRevisionsToKeep,
141
+		keepYoungerThan:    keepYoungerThan,
142
+		tagRevisionsToKeep: tagRevisionsToKeep,
143 143
 	}
144 144
 
145 145
 	addImagesToGraph(g, images, algorithm)
... ...
@@ -176,9 +177,8 @@ func addImagesToGraph(g graph.Graph, images *imageapi.ImageList, algorithm prune
176 176
 		}
177 177
 
178 178
 		age := util.Now().Sub(image.CreationTimestamp.Time)
179
-		ageInMinutes := int(age.Minutes())
180
-		if ageInMinutes < algorithm.minimumAgeInMinutesToPrune {
181
-			glog.V(4).Infof("Image %q is younger than minimum pruning age, skipping (age=%d)", image.Name, ageInMinutes)
179
+		if age < algorithm.keepYoungerThan {
180
+			glog.V(4).Infof("Image %q is younger than minimum pruning age, skipping (age=%v)", image.Name, age)
182 181
 			continue
183 182
 		}
184 183
 
... ...
@@ -218,7 +218,7 @@ func addImageStreamsToGraph(g graph.Graph, streams *imageapi.ImageStreamList, al
218 218
 		oldImageRevisionReferenceKind := graph.WeakReferencedImageGraphEdgeKind
219 219
 
220 220
 		age := util.Now().Sub(stream.CreationTimestamp.Time)
221
-		if int(age.Minutes()) < algorithm.minimumAgeInMinutesToPrune {
221
+		if age < algorithm.keepYoungerThan {
222 222
 			// stream's age is below threshold - use a strong reference for old image revisions instead
223 223
 			glog.V(4).Infof("Stream %s/%s is below age threshold - none of its images are eligible for pruning", stream.Namespace, stream.Name)
224 224
 			oldImageRevisionReferenceKind = graph.ReferencedImageGraphEdgeKind
... ...
@@ -277,7 +277,7 @@ func addPodsToGraph(g graph.Graph, pods *kapi.PodList, algorithm pruneAlgorithm)
277 277
 
278 278
 		if pod.Status.Phase != kapi.PodRunning && pod.Status.Phase != kapi.PodPending {
279 279
 			age := util.Now().Sub(pod.CreationTimestamp.Time)
280
-			if int(age.Minutes()) >= algorithm.minimumAgeInMinutesToPrune {
280
+			if age >= algorithm.keepYoungerThan {
281 281
 				glog.V(4).Infof("Pod %s/%s is not running or pending and age is at least minimum pruning age - skipping", pod.Namespace, pod.Name)
282 282
 				// not pending or running, age is at least minimum pruning age, skip
283 283
 				continue
... ...
@@ -538,10 +538,10 @@ func streamLayerReferences(g graph.Graph, layerNode *graph.ImageLayerNode) []*gr
538 538
 }
539 539
 
540 540
 // pruneLayers creates a mapping of registryURLs to
541
-// dockerregistry.DeleteLayersRequest objects, invoking layerPruneFunc for each
541
+// server.DeleteLayersRequest objects, invoking layerPruneFunc for each
542 542
 // registryURL and request.
543 543
 func pruneLayers(g graph.Graph, layerNodes []*graph.ImageLayerNode, layerPruneFunc LayerPruneFunc) {
544
-	registryDeletionRequests := map[string]dockerregistry.DeleteLayersRequest{}
544
+	registryDeletionRequests := map[string]server.DeleteLayersRequest{}
545 545
 
546 546
 	for _, layerNode := range layerNodes {
547 547
 		glog.V(4).Infof("Examining layer %q", layerNode.Layer)
... ...
@@ -571,7 +571,7 @@ func pruneLayers(g graph.Graph, layerNodes []*graph.ImageLayerNode, layerPruneFu
571 571
 			deletionRequest, ok := registryDeletionRequests[ref.Registry]
572 572
 			if !ok {
573 573
 				glog.V(4).Infof("Request not found - creating new one")
574
-				deletionRequest = dockerregistry.DeleteLayersRequest{}
574
+				deletionRequest = server.DeleteLayersRequest{}
575 575
 				registryDeletionRequests[ref.Registry] = deletionRequest
576 576
 			}
577 577
 
... ...
@@ -648,7 +648,7 @@ func DeletingImagePruneFunc(images client.ImageInterface, streams client.ImageSt
648 648
 // DescribingLayerPruneFunc returns a LayerPruneFunc that writes information
649 649
 // about the layers that are eligible for pruning to out.
650 650
 func DescribingLayerPruneFunc(out io.Writer) LayerPruneFunc {
651
-	return func(registryURL string, deletions dockerregistry.DeleteLayersRequest) (error, map[string][]error) {
651
+	return func(registryURL string, deletions server.DeleteLayersRequest) (error, map[string][]error) {
652 652
 		result := map[string][]error{}
653 653
 
654 654
 		fmt.Fprintf(out, "Pruning from registry %q\n", registryURL)
... ...
@@ -676,7 +676,7 @@ func DescribingLayerPruneFunc(out io.Writer) LayerPruneFunc {
676 676
 // key being a layer, and each value being a list of Docker image repository
677 677
 // names referenced by the layer.
678 678
 func DeletingLayerPruneFunc(registryClient *http.Client) LayerPruneFunc {
679
-	return func(registryURL string, deletions dockerregistry.DeleteLayersRequest) (requestError error, layerErrors map[string][]error) {
679
+	return func(registryURL string, deletions server.DeleteLayersRequest) (requestError error, layerErrors map[string][]error) {
680 680
 		glog.V(4).Infof("Starting pruning of layers from %q, req %#v", registryURL, deletions)
681 681
 		body, err := json.Marshal(&deletions)
682 682
 		if err != nil {
... ...
@@ -710,7 +710,7 @@ func DeletingLayerPruneFunc(registryClient *http.Client) LayerPruneFunc {
710 710
 			return fmt.Errorf("Unexpected status code %d in response %s", resp.StatusCode, buf), nil
711 711
 		}
712 712
 
713
-		var deleteResponse dockerregistry.DeleteLayersResponse
713
+		var deleteResponse server.DeleteLayersResponse
714 714
 		if err := json.Unmarshal(buf, &deleteResponse); err != nil {
715 715
 			glog.Errorf("Error unmarshaling response: %v", err)
716 716
 			return fmt.Errorf("Error unmarshaling response: %v", err), nil
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"io"
6 6
 
7
-	"github.com/openshift/origin/pkg/cmd/dockerregistry"
7
+	"github.com/openshift/origin/pkg/dockerregistry/server"
8 8
 	imageapi "github.com/openshift/origin/pkg/image/api"
9 9
 )
10 10
 
... ...
@@ -93,7 +93,7 @@ func (p *summarizingPruner) imagePruneFunc(base ImagePruneFunc) ImagePruneFunc {
93 93
 }
94 94
 
95 95
 func (p *summarizingPruner) layerPruneFunc(base LayerPruneFunc) LayerPruneFunc {
96
-	return func(registryURL string, req dockerregistry.DeleteLayersRequest) (error, map[string][]error) {
96
+	return func(registryURL string, req server.DeleteLayersRequest) (error, map[string][]error) {
97 97
 		requestError, layerErrors := base(registryURL, req)
98 98
 		p.registryResults[registryURL] = registryResult{
99 99
 			requestError: requestError,