Browse Source

Bump config version to v4

We've changed the config to be more consistent about "copies" and
"artifacts". This means that "copies" no longer accepts a string, but a
slice of strings. We should bump the config version and blubber version
to make it clear that we're not longer backwards compatible.

Change-Id: Ib68f623cbdcdf0aed74f4fdaec062835748e8eb9
Tyler Cipriani 4 months ago
parent
commit
d2ff2621c4

+ 2
- 2
README.md View File

@@ -11,7 +11,7 @@ running ad-hoc commands.
11 11
 ## Example configuration
12 12
 
13 13
 ```yaml
14
-version: v3
14
+version: v4
15 15
 base: debian:jessie
16 16
 apt:
17 17
   packages: [libjpeg, libyaml]
@@ -69,7 +69,7 @@ In the example configuration, the `test` variant when expanded effectively
69 69
 becomes:
70 70
 
71 71
 ```yaml
72
-version: v3
72
+version: v4
73 73
 base: debian:jessie
74 74
 apt:
75 75
   packages: [libjpeg, libyaml, libjpeg-dev, libyaml-dev, chromium]

+ 1
- 1
VERSION View File

@@ -1 +1 @@
1
-0.7.0
1
+0.8.0

+ 9
- 9
api/openapi-spec/blubberoid.yaml View File

@@ -18,7 +18,7 @@ paths:
18 18
         content:
19 19
           application/json:
20 20
             schema:
21
-              $ref: '#/components/schemas/v3.Config'
21
+              $ref: '#/components/schemas/v4.Config'
22 22
           application/yaml:
23 23
             schema:
24 24
               type: string
@@ -49,7 +49,7 @@ paths:
49 49
             headers:
50 50
               Content-Type: application/json
