Browse code

Merge pull request #410 from thaJeztah/19.03_backport_fix_buildkit_prunegc_filter_config

[19.03 backport] daemon/config: fix filter type in BuildKit GC config

Andrew Hsu authored on 2019/10/29 02:52:31
Showing 4 changed files
... ...
@@ -36,6 +36,15 @@ func NewArgs(initialArgs ...KeyValuePair) Args {
36 36
 	return args
37 37
 }
38 38
 
39
+// Keys returns all the keys in list of Args
40
+func (args Args) Keys() []string {
41
+	keys := make([]string, 0, len(args.fields))
42
+	for k := range args.fields {
43
+		keys = append(keys, k)
44
+	}
45
+	return keys
46
+}
47
+
39 48
 // MarshalJSON returns a JSON byte representation of the Args
40 49
 func (args Args) MarshalJSON() ([]byte, error) {
41 50
 	if len(args.fields) == 0 {
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/containerd/containerd/content/local"
9 9
 	"github.com/containerd/containerd/platforms"
10 10
 	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/filters"
11 12
 	"github.com/docker/docker/builder/builder-next/adapters/containerimage"
12 13
 	"github.com/docker/docker/builder/builder-next/adapters/localinlinecache"
13 14
 	"github.com/docker/docker/builder/builder-next/adapters/snapshot"
... ...
@@ -232,7 +233,7 @@ func getGCPolicy(conf config.BuilderConfig, root string) ([]client.PruneInfo, er
232 232
 				gcPolicy[i], err = toBuildkitPruneInfo(types.BuildCachePruneOptions{
233 233
 					All:         p.All,
234 234
 					KeepStorage: b,
235
-					Filters:     p.Filter,
235
+					Filters:     filters.Args(p.Filter),
236 236
 				})
237 237
 				if err != nil {
238 238
 					return nil, err
... ...
@@ -1,12 +1,57 @@
1 1
 package config
2 2
 
3
-import "github.com/docker/docker/api/types/filters"
3
+import (
4
+	"encoding/json"
5
+	"fmt"
6
+	"sort"
7
+	"strings"
8
+
9
+	"github.com/docker/docker/api/types/filters"
10
+)
4 11
 
5 12
 // BuilderGCRule represents a GC rule for buildkit cache
6 13
 type BuilderGCRule struct {
7
-	All         bool         `json:",omitempty"`
8
-	Filter      filters.Args `json:",omitempty"`
9
-	KeepStorage string       `json:",omitempty"`
14
+	All         bool            `json:",omitempty"`
15
+	Filter      BuilderGCFilter `json:",omitempty"`
16
+	KeepStorage string          `json:",omitempty"`
17
+}
18
+
19
+// BuilderGCFilter contains garbage-collection filter rules for a BuildKit builder
20
+type BuilderGCFilter filters.Args
21
+
22
+// MarshalJSON returns a JSON byte representation of the BuilderGCFilter
23
+func (x *BuilderGCFilter) MarshalJSON() ([]byte, error) {
24
+	f := filters.Args(*x)
25
+	keys := f.Keys()
26
+	sort.Strings(keys)
27
+	arr := make([]string, 0, len(keys))
28
+	for _, k := range keys {
29
+		values := f.Get(k)
30
+		for _, v := range values {
31
+			arr = append(arr, fmt.Sprintf("%s=%s", k, v))
32
+		}
33
+	}
34
+	return json.Marshal(arr)
35
+}
36
+
37
+// UnmarshalJSON fills the BuilderGCFilter values structure from JSON input
38
+func (x *BuilderGCFilter) UnmarshalJSON(data []byte) error {
39
+	var arr []string
40
+	f := filters.NewArgs()
41
+	if err := json.Unmarshal(data, &arr); err != nil {
42
+		// backwards compat for deprecated buggy form
43
+		err := json.Unmarshal(data, &f)
44
+		*x = BuilderGCFilter(f)
45
+		return err
46
+	}
47
+	for _, s := range arr {
48
+		fields := strings.SplitN(s, "=", 2)
49
+		name := strings.ToLower(strings.TrimSpace(fields[0]))
50
+		value := strings.TrimSpace(fields[1])
51
+		f.Add(name, value)
52
+	}
53
+	*x = BuilderGCFilter(f)
54
+	return nil
10 55
 }
11 56
 
12 57
 // BuilderGCConfig contains GC config for a buildkit builder
13 58
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+package config
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/docker/docker/api/types/filters"
6
+	"github.com/google/go-cmp/cmp"
7
+	"gotest.tools/assert"
8
+	"gotest.tools/fs"
9
+)
10
+
11
+func TestBuilderGC(t *testing.T) {
12
+	tempFile := fs.NewFile(t, "config", fs.WithContent(`{
13
+  "builder": {
14
+    "gc": {
15
+      "enabled": true,
16
+      "policy": [
17
+        {"keepStorage": "10GB", "filter": ["unused-for=2200h"]},
18
+        {"keepStorage": "50GB", "filter": {"unused-for": {"3300h": true}}},
19
+        {"keepStorage": "100GB", "all": true}
20
+      ]
21
+    }
22
+  }
23
+}`))
24
+	defer tempFile.Remove()
25
+	configFile := tempFile.Path()
26
+
27
+	cfg, err := MergeDaemonConfigurations(&Config{}, nil, configFile)
28
+	assert.NilError(t, err)
29
+	assert.Assert(t, cfg.Builder.GC.Enabled)
30
+	f1 := filters.NewArgs()
31
+	f1.Add("unused-for", "2200h")
32
+	f2 := filters.NewArgs()
33
+	f2.Add("unused-for", "3300h")
34
+	expectedPolicy := []BuilderGCRule{
35
+		{KeepStorage: "10GB", Filter: BuilderGCFilter(f1)},
36
+		{KeepStorage: "50GB", Filter: BuilderGCFilter(f2)}, /* parsed from deprecated form */
37
+		{KeepStorage: "100GB", All: true},
38
+	}
39
+	assert.DeepEqual(t, cfg.Builder.GC.Policy, expectedPolicy, cmp.AllowUnexported(BuilderGCFilter{}))
40
+	// double check to please the skeptics
41
+	assert.Assert(t, filters.Args(cfg.Builder.GC.Policy[0].Filter).UniqueExactMatch("unused-for", "2200h"))
42
+	assert.Assert(t, filters.Args(cfg.Builder.GC.Policy[1].Filter).UniqueExactMatch("unused-for", "3300h"))
43
+}