forked from TrueCloudLab/distribution
Compare commits
18 commits
main
...
release/2.
Author | SHA1 | Date | |
---|---|---|---|
|
03efb43768 | ||
|
431e46a7f9 | ||
|
1e0f3b7b64 | ||
|
93e3aa9b21 | ||
|
93d76247f2 | ||
|
ba672e8b69 | ||
|
96230decb9 | ||
|
c0d3813f86 | ||
|
011b7e493b | ||
|
0a1fcf9712 | ||
|
ed02e88075 | ||
|
fd5a404996 | ||
|
3f538cac90 | ||
|
3330cc567e | ||
|
775d0968cc | ||
|
64a9727f11 | ||
|
dafb59f4ab | ||
|
3f7fa41272 |
14 changed files with 220 additions and 100 deletions
5
AUTHORS
5
AUTHORS
|
@ -1,4 +1,5 @@
|
||||||
Aaron Lehmann <aaron.lehmann@docker.com>
|
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||||
|
Aaron Schlesinger <aschlesinger@deis.com>
|
||||||
Aaron Vinson <avinson.public@gmail.com>
|
Aaron Vinson <avinson.public@gmail.com>
|
||||||
Adam Enger <adamenger@gmail.com>
|
Adam Enger <adamenger@gmail.com>
|
||||||
Adrian Mouat <adrian.mouat@gmail.com>
|
Adrian Mouat <adrian.mouat@gmail.com>
|
||||||
|
@ -27,6 +28,7 @@ burnettk <burnettk@gmail.com>
|
||||||
Carson A <ca@carsonoid.net>
|
Carson A <ca@carsonoid.net>
|
||||||
Chris Dillon <squarism@gmail.com>
|
Chris Dillon <squarism@gmail.com>
|
||||||
Daisuke Fujita <dtanshi45@gmail.com>
|
Daisuke Fujita <dtanshi45@gmail.com>
|
||||||
|
Daniel Huhn <daniel@danielhuhn.de>
|
||||||
Darren Shepherd <darren@rancher.com>
|
Darren Shepherd <darren@rancher.com>
|
||||||
Dave Trombley <dave.trombley@gmail.com>
|
Dave Trombley <dave.trombley@gmail.com>
|
||||||
Dave Tucker <dt@docker.com>
|
Dave Tucker <dt@docker.com>
|
||||||
|
@ -59,6 +61,7 @@ Jeff Nickoloff <jeff@allingeek.com>
|
||||||
Jessie Frazelle <jessie@docker.com>
|
Jessie Frazelle <jessie@docker.com>
|
||||||
Jianqing Wang <tsing@jianqing.org>
|
Jianqing Wang <tsing@jianqing.org>
|
||||||
John Starks <jostarks@microsoft.com>
|
John Starks <jostarks@microsoft.com>
|
||||||
|
Jon Johnson <jonjohnson@google.com>
|
||||||
Jon Poler <jonathan.poler@apcera.com>
|
Jon Poler <jonathan.poler@apcera.com>
|
||||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||||
Jordan Liggitt <jliggitt@redhat.com>
|
Jordan Liggitt <jliggitt@redhat.com>
|
||||||
|
@ -103,6 +106,7 @@ Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
|
||||||
Shreyas Karnik <karnik.shreyas@gmail.com>
|
Shreyas Karnik <karnik.shreyas@gmail.com>
|
||||||
Simon Thulbourn <simon+github@thulbourn.com>
|
Simon Thulbourn <simon+github@thulbourn.com>
|
||||||
Spencer Rinehart <anubis@overthemonkey.com>
|
Spencer Rinehart <anubis@overthemonkey.com>
|
||||||
|
Stefan Majewsky <stefan.majewsky@sap.com>
|
||||||
Stefan Weil <sw@weilnetz.de>
|
Stefan Weil <sw@weilnetz.de>
|
||||||
Stephen J Day <stephen.day@docker.com>
|
Stephen J Day <stephen.day@docker.com>
|
||||||
Sungho Moon <sungho.moon@navercorp.com>
|
Sungho Moon <sungho.moon@navercorp.com>
|
||||||
|
@ -114,6 +118,7 @@ Thomas Sjögren <konstruktoid@users.noreply.github.com>
|
||||||
Tianon Gravi <admwiggin@gmail.com>
|
Tianon Gravi <admwiggin@gmail.com>
|
||||||
Tibor Vass <teabee89@gmail.com>
|
Tibor Vass <teabee89@gmail.com>
|
||||||
Tonis Tiigi <tonistiigi@gmail.com>
|
Tonis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Tony Holdstock-Brown <tony@docker.com>
|
||||||
Trevor Pounds <trevor.pounds@gmail.com>
|
Trevor Pounds <trevor.pounds@gmail.com>
|
||||||
Troels Thomsen <troels@thomsen.io>
|
Troels Thomsen <troels@thomsen.io>
|
||||||
Vincent Batts <vbatts@redhat.com>
|
Vincent Batts <vbatts@redhat.com>
|
||||||
|
|
|
@ -110,7 +110,8 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani
|
||||||
ContainerConfig struct {
|
ContainerConfig struct {
|
||||||
Cmd []string
|
Cmd []string
|
||||||
} `json:"container_config,omitempty"`
|
} `json:"container_config,omitempty"`
|
||||||
ThrowAway bool `json:"throwaway,omitempty"`
|
Author string `json:"author,omitempty"`
|
||||||
|
ThrowAway bool `json:"throwaway,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
fsLayerList := make([]FSLayer, len(img.History))
|
fsLayerList := make([]FSLayer, len(img.History))
|
||||||
|
@ -145,6 +146,7 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani
|
||||||
Parent: parent,
|
Parent: parent,
|
||||||
Comment: h.Comment,
|
Comment: h.Comment,
|
||||||
Created: h.Created,
|
Created: h.Created,
|
||||||
|
Author: h.Author,
|
||||||
}
|
}
|
||||||
v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
|
v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
|
||||||
if h.EmptyLayer {
|
if h.EmptyLayer {
|
||||||
|
|
|
@ -163,6 +163,7 @@ func TestConfigBuilder(t *testing.T) {
|
||||||
"empty_layer": true
|
"empty_layer": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"author": "Alyssa P. Hacker \u003calyspdev@example.com\u003e",
|
||||||
"created": "2015-11-04T23:06:32.083868454Z",
|
"created": "2015-11-04T23:06:32.083868454Z",
|
||||||
"created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"
|
"created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"
|
||||||
},
|
},
|
||||||
|
@ -252,8 +253,8 @@ func TestConfigBuilder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedV1Compatibility := []string{
|
expectedV1Compatibility := []string{
|
||||||
`{"architecture":"amd64","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","echo hi"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container":"e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001","container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"2015-11-04T23:06:32.365666163Z","docker_version":"1.9.0-dev","id":"0850bfdeb7b060b1004a09099846c2f023a3f2ecbf33f56b4774384b00ce0323","os":"linux","parent":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","throwaway":true}`,
|
`{"architecture":"amd64","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","echo hi"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container":"e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001","container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"2015-11-04T23:06:32.365666163Z","docker_version":"1.9.0-dev","id":"69e5c1bfadad697fdb6db59f6326648fa119e0c031a0eda33b8cfadcab54ba7f","os":"linux","parent":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","throwaway":true}`,
|
||||||
`{"id":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","parent":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","created":"2015-11-04T23:06:32.083868454Z","container_config":{"Cmd":["/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"]}}`,
|
`{"id":"74cf9c92699240efdba1903c2748ef57105d5bedc588084c4e88f3bb1c3ef0b0","parent":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","created":"2015-11-04T23:06:32.083868454Z","container_config":{"Cmd":["/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"]},"author":"Alyssa P. Hacker \u003calyspdev@example.com\u003e"}`,
|
||||||
`{"id":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","parent":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","created":"2015-11-04T23:06:31.192097572Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV asdf=true"]},"throwaway":true}`,
|
`{"id":"178be37afc7c49e951abd75525dbe0871b62ad49402f037164ee6314f754599d","parent":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","created":"2015-11-04T23:06:31.192097572Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV asdf=true"]},"throwaway":true}`,
|
||||||
`{"id":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","parent":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","created":"2015-11-04T23:06:30.934316144Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV derived=true"]},"throwaway":true}`,
|
`{"id":"b449305a55a283538c4574856a8b701f2a3d5ec08ef8aec47f385f20339a4866","parent":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","created":"2015-11-04T23:06:30.934316144Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV derived=true"]},"throwaway":true}`,
|
||||||
`{"id":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","parent":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:55.613815829Z","container_config":{"Cmd":["/bin/sh -c #(nop) CMD [\"sh\"]"]}}`,
|
`{"id":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","parent":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:55.613815829Z","container_config":{"Cmd":["/bin/sh -c #(nop) CMD [\"sh\"]"]}}`,
|
||||||
|
|
|
@ -55,6 +55,9 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) {
|
||||||
|
|
||||||
// Add config to the blob store
|
// Add config to the blob store
|
||||||
m.Config, err = mb.bs.Put(ctx, MediaTypeConfig, mb.configJSON)
|
m.Config, err = mb.bs.Put(ctx, MediaTypeConfig, mb.configJSON)
|
||||||
|
// Override MediaType, since Put always replaces the specified media
|
||||||
|
// type with application/octet-stream in the descriptor it returns.
|
||||||
|
m.Config.MediaType = MediaTypeConfig
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (bs *mockBlobService) Put(ctx context.Context, mediaType string, p []byte)
|
||||||
d := distribution.Descriptor{
|
d := distribution.Descriptor{
|
||||||
Digest: digest.FromBytes(p),
|
Digest: digest.FromBytes(p),
|
||||||
Size: int64(len(p)),
|
Size: int64(len(p)),
|
||||||
MediaType: mediaType,
|
MediaType: "application/octet-stream",
|
||||||
}
|
}
|
||||||
bs.descriptors[d.Digest] = d
|
bs.descriptors[d.Digest] = d
|
||||||
return d, nil
|
return d, nil
|
||||||
|
|
|
@ -46,7 +46,11 @@ func copyFullPayload(responseWriter http.ResponseWriter, r *http.Request, destWr
|
||||||
// instead of showing 0 for the HTTP status.
|
// instead of showing 0 for the HTTP status.
|
||||||
responseWriter.WriteHeader(499)
|
responseWriter.WriteHeader(499)
|
||||||
|
|
||||||
ctxu.GetLogger(context).Error("client disconnected during " + action)
|
ctxu.GetLoggerWithFields(context, map[interface{}]interface{}{
|
||||||
|
"error": err,
|
||||||
|
"copied": copied,
|
||||||
|
"contentLength": r.ContentLength,
|
||||||
|
}, "error", "copied", "contentLength").Error("client disconnected during " + action)
|
||||||
return errors.New("client disconnected")
|
return errors.New("client disconnected")
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/storage"
|
||||||
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
||||||
"github.com/docker/distribution/version"
|
"github.com/docker/distribution/version"
|
||||||
|
"github.com/docker/libtrust"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,6 +17,7 @@ var showVersion bool
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(ServeCmd)
|
RootCmd.AddCommand(ServeCmd)
|
||||||
RootCmd.AddCommand(GCCmd)
|
RootCmd.AddCommand(GCCmd)
|
||||||
|
GCCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "do everything except remove the blobs")
|
||||||
RootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
|
RootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,3 +34,51 @@ var RootCmd = &cobra.Command{
|
||||||
cmd.Usage()
|
cmd.Usage()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dryRun bool
|
||||||
|
|
||||||
|
// GCCmd is the cobra command that corresponds to the garbage-collect subcommand
|
||||||
|
var GCCmd = &cobra.Command{
|
||||||
|
Use: "garbage-collect <config>",
|
||||||
|
Short: "`garbage-collect` deletes layers not referenced by any manifests",
|
||||||
|
Long: "`garbage-collect` deletes layers not referenced by any manifests",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
config, err := resolveConfiguration(args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)
|
||||||
|
cmd.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to construct %s driver: %v", config.Storage.Type(), err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, err = configureLogging(ctx, config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unable to configure logging with config: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := libtrust.GenerateECP256PrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, 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 = storage.MarkAndSweep(ctx, driver, registry, dryRun)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "failed to garbage collect: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/docker/distribution/registry/storage/cache/memory"
|
"github.com/docker/distribution/registry/storage/cache/memory"
|
||||||
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestWriteSeek tests that the current file size can be
|
// TestWriteSeek tests that the current file size can be
|
||||||
|
@ -83,6 +84,15 @@ func TestSimpleBlobUpload(t *testing.T) {
|
||||||
t.Fatalf("unexpected error during upload cancellation: %v", err)
|
t.Fatalf("unexpected error during upload cancellation: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the enclosing directory
|
||||||
|
uploadPath := path.Dir(blobUpload.(*blobWriter).path)
|
||||||
|
|
||||||
|
// ensure state was cleaned up
|
||||||
|
_, err = driver.List(ctx, uploadPath)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("files in upload path after cleanup")
|
||||||
|
}
|
||||||
|
|
||||||
// Do a resume, get unknown upload
|
// Do a resume, get unknown upload
|
||||||
blobUpload, err = bs.Resume(ctx, blobUpload.ID())
|
blobUpload, err = bs.Resume(ctx, blobUpload.ID())
|
||||||
if err != distribution.ErrBlobUploadUnknown {
|
if err != distribution.ErrBlobUploadUnknown {
|
||||||
|
@ -128,6 +138,13 @@ func TestSimpleBlobUpload(t *testing.T) {
|
||||||
t.Fatalf("unexpected error finishing layer upload: %v", err)
|
t.Fatalf("unexpected error finishing layer upload: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure state was cleaned up
|
||||||
|
uploadPath = path.Dir(blobUpload.(*blobWriter).path)
|
||||||
|
_, err = driver.List(ctx, uploadPath)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("files in upload path after commit")
|
||||||
|
}
|
||||||
|
|
||||||
// After finishing an upload, it should no longer exist.
|
// After finishing an upload, it should no longer exist.
|
||||||
if _, err := bs.Resume(ctx, blobUpload.ID()); err != distribution.ErrBlobUploadUnknown {
|
if _, err := bs.Resume(ctx, blobUpload.ID()); err != distribution.ErrBlobUploadUnknown {
|
||||||
t.Fatalf("expected layer upload to be unknown, got %v", err)
|
t.Fatalf("expected layer upload to be unknown, got %v", err)
|
||||||
|
|
|
@ -18,8 +18,8 @@ var (
|
||||||
errResumableDigestNotAvailable = errors.New("resumable digest not available")
|
errResumableDigestNotAvailable = errors.New("resumable digest not available")
|
||||||
)
|
)
|
||||||
|
|
||||||
// layerWriter is used to control the various aspects of resumable
|
// blobWriter is used to control the various aspects of resumable
|
||||||
// layer upload. It implements the LayerUpload interface.
|
// blob upload.
|
||||||
type blobWriter struct {
|
type blobWriter struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
blobStore *linkedBlobStore
|
blobStore *linkedBlobStore
|
||||||
|
@ -34,6 +34,7 @@ type blobWriter struct {
|
||||||
path string
|
path string
|
||||||
|
|
||||||
resumableDigestEnabled bool
|
resumableDigestEnabled bool
|
||||||
|
committed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ distribution.BlobWriter = &blobWriter{}
|
var _ distribution.BlobWriter = &blobWriter{}
|
||||||
|
@ -56,6 +57,8 @@ func (bw *blobWriter) Commit(ctx context.Context, desc distribution.Descriptor)
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bw.Close()
|
||||||
|
|
||||||
canonical, err := bw.validateBlob(ctx, desc)
|
canonical, err := bw.validateBlob(ctx, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
|
@ -78,6 +81,7 @@ func (bw *blobWriter) Commit(ctx context.Context, desc distribution.Descriptor)
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bw.committed = true
|
||||||
return canonical, nil
|
return canonical, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,11 +93,14 @@ func (bw *blobWriter) Cancel(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bw.Close(); err != nil {
|
||||||
|
context.GetLogger(ctx).Errorf("error closing blobwriter: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := bw.removeResources(ctx); err != nil {
|
if err := bw.removeResources(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bw.Close()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +137,10 @@ func (bw *blobWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bw *blobWriter) Close() error {
|
func (bw *blobWriter) Close() error {
|
||||||
|
if bw.committed {
|
||||||
|
return errors.New("blobwriter close after commit")
|
||||||
|
}
|
||||||
|
|
||||||
if err := bw.storeHashState(bw.blobStore.ctx); err != nil {
|
if err := bw.storeHashState(bw.blobStore.ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -493,6 +493,9 @@ func (w *writer) Write(p []byte) (int, error) {
|
||||||
|
|
||||||
// Size returns the number of bytes written to this FileWriter.
|
// Size returns the number of bytes written to this FileWriter.
|
||||||
func (w *writer) Size() int64 {
|
func (w *writer) Size() int64 {
|
||||||
|
if !w.closed {
|
||||||
|
return w.offset + int64(w.buffSize)
|
||||||
|
}
|
||||||
return w.size
|
return w.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -80,6 +81,7 @@ func init() {
|
||||||
"ap-northeast-1",
|
"ap-northeast-1",
|
||||||
"ap-northeast-2",
|
"ap-northeast-2",
|
||||||
"sa-east-1",
|
"sa-east-1",
|
||||||
|
"cn-north-1",
|
||||||
} {
|
} {
|
||||||
validRegions[region] = struct{}{}
|
validRegions[region] = struct{}{}
|
||||||
}
|
}
|
||||||
|
@ -136,14 +138,21 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
||||||
secretKey = ""
|
secretKey = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
regionEndpoint := parameters["regionendpoint"]
|
||||||
|
if regionEndpoint == nil {
|
||||||
|
regionEndpoint = ""
|
||||||
|
}
|
||||||
|
|
||||||
regionName, ok := parameters["region"]
|
regionName, ok := parameters["region"]
|
||||||
if regionName == nil || fmt.Sprint(regionName) == "" {
|
if regionName == nil || fmt.Sprint(regionName) == "" {
|
||||||
return nil, fmt.Errorf("No region parameter provided")
|
return nil, fmt.Errorf("No region parameter provided")
|
||||||
}
|
}
|
||||||
region := fmt.Sprint(regionName)
|
region := fmt.Sprint(regionName)
|
||||||
_, ok = validRegions[region]
|
// Don't check the region value if a custom endpoint is provided.
|
||||||
if !ok {
|
if regionEndpoint == "" {
|
||||||
return nil, fmt.Errorf("Invalid region provided: %v", region)
|
if _, ok = validRegions[region]; !ok {
|
||||||
|
return nil, fmt.Errorf("Invalid region provided: %v", region)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket := parameters["bucket"]
|
bucket := parameters["bucket"]
|
||||||
|
@ -151,11 +160,6 @@ func FromParameters(parameters map[string]interface{}) (*Driver, error) {
|
||||||
return nil, fmt.Errorf("No bucket parameter provided")
|
return nil, fmt.Errorf("No bucket parameter provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
regionEndpoint := parameters["regionendpoint"]
|
|
||||||
if regionEndpoint == nil {
|
|
||||||
regionEndpoint = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptBool := false
|
encryptBool := false
|
||||||
encrypt := parameters["encrypt"]
|
encrypt := parameters["encrypt"]
|
||||||
switch encrypt := encrypt.(type) {
|
switch encrypt := encrypt.(type) {
|
||||||
|
@ -716,6 +720,12 @@ func (d *driver) newWriter(key, uploadID string, parts []*s3.Part) storagedriver
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type completedParts []*s3.CompletedPart
|
||||||
|
|
||||||
|
func (a completedParts) Len() int { return len(a) }
|
||||||
|
func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a completedParts) Less(i, j int) bool { return *a[i].PartNumber < *a[j].PartNumber }
|
||||||
|
|
||||||
func (w *writer) Write(p []byte) (int, error) {
|
func (w *writer) Write(p []byte) (int, error) {
|
||||||
if w.closed {
|
if w.closed {
|
||||||
return 0, fmt.Errorf("already closed")
|
return 0, fmt.Errorf("already closed")
|
||||||
|
@ -728,19 +738,22 @@ func (w *writer) Write(p []byte) (int, error) {
|
||||||
// If the last written part is smaller than minChunkSize, we need to make a
|
// If the last written part is smaller than minChunkSize, we need to make a
|
||||||
// new multipart upload :sadface:
|
// new multipart upload :sadface:
|
||||||
if len(w.parts) > 0 && int(*w.parts[len(w.parts)-1].Size) < minChunkSize {
|
if len(w.parts) > 0 && int(*w.parts[len(w.parts)-1].Size) < minChunkSize {
|
||||||
var completedParts []*s3.CompletedPart
|
var completedUploadedParts completedParts
|
||||||
for _, part := range w.parts {
|
for _, part := range w.parts {
|
||||||
completedParts = append(completedParts, &s3.CompletedPart{
|
completedUploadedParts = append(completedUploadedParts, &s3.CompletedPart{
|
||||||
ETag: part.ETag,
|
ETag: part.ETag,
|
||||||
PartNumber: part.PartNumber,
|
PartNumber: part.PartNumber,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(completedUploadedParts)
|
||||||
|
|
||||||
_, err := w.driver.S3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
|
_, err := w.driver.S3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
|
||||||
Bucket: aws.String(w.driver.Bucket),
|
Bucket: aws.String(w.driver.Bucket),
|
||||||
Key: aws.String(w.key),
|
Key: aws.String(w.key),
|
||||||
UploadId: aws.String(w.uploadID),
|
UploadId: aws.String(w.uploadID),
|
||||||
MultipartUpload: &s3.CompletedMultipartUpload{
|
MultipartUpload: &s3.CompletedMultipartUpload{
|
||||||
Parts: completedParts,
|
Parts: completedUploadedParts,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -880,19 +893,23 @@ func (w *writer) Commit() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.committed = true
|
w.committed = true
|
||||||
var completedParts []*s3.CompletedPart
|
|
||||||
|
var completedUploadedParts completedParts
|
||||||
for _, part := range w.parts {
|
for _, part := range w.parts {
|
||||||
completedParts = append(completedParts, &s3.CompletedPart{
|
completedUploadedParts = append(completedUploadedParts, &s3.CompletedPart{
|
||||||
ETag: part.ETag,
|
ETag: part.ETag,
|
||||||
PartNumber: part.PartNumber,
|
PartNumber: part.PartNumber,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Sort(completedUploadedParts)
|
||||||
|
|
||||||
_, err = w.driver.S3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
|
_, err = w.driver.S3.CompleteMultipartUpload(&s3.CompleteMultipartUploadInput{
|
||||||
Bucket: aws.String(w.driver.Bucket),
|
Bucket: aws.String(w.driver.Bucket),
|
||||||
Key: aws.String(w.key),
|
Key: aws.String(w.key),
|
||||||
UploadId: aws.String(w.uploadID),
|
UploadId: aws.String(w.uploadID),
|
||||||
MultipartUpload: &s3.CompletedMultipartUpload{
|
MultipartUpload: &s3.CompletedMultipartUpload{
|
||||||
Parts: completedParts,
|
Parts: completedUploadedParts,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -698,6 +698,9 @@ func (w *writer) Close() error {
|
||||||
if err := w.driver.createManifest(w.path, w.driver.Container+"/"+w.segmentsPath); err != nil {
|
if err := w.driver.createManifest(w.path, w.driver.Container+"/"+w.segmentsPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := w.waitForSegmentsToShowUp(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.closed = true
|
w.closed = true
|
||||||
|
|
||||||
|
@ -732,10 +735,14 @@ func (w *writer) Commit() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
w.committed = true
|
w.committed = true
|
||||||
|
return w.waitForSegmentsToShowUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) waitForSegmentsToShowUp() error {
|
||||||
var err error
|
var err error
|
||||||
waitingTime := readAfterWriteWait
|
waitingTime := readAfterWriteWait
|
||||||
endTime := time.Now().Add(readAfterWriteTimeout)
|
endTime := time.Now().Add(readAfterWriteTimeout)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var info swift.Object
|
var info swift.Object
|
||||||
if info, _, err = w.driver.Conn.Object(w.driver.Container, w.driver.swiftPath(w.path)); err == nil {
|
if info, _, err = w.driver.Conn.Object(w.driver.Container, w.driver.swiftPath(w.path)); err == nil {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
package registry
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
|
@ -10,21 +9,15 @@ import (
|
||||||
"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/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"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/libtrust"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func emit(format string, a ...interface{}) {
|
func emit(format string, a ...interface{}) {
|
||||||
if dryRun {
|
fmt.Printf(format+"\n", a...)
|
||||||
fmt.Printf(format+"\n", a...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace) error {
|
// MarkAndSweep performs a mark and sweep of registry data
|
||||||
|
func MarkAndSweep(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace, dryRun bool) error {
|
||||||
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
|
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unable to convert Namespace to RepositoryEnumerator")
|
return fmt.Errorf("unable to convert Namespace to RepositoryEnumerator")
|
||||||
|
@ -33,7 +26,9 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
// 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(repoName)
|
if dryRun {
|
||||||
|
emit(repoName)
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
named, err := reference.ParseNamed(repoName)
|
named, err := reference.ParseNamed(repoName)
|
||||||
|
@ -57,7 +52,9 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
|
|
||||||
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
|
err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error {
|
||||||
// Mark the manifest's blob
|
// Mark the manifest's blob
|
||||||
emit("%s: marking manifest %s ", repoName, dgst)
|
if dryRun {
|
||||||
|
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)
|
||||||
|
@ -68,7 +65,9 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
descriptors := manifest.References()
|
descriptors := manifest.References()
|
||||||
for _, descriptor := range descriptors {
|
for _, descriptor := range descriptors {
|
||||||
markSet[descriptor.Digest] = struct{}{}
|
markSet[descriptor.Digest] = struct{}{}
|
||||||
emit("%s: marking blob %s", repoName, descriptor.Digest)
|
if dryRun {
|
||||||
|
emit("%s: marking blob %s", repoName, descriptor.Digest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch manifest.(type) {
|
switch manifest.(type) {
|
||||||
|
@ -82,13 +81,17 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
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("%s: marking signature %s", repoName, signatureDigest)
|
if dryRun {
|
||||||
|
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("%s: marking configuration %s", repoName, config.Digest)
|
if dryRun {
|
||||||
|
emit("%s: marking configuration %s", repoName, config.Digest)
|
||||||
|
}
|
||||||
markSet[config.Digest] = struct{}{}
|
markSet[config.Digest] = struct{}{}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -96,6 +99,17 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// In certain situations such as unfinished uploads, deleting all
|
||||||
|
// tags in S3 or removing the _manifests folder manually, this
|
||||||
|
// error may be of type PathNotFound.
|
||||||
|
//
|
||||||
|
// In these cases we can continue marking other manifests safely.
|
||||||
|
if _, ok := err.(driver.PathNotFoundError); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -116,13 +130,14 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error enumerating blobs: %v", err)
|
return fmt.Errorf("error enumerating blobs: %v", err)
|
||||||
}
|
}
|
||||||
|
if dryRun {
|
||||||
emit("\n%d blobs marked, %d blobs eligible for deletion", len(markSet), len(deleteSet))
|
emit("\n%d blobs marked, %d blobs eligible for deletion", len(markSet), len(deleteSet))
|
||||||
|
}
|
||||||
// Construct vacuum
|
// Construct vacuum
|
||||||
vacuum := storage.NewVacuum(ctx, storageDriver)
|
vacuum := NewVacuum(ctx, storageDriver)
|
||||||
for dgst := range deleteSet {
|
for dgst := range deleteSet {
|
||||||
emit("blob eligible for deletion: %s", dgst)
|
|
||||||
if dryRun {
|
if dryRun {
|
||||||
|
emit("blob eligible for deletion: %s", dgst)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = vacuum.RemoveBlob(string(dgst))
|
err = vacuum.RemoveBlob(string(dgst))
|
||||||
|
@ -133,55 +148,3 @@ func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, regis
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
GCCmd.Flags().BoolVarP(&dryRun, "dry-run", "d", false, "do everything expect remove the blobs")
|
|
||||||
}
|
|
||||||
|
|
||||||
var dryRun bool
|
|
||||||
|
|
||||||
// GCCmd is the cobra command that corresponds to the garbage-collect subcommand
|
|
||||||
var GCCmd = &cobra.Command{
|
|
||||||
Use: "garbage-collect <config>",
|
|
||||||
Short: "`garbage-collect` deletes layers not referenced by any manifests",
|
|
||||||
Long: "`garbage-collect` deletes layers not referenced by any manifests",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
config, err := resolveConfiguration(args)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "configuration error: %v\n", err)
|
|
||||||
cmd.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := factory.Create(config.Storage.Type(), config.Storage.Parameters())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to construct %s driver: %v", config.Storage.Type(), err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx, err = configureLogging(ctx, config)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "unable to configure logging with config: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := libtrust.GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, 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 {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to garbage collect: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
package registry
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"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/inmemory"
|
"github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
|
@ -22,7 +22,7 @@ type image struct {
|
||||||
|
|
||||||
func createRegistry(t *testing.T, driver driver.StorageDriver) distribution.Namespace {
|
func createRegistry(t *testing.T, driver driver.StorageDriver) distribution.Namespace {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
registry, err := storage.NewRegistry(ctx, driver, storage.EnableDelete)
|
registry, err := NewRegistry(ctx, driver, EnableDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to construct namespace")
|
t.Fatalf("Failed to construct namespace")
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ func TestNoDeletionNoEffect(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run GC
|
// Run GC
|
||||||
err = markAndSweep(context.Background(), inmemoryDriver, registry)
|
err = MarkAndSweep(context.Background(), inmemoryDriver, registry, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,37 @@ func TestNoDeletionNoEffect(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGCWithMissingManifests(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
d := inmemory.New()
|
||||||
|
|
||||||
|
registry := createRegistry(t, d)
|
||||||
|
repo := makeRepository(t, registry, "testrepo")
|
||||||
|
uploadRandomSchema1Image(t, repo)
|
||||||
|
|
||||||
|
// Simulate a missing _manifests directory
|
||||||
|
revPath, err := pathFor(manifestRevisionsPathSpec{"testrepo"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_manifestsPath := path.Dir(revPath)
|
||||||
|
err = d.Delete(ctx, _manifestsPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = MarkAndSweep(context.Background(), d, registry, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs := allBlobs(t, registry)
|
||||||
|
if len(blobs) > 0 {
|
||||||
|
t.Errorf("unexpected blobs after gc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeletionHasEffect(t *testing.T) {
|
func TestDeletionHasEffect(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
inmemoryDriver := inmemory.New()
|
inmemoryDriver := inmemory.New()
|
||||||
|
@ -193,7 +224,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, registry)
|
err = MarkAndSweep(context.Background(), inmemoryDriver, registry, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -327,7 +358,7 @@ func TestOrphanBlobDeleted(t *testing.T) {
|
||||||
uploadRandomSchema2Image(t, repo)
|
uploadRandomSchema2Image(t, repo)
|
||||||
|
|
||||||
// Run GC
|
// Run GC
|
||||||
err = markAndSweep(context.Background(), inmemoryDriver, registry)
|
err = MarkAndSweep(context.Background(), inmemoryDriver, registry, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed mark and sweep: %v", err)
|
t.Fatalf("Failed mark and sweep: %v", err)
|
||||||
}
|
}
|
Loading…
Reference in a new issue