51 51
             body: {
52
-              "version": "v3",
52
+              "version": "v4",
53 53
               "base": "docker-registry.wikimedia.org/nodejs-slim",
54 54
               "apt": { "packages": ["librsvg2-2"] },
55 55
               "lives": { "in": "/srv/service" },
@@ -73,10 +73,10 @@ paths:
73 73
 
74 74
 components:
75 75
   schemas:
76
-    v3.Config:
77
-      title: Top-level blubber configuration (version v3)
76
+    v4.Config:
77
+      title: Top-level blubber configuration (version v4)
78 78
       allOf:
79
-        - $ref: '#/components/schemas/v3.CommonConfig'
79
+        - $ref: '#/components/schemas/v4.CommonConfig'
80 80
         - type: object
81 81
           properties:
82 82
             required: [version, variants]
@@ -95,9 +95,9 @@ components:
95 95
               #
96 96
               # patternProperties:
97 97
               #   "^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$":
98
-              #     $ref: '#/components/schemas/v3.VariantConfig'
98
+              #     $ref: '#/components/schemas/v4.VariantConfig'
99 99
 
100
-    v3.CommonConfig:
100
+    v4.CommonConfig:
101 101
       type: object
102 102
       properties:
103 103
         base:
@@ -185,9 +185,9 @@ components:
185 185
           description: Runtime entry point command and arguments
186 186
           items:
187 187
             type: string
188
-    v3.VariantConfig:
188
+    v4.VariantConfig:
189 189
       allOf:
190
-        - $ref: '#/components/schemas/v3.CommonConfig'
190
+        - $ref: '#/components/schemas/v4.CommonConfig'
191 191
         - type: object
192 192
           properties:
193 193
             includes:

+ 1
- 1
blubber.example.yaml View File

@@ -1,5 +1,5 @@
1 1
 ---
2
-version: v3
2
+version: v4
3 3
 base: docker-registry.wikimedia.org/wikimedia-stretch:latest
4 4
 apt:
5 5
   packages: [libjpeg, libyaml]

+ 3
- 3
cmd/blubberoid/main_test.go View File

@@ -13,7 +13,7 @@ import (
13 13
 func TestBlubberoidYAMLRequest(t *testing.T) {
14 14
 	rec := httptest.NewRecorder()
15 15
 	req := httptest.NewRequest("POST", "/v1/test", strings.NewReader(`---
16
-    version: v3
16
+    version: v4
17 17
     base: foo
18 18
     variants:
19 19
       test: {}`))
@@ -34,7 +34,7 @@ func TestBlubberoidJSONRequest(t *testing.T) {
34 34
 	t.Run("valid JSON syntax", func(t *testing.T) {
35 35
 		rec := httptest.NewRecorder()
36 36
 		req := httptest.NewRequest("POST", "/v1/test", strings.NewReader(`{
37
-			"version": "v3",
37
+			"version": "v4",
38 38
 			"base": "foo",
39 39
 			"variants": {
40 40
 				"test": {}
@@ -56,7 +56,7 @@ func TestBlubberoidJSONRequest(t *testing.T) {
56 56
 	t.Run("invalid JSON syntax", func(t *testing.T) {
57 57
 		rec := httptest.NewRecorder()
58 58
 		req := httptest.NewRequest("POST", "/v1/test", strings.NewReader(`{
59
-			version: "v3",
59
+			version: "v4",
60 60
 			base: "foo",
61 61
 			variants: {
62 62
 				test: {},

+ 9
- 9
cmd/blubberoid/openapi.go View File

@@ -22,7 +22,7 @@ paths:
22 22
         content:
23 23
           application/json:
24 24
             schema:
25
-              $ref: '#/components/schemas/v3.Config'
25
+              $ref: '#/components/schemas/v4.Config'
26 26
           application/yaml:
27 27
             schema:
28 28
               type: string
@@ -53,7 +53,7 @@ paths:
53 53
             headers:
54 54
               Content-Type: application/json
55 55
             body: {
56
-              "version": "v3",
56
+              "version": "v4",
57 57
               "base": "docker-registry.wikimedia.org/nodejs-slim",
58 58
               "apt": { "packages": ["librsvg2-2"] },
59 59
               "lives": { "in": "/srv/service" },
@@ -77,10 +77,10 @@ paths:
77 77
 
78 78
 components:
79 79
   schemas:
80
-    v3.Config:
81
-      title: Top-level blubber configuration (version v3)
80
+    v4.Config:
81
+      title: Top-level blubber configuration (version v4)
82 82
       allOf:
83
-        - $ref: '#/components/schemas/v3.CommonConfig'
83
+        - $ref: '#/components/schemas/v4.CommonConfig'
84 84
         - type: object
85 85
           properties:
86 86
             required: [version, variants]
@@ -99,9 +99,9 @@ components:
99 99
               #
100 100
               # patternProperties:
101 101
               #   "^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$":
102
-              #     $ref: '#/components/schemas/v3.VariantConfig'
102
+              #     $ref: '#/components/schemas/v4.VariantConfig'
103 103
 
104
-    v3.CommonConfig:
104
+    v4.CommonConfig:
105 105
       type: object
106 106
       properties:
107 107
         base:
@@ -189,9 +189,9 @@ components:
189 189
           description: Runtime entry point command and arguments
190 190
           items:
191 191
             type: string
192
-    v3.VariantConfig:
192
+    v4.VariantConfig:
193 193
       allOf:
194
-        - $ref: '#/components/schemas/v3.CommonConfig'
194
+        - $ref: '#/components/schemas/v4.CommonConfig'
195 195
         - type: object
196 196
           properties:
197 197
             includes:

+ 1
- 1
config/apt_test.go View File

@@ -12,7 +12,7 @@ import (
12 12
 
13 13
 func TestAptConfigYAML(t *testing.T) {
14 14
 	cfg, err := config.ReadYAMLConfig([]byte(`---
15
-    version: v3
15
+    version: v4
16 16
     apt:
17 17
       packages:
18 18
         - libfoo

+ 12
- 12
config/artifacts_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestArtifactsConfigYAML(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     variants:
17 17
       build: {}
@@ -80,7 +80,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
80 80
 	t.Run("from", func(t *testing.T) {
81 81
 		t.Run("ok", func(t *testing.T) {
82 82
 			_, err := config.ReadYAMLConfig([]byte(`---
83
-        version: v3
83
+        version: v4
84 84
         variants:
85 85
           build: {}
86 86
           foo:
@@ -94,7 +94,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
94 94
 
95 95
 		t.Run("missing", func(t *testing.T) {
96 96
 			_, err := config.ReadYAMLConfig([]byte(`---
97
-        version: v3
97
+        version: v4
98 98
         variants:
99 99
           build: {}
100 100
           foo:
@@ -112,7 +112,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
112 112
 
113 113
 		t.Run("bad", func(t *testing.T) {
114 114
 			_, err := config.ReadYAMLConfig([]byte(`---
115
-        version: v3
115
+        version: v4
116 116
         variants:
117 117
           build: {}
118 118
           foo:
@@ -133,7 +133,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
133 133
 		t.Run("source", func(t *testing.T) {
134 134
 			t.Run("with no destination given can be empty", func(t *testing.T) {
135 135
 				_, err := config.ReadYAMLConfig([]byte(`---
136
-          version: v3
136
+          version: v4
137 137
           variants:
138 138
             build: {}
139 139
             foo:
@@ -145,7 +145,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
145 145
 
146 146
 			t.Run("with destination given must not be empty", func(t *testing.T) {
147 147
 				_, err := config.ReadYAMLConfig([]byte(`---
148
-          version: v3
148
+          version: v4
149 149
           variants:
150 150
             build: {}
151 151
             foo:
@@ -164,7 +164,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
164 164
 		t.Run("destination", func(t *testing.T) {
165 165
 			t.Run("with no source given can be empty", func(t *testing.T) {
166 166
 				_, err := config.ReadYAMLConfig([]byte(`---
167
-          version: v3
167
+          version: v4
168 168
           variants:
169 169
             build: {}
170 170
             foo:
@@ -176,7 +176,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
176 176
 
177 177
 			t.Run("with source given must not be empty", func(t *testing.T) {
178 178
 				_, err := config.ReadYAMLConfig([]byte(`---
179
-          version: v3
179
+          version: v4
180 180
           variants:
181 181
             build: {}
182 182
             foo:
@@ -198,7 +198,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
198 198
 		t.Run("source", func(t *testing.T) {
199 199
 			t.Run("must be a relative path", func(t *testing.T) {
200 200
 				_, err := config.ReadYAMLConfig([]byte(`---
201
-          version: v3
201
+          version: v4
202 202
           variants:
203 203
             foo:
204 204
               copies:
@@ -215,7 +215,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
215 215
 
216 216
 			t.Run("must not use ../", func(t *testing.T) {
217 217
 				_, err := config.ReadYAMLConfig([]byte(`---
218
-          version: v3
218
+          version: v4
219 219
           variants:
220 220
             foo:
221 221
               copies:
@@ -234,7 +234,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
234 234
 		t.Run("destination", func(t *testing.T) {
235 235
 			t.Run("must be a relative path", func(t *testing.T) {
236 236
 				_, err := config.ReadYAMLConfig([]byte(`---
237
-          version: v3
237
+          version: v4
238 238
           variants:
239 239
             foo:
240 240
               copies:
@@ -251,7 +251,7 @@ func TestArtifactsConfigValidation(t *testing.T) {
251 251
 
252 252
 			t.Run("must not use ../", func(t *testing.T) {
253 253
 				_, err := config.ReadYAMLConfig([]byte(`---
254
-          version: v3
254
+          version: v4
255 255
           variants:
256 256
             foo:
257 257
               copies:

+ 1
- 1
config/builder_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestBuilderConfigYAML(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     builder:
17 17
       command: [make, -f, Makefile, test]

+ 1
- 1
config/common_test.go View File

@@ -10,7 +10,7 @@ import (
10 10
 
11 11
 func TestCommonConfigYAML(t *testing.T) {
12 12
 	cfg, err := config.ReadYAMLConfig([]byte(`---
13
-    version: v3
13
+    version: v4
14 14
     base: fooimage
15 15
     entrypoint: ["/bin/foo"]
16 16
     variants:

+ 4
- 4
config/config_test.go View File

@@ -10,12 +10,12 @@ import (
10 10
 
11 11
 func TestConfigYAML(t *testing.T) {
12 12
 	cfg, err := config.ReadYAMLConfig([]byte(`---
13
-    version: v3
13
+    version: v4
14 14
     variants:
15 15
       foo: {}`))
16 16
 
17 17
 	if assert.NoError(t, err) {
18
-		assert.Equal(t, "v3", cfg.Version)
18
+		assert.Equal(t, "v4", cfg.Version)
19 19
 		assert.Contains(t, cfg.Variants, "foo")
20 20
 		assert.IsType(t, config.VariantConfig{}, cfg.Variants["foo"])
21 21
 	}
@@ -25,7 +25,7 @@ func TestConfigValidation(t *testing.T) {
25 25
 	t.Run("variants", func(t *testing.T) {
26 26
 		t.Run("ok", func(t *testing.T) {
27 27
 			err := config.Validate(config.Config{
28
-				VersionConfig: config.VersionConfig{Version: "v3"},
28
+				VersionConfig: config.VersionConfig{Version: "v4"},
29 29
 				Variants: map[string]config.VariantConfig{
30 30
 					"build": config.VariantConfig{},
31 31
 					"foo":   config.VariantConfig{},
@@ -37,7 +37,7 @@ func TestConfigValidation(t *testing.T) {
37 37
 
38 38
 		t.Run("bad", func(t *testing.T) {
39 39
 			err := config.Validate(config.Config{
40
-				VersionConfig: config.VersionConfig{Version: "v3"},
40
+				VersionConfig: config.VersionConfig{Version: "v4"},
41 41
 				Variants: map[string]config.VariantConfig{
42 42
 					"build foo": config.VariantConfig{},
43 43
 					"foo bar":   config.VariantConfig{},

+ 1
- 1
config/flag_test.go View File

@@ -10,7 +10,7 @@ import (
10 10
 
11 11
 func TestFlagMerge(t *testing.T) {
12 12
 	cfg, err := config.ReadYAMLConfig([]byte(`---
13
-    version: v3
13
+    version: v4
14 14
     base: foo
15 15
     runs: { insecurely: true }
16 16
     variants:

+ 2
- 2
config/lives_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestLivesConfigYAML(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     lives:
17 17
       in: /some/directory
@@ -40,7 +40,7 @@ func TestLivesConfigYAML(t *testing.T) {
40 40
 
41 41
 func TestLivesConfigDefaults(t *testing.T) {
42 42
 	cfg, err := config.ReadYAMLConfig([]byte(`---
43
-    version: v3
43
+    version: v4
44 44
     base: foo`))
45 45
 
46 46
 	if assert.NoError(t, err) {

+ 1
- 1
config/node_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestNodeConfigYAML(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     node:
17 17
       requirements: [package.json]

+ 3
- 3
config/python_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestPythonConfigYAMLMerge(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     python:
17 17
       version: python2.7
@@ -37,7 +37,7 @@ func TestPythonConfigYAMLMerge(t *testing.T) {
37 37
 
38 38
 func TestPythonConfigYAMLMergeEmpty(t *testing.T) {
39 39
 	cfg, err := config.ReadYAMLConfig([]byte(`---
40
-    version: v3
40
+    version: v4
41 41
     base: foo
42 42
     python:
43 43
       requirements: [requirements.txt]
@@ -59,7 +59,7 @@ func TestPythonConfigYAMLMergeEmpty(t *testing.T) {
59 59
 
60 60
 func TestPythonConfigYAMLDoNotMergeNil(t *testing.T) {
61 61
 	cfg, err := config.ReadYAMLConfig([]byte(`---
62
-    version: v3
62
+    version: v4
63 63
     base: foo
64 64
     python:
65 65
       requirements: [requirements.txt]

+ 5
- 5
config/reader_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func ExampleResolveIncludes() {
13 13
 	cfg, _ := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     variants:
16 16
       varA: { includes: [varB, varC] }
17 17
       varB: { includes: [varD, varE] }
@@ -29,7 +29,7 @@ func ExampleResolveIncludes() {
29 29
 
30 30
 func TestReadYAMLConfigErrorsOnUnknownYAML(t *testing.T) {
31 31
 	_, err := config.ReadYAMLConfig([]byte(`---
32
-    version: v3
32
+    version: v4
33 33
     newphone: whodis
34 34
     variants:
35 35
       foo: {}`))
@@ -55,7 +55,7 @@ func TestReadYAMLConfigValidateVersionBeforeStrictUnmarshal(t *testing.T) {
55 55
 
56 56
 func TestResolveIncludesPreventsInfiniteRecursion(t *testing.T) {
57 57
 	cfg, err := config.ReadYAMLConfig([]byte(`---
58
-    version: v3
58
+    version: v4
59 59
     variants:
60 60
       varA: { includes: [varB] }
61 61
       varB: { includes: [varA] }`))
@@ -69,7 +69,7 @@ func TestResolveIncludesPreventsInfiniteRecursion(t *testing.T) {
69 69
 
70 70
 func TestMultiLevelIncludes(t *testing.T) {
71 71
 	cfg, err := config.ReadYAMLConfig([]byte(`---
72
-    version: v3
72
+    version: v4
73 73
     base: foo-slim
74 74
     variants:
75 75
       build:
@@ -101,7 +101,7 @@ func TestMultiLevelIncludes(t *testing.T) {
101 101
 
102 102
 func TestMultiIncludes(t *testing.T) {
103 103
 	cfg, err := config.ReadYAMLConfig([]byte(`---
104
-    version: v3
104
+    version: v4
105 105
     variants:
106 106
       mammal:
107 107
         base: neutral

+ 1
- 1
config/runs_test.go View File

@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 func TestRunsConfigYAML(t *testing.T) {
13 13
 	cfg, err := config.ReadYAMLConfig([]byte(`---
14
-    version: v3
14
+    version: v4
15 15
     base: foo
16 16
     runs:
17 17
       as: someuser

+ 5
- 5
config/variant_test.go View File

@@ -12,7 +12,7 @@ import (
12 12
 
13 13
 func TestVariantConfigYAML(t *testing.T) {
14 14
 	cfg, err := config.ReadYAMLConfig([]byte(`---
15
-    version: v3
15
+    version: v4
16 16
     base: foo
17 17
     variants:
18 18
       build:
@@ -165,7 +165,7 @@ func TestVariantConfigValidation(t *testing.T) {
165 165
 	t.Run("includes", func(t *testing.T) {
166 166
 		t.Run("ok", func(t *testing.T) {
167 167
 			_, err := config.ReadYAMLConfig([]byte(`---
168
-        version: v3
168
+        version: v4
169 169
         variants:
170 170
           build: {}
171 171
           foo: { includes: [build] }`))
@@ -175,7 +175,7 @@ func TestVariantConfigValidation(t *testing.T) {
175 175
 
176 176
 		t.Run("optional", func(t *testing.T) {
177 177
 			_, err := config.ReadYAMLConfig([]byte(`---
178
-        version: v3
178
+        version: v4
179 179
         variants:
180 180
           build: {}
181 181
           foo: {}`))
@@ -185,7 +185,7 @@ func TestVariantConfigValidation(t *testing.T) {
185 185
 
186 186
 		t.Run("bad", func(t *testing.T) {
187 187
 			_, err := config.ReadYAMLConfig([]byte(`---
188
-        version: v3
188
+        version: v4
189 189
         variants:
190 190
           build: {}
191 191
           foo: { includes: [build, foobuild, foo_build] }`))
@@ -205,7 +205,7 @@ func TestVariantConfigValidation(t *testing.T) {
205 205
 
206 206
 		t.Run("should not contain duplicates", func(t *testing.T) {
207 207
 			_, err := config.ReadYAMLConfig([]byte(`---
208
-        version: v3
208
+        version: v4
209 209
         variants:
210 210
           build: {}
211 211
           foo: { copies: [foo, bar, foo] }`))

+ 1
- 1
config/version.go View File

@@ -2,7 +2,7 @@ package config
2 2
 
3 3
 // CurrentVersion declares the currently supported config version.
4 4
 //
5
-const CurrentVersion string = "v3"
5
+const CurrentVersion string = "v4"
6 6
 
7 7
 // VersionConfig contains a single field that allows for validation of the
8 8
 // config version independent from an entire Config struct.

+ 3
- 3
config/version_test.go View File

@@ -10,21 +10,21 @@ import (
10 10
 
11 11
 func TestVersionConfigYAML(t *testing.T) {
12 12
 	cfg, err := config.ReadYAMLConfig([]byte(`---
13
-    version: v3
13
+    version: v4
14 14
     variants:
15 15
       foo: {}`))
16 16
 
17 17
 	assert.Nil(t, err)
18 18
 
19 19
 	if assert.NoError(t, err) {
20
-		assert.Equal(t, "v3", cfg.Version)
20
+		assert.Equal(t, "v4", cfg.Version)
21 21
 	}
22 22
 }
23 23
 
24 24
 func TestVersionConfigValidation(t *testing.T) {
25 25
 	t.Run("supported version", func(t *testing.T) {
26 26
 		err := config.Validate(config.VersionConfig{
27
-			Version: "v3",
27
+			Version: "v4",
28 28
 		})
29 29
 
30 30
 		assert.False(t, config.IsValidationError(err))

+ 4
- 4
docker/compiler_test.go View File

@@ -13,7 +13,7 @@ import (
13 13
 
14 14
 func TestSingleStageHasNoName(t *testing.T) {
15 15
 	cfg, err := config.ReadYAMLConfig([]byte(`---
16
-    version: v3
16
+    version: v4
17 17
     base: foo/bar
18 18
     variants:
19 19
       development: {}`))
@@ -28,7 +28,7 @@ func TestSingleStageHasNoName(t *testing.T) {
28 28
 
29 29
 func TestMultiStageIncludesStageNames(t *testing.T) {
30 30
 	cfg, err := config.ReadYAMLConfig([]byte(`---
31
-    version: v3
31
+    version: v4
32 32
     base: foo/bar
33 33
     variants:
34 34
       build: {}
@@ -52,7 +52,7 @@ func TestMultiStageIncludesStageNames(t *testing.T) {
52 52
 
53 53
 func TestMultipleArtifactsFromSameStage(t *testing.T) {
54 54
 	cfg, err := config.ReadYAMLConfig([]byte(`---
55
-    version: v3
55
+    version: v4
56 56
     base: foo/bar
57 57
     variants:
58 58
       build: {}
@@ -79,7 +79,7 @@ func TestMultipleArtifactsFromSameStage(t *testing.T) {
79 79
 
80 80
 func TestMetaDataLabels(t *testing.T) {
81 81
 	cfg, err := config.ReadYAMLConfig([]byte(`---
82
-    version: v3
82
+    version: v4
83 83
     base: foo/bar
84 84
     variants:
85 85
       development: {}`))

Loading…
Cancel
Save