From 5a9d8d0ef41a8465ac6c4788997f4e428c058637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Tormo?= Date: Mon, 27 Apr 2020 10:29:25 +0200 Subject: [PATCH] Add minimum image age option --- gc/collector.go | 9 +++++---- gc/image.go | 2 +- gc/image_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++- gc/option.go | 11 +++++++++++ gc/option_test.go | 7 +++++++ main.go | 19 +++++++++++-------- 6 files changed, 77 insertions(+), 14 deletions(-) diff --git a/gc/collector.go b/gc/collector.go index deec42e..3b17deb 100644 --- a/gc/collector.go +++ b/gc/collector.go @@ -27,10 +27,11 @@ type Collector interface { type collector struct { client docker.APIClient - whitelist []string // reserved containers - reserved []string // reserved images - threshold int64 // target threshold in bytes - filter FilterFunc + whitelist []string // reserved containers + reserved []string // reserved images + threshold int64 // target threshold in bytes + minImageAge time.Duration + filter FilterFunc } // New returns a garbage collector. diff --git a/gc/image.go b/gc/image.go index 5d2796b..76caea3 100644 --- a/gc/image.go +++ b/gc/image.go @@ -72,7 +72,7 @@ func (c *collector) collectImages(ctx context.Context) error { if isImageUsed(image, df.Containers) { continue } - if time.Unix(image.Created, 0).Add(time.Hour).After(now) { + if time.Unix(image.Created, 0).Add(c.minImageAge).After(now) { continue } diff --git a/gc/image_test.go b/gc/image_test.go index 31a4239..e8dfc6a 100644 --- a/gc/image_test.go +++ b/gc/image_test.go @@ -129,6 +129,47 @@ func TestCollectImages_BelowThreshold(t *testing.T) { } } +// this test verifies that we do not purge images that are not old enough +func TestCollectImages_SkipNotOldEnough(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + mockdf := types.DiskUsage{ + LayersSize: 1, + Images: []*types.ImageSummary{ + // this image was created 20 mins ago + { + ID: "a180b24e38ed", + Created: time.Now().Add(-20*time.Minute).Unix(), + }, + // this image was created 40 mins ago + { + ID: "481995377a04", + Created: time.Now().Add(-40*time.Minute).Unix(), + }, + }, + } + + mockImages := []types.ImageInspect{ + {ID: "a180b24e38ed"}, + {ID: "481995377a04"}, + } + + client := mocks.NewMockAPIClient(controller) + client.EXPECT().DiskUsage(gomock.Any()).Return(mockdf, nil) + + // We DO NOT expect image 0 to be removed since is not old enough + client.EXPECT().ImageInspectWithRaw(gomock.Any(), mockImages[1].ID).Return(mockImages[1], nil, nil) + client.EXPECT().ImageRemove(gomock.Any(), mockImages[1].ID, imageRemoveOpts).Return(nil, nil) + + // Minimum image age set to 30 mins + c := New(client, WithMinImageAge(time.Hour/2)).(*collector) + err := c.collectImages(context.Background()) + if err != nil { + t.Error(err) + } +} + // this test verifies that we do not purge images that are // in-use by the system or are newly created. func TestCollectImages_Skip(t *testing.T) { @@ -156,7 +197,7 @@ func TestCollectImages_Skip(t *testing.T) { client := mocks.NewMockAPIClient(controller) client.EXPECT().DiskUsage(gomock.Any()).Return(mockdf, nil) - c := New(client).(*collector) + c := New(client, WithMinImageAge(time.Hour)).(*collector) err := c.collectImages(context.Background()) if err != nil { t.Error(err) diff --git a/gc/option.go b/gc/option.go index 2668c2d..6ac1116 100644 --- a/gc/option.go +++ b/gc/option.go @@ -4,6 +4,8 @@ package gc +import "time" + // Option configures a garbage collector option. type Option func(*collector) @@ -34,6 +36,15 @@ func WithThreshold(threshold int64) Option { } } +// WithMinImageAge returns an option to set the minimum +// age a image should be to become a candidate for removal. +// Images younger than this value won't be removed +func WithMinImageAge(minImageAge time.Duration) Option { + return func(c *collector) { + c.minImageAge = minImageAge + } +} + // ReservedImages provides a list of reserved images names // that should not be removed. var ReservedImages = []string{ diff --git a/gc/option_test.go b/gc/option_test.go index 9571e4b..ba49e39 100644 --- a/gc/option_test.go +++ b/gc/option_test.go @@ -7,13 +7,16 @@ package gc import ( "reflect" "testing" + "time" ) func TestOptions(t *testing.T) { + expectedMinImageAge,_ := time.ParseDuration("5h") c := New(nil, WithImageWhitelist([]string{"foo"}), WithThreshold(42), WithWhitelist([]string{"bar"}), + WithMinImageAge(expectedMinImageAge), ).(*collector) if got, want := c.threshold, int64(42); got != want { @@ -25,4 +28,8 @@ func TestOptions(t *testing.T) { if got, want := c.reserved, []string{"foo"}; !reflect.DeepEqual(want, got) { t.Errorf("Want image whitelist %v, got %v", want, got) } + + if got, want := c.minImageAge, expectedMinImageAge; !reflect.DeepEqual(want, got) { + t.Errorf("Want minImageAge %v, got %v", want, got) + } } diff --git a/main.go b/main.go index 55b41de..5bc98ef 100644 --- a/main.go +++ b/main.go @@ -21,14 +21,15 @@ import ( ) type config struct { - Once bool `envconfig:"GC_ONCE"` - Debug bool `envconfig:"GC_DEBUG"` - Color bool `envconfig:"GC_DEBUG_COLOR"` - Pretty bool `envconfig:"GC_DEBUG_PRETTY"` - Images []string `envconfig:"GC_IGNORE_IMAGES"` - Containers []string `envconfig:"GC_IGNORE_CONTAINERS"` - Interval time.Duration `envconfig:"GC_INTERVAL" default:"5m"` - Cache string `envconfig:"GC_CACHE" default:"5gb"` + Once bool `envconfig:"GC_ONCE"` + Debug bool `envconfig:"GC_DEBUG"` + Color bool `envconfig:"GC_DEBUG_COLOR"` + Pretty bool `envconfig:"GC_DEBUG_PRETTY"` + Images []string `envconfig:"GC_IGNORE_IMAGES"` + Containers []string `envconfig:"GC_IGNORE_CONTAINERS"` + Interval time.Duration `envconfig:"GC_INTERVAL" default:"5m"` + MinImageAge time.Duration `envconfig:"GC_MIN_IMAGE_AGE" default:"1h"` + Cache string `envconfig:"GC_CACHE" default:"5gb"` } func main() { @@ -61,6 +62,7 @@ func main() { gc.WithImageWhitelist(cfg.Images), gc.WithThreshold(size), gc.WithWhitelist(gc.ReservedNames), + gc.WithMinImageAge(cfg.MinImageAge), gc.WithWhitelist(cfg.Containers), ) if cfg.Once { @@ -71,6 +73,7 @@ func main() { Strs("ignore-images", cfg.Images). Str("cache", cfg.Cache). Str("interval", units.HumanDuration(cfg.Interval)). + Str("minimal image age", units.HumanDuration(cfg.MinImageAge)). Msg("starting the garbage collector") gc.Schedule(ctx, collector, cfg.Interval)