feat: add friendly tips for helmfile config order (#710)

* feat: add kindful tips for helmfile config order

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2023-08-06 22:22:36 +08:00 committed by GitHub
parent aac734ffd5
commit 48e48aa568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 689 additions and 661 deletions

View File

@ -41,7 +41,7 @@ func testListWithEnvironment(t *testing.T, cfg configImpl) {
environments: environments:
development: {} development: {}
shared: {} shared: {}
---
releases: releases:
- name: logging - name: logging
chart: incubator/raw chart: incubator/raw
@ -90,7 +90,7 @@ releases:
environments: environments:
test: {} test: {}
shared: {} shared: {}
---
repositories: repositories:
- name: bitnami - name: bitnami
url: https://charts.bitnami.com/bitnami url: https://charts.bitnami.com/bitnami

View File

@ -13,6 +13,7 @@ import (
"github.com/helmfile/helmfile/pkg/environment" "github.com/helmfile/helmfile/pkg/environment"
"github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/policy"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/runtime" "github.com/helmfile/helmfile/pkg/runtime"
"github.com/helmfile/helmfile/pkg/state" "github.com/helmfile/helmfile/pkg/state"
@ -187,6 +188,13 @@ func (a *desiredStateLoader) rawLoad(yaml []byte, baseDir, file string, evaluate
func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, baseDir, filename string, content []byte, evaluateBases bool) (*state.HelmState, error) { func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, baseDir, filename string, content []byte, evaluateBases bool) (*state.HelmState, error) {
// Allows part-splitting to work with CLRF-ed content // Allows part-splitting to work with CLRF-ed content
normalizedContent := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n")) normalizedContent := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
isStrict, err := policy.Checker(filename, normalizedContent)
if err != nil {
if isStrict {
return nil, err
}
ld.logger.Warnf("WARNING: %v", err)
}
parts := bytes.Split(normalizedContent, []byte("\n---\n")) parts := bytes.Split(normalizedContent, []byte("\n---\n"))
hasEnv := env != nil || overrodeEnv != nil hasEnv := env != nil || overrodeEnv != nil

View File

@ -8,51 +8,6 @@ first-pass rendering output of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
first-pass produced: &{default map[] map[]} first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.0": {default map[] map[]} first-pass rendering result of "helmfile_1.yaml.part.0": {default map[] map[]}
@ -64,51 +19,106 @@ second-pass rendering result of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
merged environment: &{default map[] map[]}
first-pass rendering starting for "helmfile_1.yaml.part.1": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4: 4:
5: releases: 5: - name: kubernetes-external-secrets
6: - name: logging 6: chart: incubator/raw
7: chart: incubator/raw 7: namespace: kube-system
8: namespace: kube-system 8: needs:
9: 9: - kube-system/logging
10: - name: kubernetes-external-secrets 10:
11: chart: incubator/raw 11: - name: external-secrets
12: namespace: kube-system 12: chart: incubator/raw
13: needs: 13: namespace: default
14: - kube-system/logging 14: labels:
15: 15: app: test
16: - name: external-secrets 16: needs:
17: chart: incubator/raw 17: - kube-system/kubernetes-external-secrets
18: namespace: default 18:
19: labels: 19: - name: my-release
20: app: test 20: chart: incubator/raw
21: needs: 21: namespace: default
22: - kube-system/kubernetes-external-secrets 22: labels:
23: 23: app: test
24: - name: my-release 24: needs:
25: chart: incubator/raw 25: - default/external-secrets
26: namespace: default 26:
27: labels: 27:
28: app: test 28: # Disabled releases are treated as missing
29: needs: 29: - name: disabled
30: - default/external-secrets 30: chart: incubator/raw
31: 31: namespace: kube-system
32: 32: installed: false
33: # Disabled releases are treated as missing 33:
34: - name: disabled 34: - name: test2
35: chart: incubator/raw 35: chart: incubator/raw
36: namespace: kube-system 36: needs:
37: installed: false 37: - kube-system/disabled
38: 38:
39: - name: test2 39: - name: test3
40: chart: incubator/raw 40: chart: incubator/raw
41: needs: 41: needs:
42: - kube-system/disabled 42: - test2
43:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.1": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4:
5: - name: kubernetes-external-secrets
6: chart: incubator/raw
7: namespace: kube-system
8: needs:
9: - kube-system/logging
10:
11: - name: external-secrets
12: chart: incubator/raw
13: namespace: default
14: labels:
15: app: test
16: needs:
17: - kube-system/kubernetes-external-secrets
18:
19: - name: my-release
20: chart: incubator/raw
21: namespace: default
22: labels:
23: app: test
24: needs:
25: - default/external-secrets
26:
27:
28: # Disabled releases are treated as missing
29: - name: disabled
30: chart: incubator/raw
31: namespace: kube-system
32: installed: false
33:
34: - name: test2
35: chart: incubator/raw
36: needs:
37: - kube-system/disabled
38:
39: - name: test3
40: chart: incubator/raw
41: needs:
42: - test2
43: 43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
merged environment: &{default map[] map[]} merged environment: &{default map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"
@ -121,24 +131,6 @@ first-pass rendering output of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
first-pass produced: &{default map[] map[]} first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.0": {default map[] map[]} first-pass rendering result of "helmfile_2.yaml.part.0": {default map[] map[]}
@ -150,24 +142,52 @@ second-pass rendering result of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories: merged environment: &{default map[] map[]}
6: - name: bitnami first-pass rendering starting for "helmfile_2.yaml.part.1": inherited=&{default map[] map[]}, overrode=<nil>
7: url: https://charts.bitnami.com/bitnami first-pass uses: &{default map[] map[]}
8: first-pass rendering output of "helmfile_2.yaml.part.1":
9: releases: 0: repositories:
10: - name: cache 1: - name: bitnami
11: namespace: my-app 2: url: https://charts.bitnami.com/bitnami
12: chart: bitnami/redis 3:
13: version: 17.0.7 4: releases:
14: labels: 5: - name: cache
15: app: test 6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.1": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_2.yaml.part.1":
0: repositories:
1: - name: bitnami
2: url: https://charts.bitnami.com/bitnami
3:
4: releases:
5: - name: cache
6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16: 16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
merged environment: &{default map[] map[]} merged environment: &{default map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"

View File

@ -8,51 +8,6 @@ first-pass rendering output of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
first-pass produced: &{staging map[] map[]} first-pass produced: &{staging map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.0": {staging map[] map[]} first-pass rendering result of "helmfile_1.yaml.part.0": {staging map[] map[]}
@ -64,51 +19,6 @@ second-pass rendering result of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
merged environment: &{staging map[] map[]} merged environment: &{staging map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"
@ -121,24 +31,6 @@ first-pass rendering output of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
first-pass produced: &{staging map[] map[]} first-pass produced: &{staging map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.0": {staging map[] map[]} first-pass rendering result of "helmfile_2.yaml.part.0": {staging map[] map[]}
@ -150,24 +42,6 @@ second-pass rendering result of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
merged environment: &{staging map[] map[]} merged environment: &{staging map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"

View File

@ -8,51 +8,6 @@ first-pass rendering output of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
first-pass produced: &{shared map[] map[]} first-pass produced: &{shared map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.0": {shared map[] map[]} first-pass rendering result of "helmfile_1.yaml.part.0": {shared map[] map[]}
@ -64,51 +19,106 @@ second-pass rendering result of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
merged environment: &{shared map[] map[]}
first-pass rendering starting for "helmfile_1.yaml.part.1": inherited=&{shared map[] map[]}, overrode=<nil>
first-pass uses: &{shared map[] map[]}
first-pass rendering output of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4: 4:
5: releases: 5: - name: kubernetes-external-secrets
6: - name: logging 6: chart: incubator/raw
7: chart: incubator/raw 7: namespace: kube-system
8: namespace: kube-system 8: needs:
9: 9: - kube-system/logging
10: - name: kubernetes-external-secrets 10:
11: chart: incubator/raw 11: - name: external-secrets
12: namespace: kube-system 12: chart: incubator/raw
13: needs: 13: namespace: default
14: - kube-system/logging 14: labels:
15: 15: app: test
16: - name: external-secrets 16: needs:
17: chart: incubator/raw 17: - kube-system/kubernetes-external-secrets
18: namespace: default 18:
19: labels: 19: - name: my-release
20: app: test 20: chart: incubator/raw
21: needs: 21: namespace: default
22: - kube-system/kubernetes-external-secrets 22: labels:
23: 23: app: test
24: - name: my-release 24: needs:
25: chart: incubator/raw 25: - default/external-secrets
26: namespace: default 26:
27: labels: 27:
28: app: test 28: # Disabled releases are treated as missing
29: needs: 29: - name: disabled
30: - default/external-secrets 30: chart: incubator/raw
31: 31: namespace: kube-system
32: 32: installed: false
33: # Disabled releases are treated as missing 33:
34: - name: disabled 34: - name: test2
35: chart: incubator/raw 35: chart: incubator/raw
36: namespace: kube-system 36: needs:
37: installed: false 37: - kube-system/disabled
38: 38:
39: - name: test2 39: - name: test3
40: chart: incubator/raw 40: chart: incubator/raw
41: needs: 41: needs:
42: - kube-system/disabled 42: - test2
43:
first-pass produced: &{shared map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.1": {shared map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4:
5: - name: kubernetes-external-secrets
6: chart: incubator/raw
7: namespace: kube-system
8: needs:
9: - kube-system/logging
10:
11: - name: external-secrets
12: chart: incubator/raw
13: namespace: default
14: labels:
15: app: test
16: needs:
17: - kube-system/kubernetes-external-secrets
18:
19: - name: my-release
20: chart: incubator/raw
21: namespace: default
22: labels:
23: app: test
24: needs:
25: - default/external-secrets
26:
27:
28: # Disabled releases are treated as missing
29: - name: disabled
30: chart: incubator/raw
31: namespace: kube-system
32: installed: false
33:
34: - name: test2
35: chart: incubator/raw
36: needs:
37: - kube-system/disabled
38:
39: - name: test3
40: chart: incubator/raw
41: needs:
42: - test2
43: 43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
merged environment: &{shared map[] map[]} merged environment: &{shared map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"
@ -121,24 +131,6 @@ first-pass rendering output of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
first-pass produced: &{shared map[] map[]} first-pass produced: &{shared map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.0": {shared map[] map[]} first-pass rendering result of "helmfile_2.yaml.part.0": {shared map[] map[]}
@ -150,24 +142,52 @@ second-pass rendering result of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories: merged environment: &{shared map[] map[]}
6: - name: bitnami first-pass rendering starting for "helmfile_2.yaml.part.1": inherited=&{shared map[] map[]}, overrode=<nil>
7: url: https://charts.bitnami.com/bitnami first-pass uses: &{shared map[] map[]}
8: first-pass rendering output of "helmfile_2.yaml.part.1":
9: releases: 0: repositories:
10: - name: cache 1: - name: bitnami
11: namespace: my-app 2: url: https://charts.bitnami.com/bitnami
12: chart: bitnami/redis 3:
13: version: 17.0.7 4: releases:
14: labels: 5: - name: cache
15: app: test 6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16:
first-pass produced: &{shared map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.1": {shared map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_2.yaml.part.1":
0: repositories:
1: - name: bitnami
2: url: https://charts.bitnami.com/bitnami
3:
4: releases:
5: - name: cache
6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16: 16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
merged environment: &{shared map[] map[]} merged environment: &{shared map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"

View File

@ -8,51 +8,6 @@ first-pass rendering output of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
first-pass produced: &{test map[] map[]} first-pass produced: &{test map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.0": {test map[] map[]} first-pass rendering result of "helmfile_1.yaml.part.0": {test map[] map[]}
@ -64,51 +19,6 @@ second-pass rendering result of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
merged environment: &{test map[] map[]} merged environment: &{test map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"
@ -121,24 +31,6 @@ first-pass rendering output of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
first-pass produced: &{test map[] map[]} first-pass produced: &{test map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.0": {test map[] map[]} first-pass rendering result of "helmfile_2.yaml.part.0": {test map[] map[]}
@ -150,24 +42,52 @@ second-pass rendering result of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories: merged environment: &{test map[] map[]}
6: - name: bitnami first-pass rendering starting for "helmfile_2.yaml.part.1": inherited=&{test map[] map[]}, overrode=<nil>
7: url: https://charts.bitnami.com/bitnami first-pass uses: &{test map[] map[]}
8: first-pass rendering output of "helmfile_2.yaml.part.1":
9: releases: 0: repositories:
10: - name: cache 1: - name: bitnami
11: namespace: my-app 2: url: https://charts.bitnami.com/bitnami
12: chart: bitnami/redis 3:
13: version: 17.0.7 4: releases:
14: labels: 5: - name: cache
15: app: test 6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16:
first-pass produced: &{test map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.1": {test map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_2.yaml.part.1":
0: repositories:
1: - name: bitnami
2: url: https://charts.bitnami.com/bitnami
3:
4: releases:
5: - name: cache
6: namespace: my-app
7: chart: bitnami/redis
8: version: 17.0.7
9: labels:
10: app: test
11:
12: - name: database
13: namespace: my-app
14: chart: bitnami/postgres
15: version: 11.6.22
16: 16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
merged environment: &{test map[] map[]} merged environment: &{test map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"

View File

@ -8,51 +8,6 @@ first-pass rendering output of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
4:
5: releases:
6: - name: logging
7: chart: incubator/raw
8: namespace: kube-system
9:
10: - name: kubernetes-external-secrets
11: chart: incubator/raw
12: namespace: kube-system
13: needs:
14: - kube-system/logging
15:
16: - name: external-secrets
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - kube-system/kubernetes-external-secrets
23:
24: - name: my-release
25: chart: incubator/raw
26: namespace: default
27: labels:
28: app: test
29: needs:
30: - default/external-secrets
31:
32:
33: # Disabled releases are treated as missing
34: - name: disabled
35: chart: incubator/raw
36: namespace: kube-system
37: installed: false
38:
39: - name: test2
40: chart: incubator/raw
41: needs:
42: - kube-system/disabled
43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
first-pass produced: &{development map[] map[]} first-pass produced: &{development map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.0": {development map[] map[]} first-pass rendering result of "helmfile_1.yaml.part.0": {development map[] map[]}
@ -64,51 +19,106 @@ second-pass rendering result of "helmfile_1.yaml.part.0":
1: environments: 1: environments:
2: development: {} 2: development: {}
3: shared: {} 3: shared: {}
merged environment: &{development map[] map[]}
first-pass rendering starting for "helmfile_1.yaml.part.1": inherited=&{development map[] map[]}, overrode=<nil>
first-pass uses: &{development map[] map[]}
first-pass rendering output of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4: 4:
5: releases: 5: - name: kubernetes-external-secrets
6: - name: logging 6: chart: incubator/raw
7: chart: incubator/raw 7: namespace: kube-system
8: namespace: kube-system 8: needs:
9: 9: - kube-system/logging
10: - name: kubernetes-external-secrets 10:
11: chart: incubator/raw 11: - name: external-secrets
12: namespace: kube-system 12: chart: incubator/raw
13: needs: 13: namespace: default
14: - kube-system/logging 14: labels:
15: 15: app: test
16: - name: external-secrets 16: needs:
17: chart: incubator/raw 17: - kube-system/kubernetes-external-secrets
18: namespace: default 18:
19: labels: 19: - name: my-release
20: app: test 20: chart: incubator/raw
21: needs: 21: namespace: default
22: - kube-system/kubernetes-external-secrets 22: labels:
23: 23: app: test
24: - name: my-release 24: needs:
25: chart: incubator/raw 25: - default/external-secrets
26: namespace: default 26:
27: labels: 27:
28: app: test 28: # Disabled releases are treated as missing
29: needs: 29: - name: disabled
30: - default/external-secrets 30: chart: incubator/raw
31: 31: namespace: kube-system
32: 32: installed: false
33: # Disabled releases are treated as missing 33:
34: - name: disabled 34: - name: test2
35: chart: incubator/raw 35: chart: incubator/raw
36: namespace: kube-system 36: needs:
37: installed: false 37: - kube-system/disabled
38: 38:
39: - name: test2 39: - name: test3
40: chart: incubator/raw 40: chart: incubator/raw
41: needs: 41: needs:
42: - kube-system/disabled 42: - test2
43:
first-pass produced: &{development map[] map[]}
first-pass rendering result of "helmfile_1.yaml.part.1": {development map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile_1.yaml.part.1":
0: releases:
1: - name: logging
2: chart: incubator/raw
3: namespace: kube-system
4:
5: - name: kubernetes-external-secrets
6: chart: incubator/raw
7: namespace: kube-system
8: needs:
9: - kube-system/logging
10:
11: - name: external-secrets
12: chart: incubator/raw
13: namespace: default
14: labels:
15: app: test
16: needs:
17: - kube-system/kubernetes-external-secrets
18:
19: - name: my-release
20: chart: incubator/raw
21: namespace: default
22: labels:
23: app: test
24: needs:
25: - default/external-secrets
26:
27:
28: # Disabled releases are treated as missing
29: - name: disabled
30: chart: incubator/raw
31: namespace: kube-system
32: installed: false
33:
34: - name: test2
35: chart: incubator/raw
36: needs:
37: - kube-system/disabled
38:
39: - name: test3
40: chart: incubator/raw
41: needs:
42: - test2
43: 43:
44: - name: test3
45: chart: incubator/raw
46: needs:
47: - test2
48:
merged environment: &{development map[] map[]} merged environment: &{development map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"
@ -121,24 +131,6 @@ first-pass rendering output of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
first-pass produced: &{development map[] map[]} first-pass produced: &{development map[] map[]}
first-pass rendering result of "helmfile_2.yaml.part.0": {development map[] map[]} first-pass rendering result of "helmfile_2.yaml.part.0": {development map[] map[]}
@ -150,24 +142,6 @@ second-pass rendering result of "helmfile_2.yaml.part.0":
1: environments: 1: environments:
2: test: {} 2: test: {}
3: shared: {} 3: shared: {}
4:
5: repositories:
6: - name: bitnami
7: url: https://charts.bitnami.com/bitnami
8:
9: releases:
10: - name: cache
11: namespace: my-app
12: chart: bitnami/redis
13: version: 17.0.7
14: labels:
15: app: test
16:
17: - name: database
18: namespace: my-app
19: chart: bitnami/postgres
20: version: 11.6.22
21:
merged environment: &{development map[] map[]} merged environment: &{development map[] map[]}
changing working directory back to "/path/to" changing working directory back to "/path/to"

View File

@ -2,39 +2,120 @@
package policy package policy
import ( import (
"bytes"
"errors" "errors"
"path/filepath" "fmt"
"regexp"
"strings"
"github.com/helmfile/helmfile/pkg/runtime" "github.com/helmfile/helmfile/pkg/runtime"
) )
var ( var (
EnvironmentsAndReleasesWithinSameYamlPartErr = errors.New("environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part") EnvironmentsAndReleasesWithinSameYamlPartErr = errors.New("environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part")
topConfigKeysRegex = regexp.MustCompile(`^[a-zA-Z]+: *$`)
separatorRegex = regexp.MustCompile(`^--- *$`)
topkeysPriority = map[string]int{
"bases": 0,
"environments": 1,
"releases": 2,
}
) )
// checkerFunc is a function that checks the helmState. // checkerFunc is a function that checks the helmState.
type checkerFunc func(string, map[string]any) (bool, error) type checkerFunc func(filePath string, content []byte) (bool, error)
func forbidEnvironmentsWithReleases(filePath string, releaseState map[string]any) (bool, error) { func forbidEnvironmentsWithReleases(filePath string, content []byte) (bool, error) {
// forbid environments and releases to be defined at the same yaml part // forbid environments and releases to be defined at the same yaml part
_, hasEnvironments := releaseState["environments"] topKeys := TopKeys(content, true)
_, hasReleases := releaseState["releases"] if len(topKeys) == 0 {
if hasEnvironments && hasReleases && (filepath.Ext(filePath) == ".gotmpl" || !runtime.V1Mode) { return true, fmt.Errorf("no top-level config keys are found in %s", filePath)
return runtime.V1Mode, EnvironmentsAndReleasesWithinSameYamlPartErr }
result := []string{}
resultKeys := map[string]interface{}{}
for _, k := range topKeys {
if k == "environments" || k == "releases" || k == "---" {
if _, ok := resultKeys[k]; !ok {
result = append(result, k)
resultKeys[k] = nil
}
}
}
if len(result) < 2 {
return false, nil
}
for i := 0; i < len(result)-1; i++ {
if result[i] != "---" && result[i+1] != "---" {
return runtime.V1Mode, EnvironmentsAndReleasesWithinSameYamlPartErr
}
} }
return false, nil return false, nil
} }
var checkerFuncs = []checkerFunc{ var checkerFuncs = []checkerFunc{
TopConfigKeysVerifier,
forbidEnvironmentsWithReleases, forbidEnvironmentsWithReleases,
} }
// Checker is a policy checker for the helmfile state. // Checker is a policy checker for the helmfile state.
func Checker(filePath string, helmState map[string]any) (bool, error) { func Checker(filePath string, content []byte) (bool, error) {
for _, fn := range checkerFuncs { for _, fn := range checkerFuncs {
if isStrict, err := fn(filePath, helmState); err != nil { if isStrict, err := fn(filePath, content); err != nil {
return isStrict, err return isStrict, err
} }
} }
return false, nil return false, nil
} }
// isTopOrderKey checks if the key is a top-level config key that must be defined in the correct order.
func isTopOrderKey(key string) bool {
_, ok := topkeysPriority[key]
return ok
}
// TopKeys returns the top-level config keys.
func TopKeys(helmfileContent []byte, hasSeparator bool) []string {
var topKeys []string
clines := bytes.Split(helmfileContent, []byte("\n"))
for _, line := range clines {
if topConfigKeysRegex.Match(line) {
lineStr := strings.Split(string(line), ":")[0]
topKeys = append(topKeys, lineStr)
}
if hasSeparator && separatorRegex.Match(line) {
topKeys = append(topKeys, strings.TrimSpace(string(line)))
}
}
return topKeys
}
// TopConfigKeysVerifier verifies the top-level config keys are defined in the correct order.
func TopConfigKeysVerifier(filePath string, helmfileContent []byte) (bool, error) {
var orderKeys, topKeys []string
topKeys = TopKeys(helmfileContent, false)
for _, k := range topKeys {
if isTopOrderKey(k) {
orderKeys = append(orderKeys, k)
}
}
if len(topKeys) == 0 {
return true, fmt.Errorf("no top-level config keys are found in %s", filePath)
}
if len(orderKeys) == 0 {
return false, nil
}
for i := 1; i < len(orderKeys); i++ {
preKey := orderKeys[i-1]
currentKey := orderKeys[i]
if topkeysPriority[preKey] > topkeysPriority[currentKey] {
return runtime.V1Mode, fmt.Errorf("top-level config key %s must be defined before %s in %s", preKey, currentKey, filePath)
}
}
return false, nil
}

View File

@ -13,51 +13,49 @@ func TestForbidEnvironmentsWithReleases(t *testing.T) {
name string name string
filePath string filePath string
v1mode bool v1mode bool
helmState map[string]any content []byte
expectedErr bool expectedErr bool
isStrict bool isStrict bool
}{ }{
{ {
name: "no error when only releases", name: "no error when only releases",
filePath: "helmfile.yaml", filePath: "helmfile.yaml.gotmpl",
v1mode: false, content: []byte("releases:\n"),
helmState: map[string]any{ v1mode: false,
"releases": any(nil),
},
expectedErr: false, expectedErr: false,
isStrict: false, isStrict: false,
}, },
{ {
name: "no error when only environments", name: "no error when only environments",
filePath: "helmfile.yaml", filePath: "helmfile.yaml.gotmpl",
v1mode: false, content: []byte("environments:\n"),
helmState: map[string]any{ v1mode: false,
"environments": map[string]any{},
},
expectedErr: false, expectedErr: false,
isStrict: false, isStrict: false,
}, },
{ {
name: "error when both releases and environments", name: "no error when has --- between releases and environments",
filePath: "helmfile.yaml", filePath: "helmfile.yaml.gotmpl",
v1mode: false, content: []byte("environments:\n---\nreleases:\n"),
helmState: map[string]any{ v1mode: false,
"environments": any(nil), expectedErr: false,
"releases": any(nil), isStrict: false,
}, },
{
name: "error when both releases and environments",
filePath: "helmfile.yaml.gotmpl",
content: []byte("environments:\nreleases:\n"),
v1mode: false,
expectedErr: true, expectedErr: true,
isStrict: false, isStrict: false,
}, },
{ {
name: "no error when both releases and environments for plain yaml on v1", name: "no error when both releases and environments for plain yaml on v1",
filePath: "helmfile.yaml", filePath: "helmfile.yaml.gotmpl",
v1mode: true, content: []byte("environments:\nreleases:\n"),
helmState: map[string]any{ v1mode: true,
"environments": any(nil), expectedErr: true,
"releases": any(nil), isStrict: true,
},
expectedErr: false,
isStrict: false,
}, },
} }
@ -69,7 +67,7 @@ func TestForbidEnvironmentsWithReleases(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
runtime.V1Mode = tc.v1mode runtime.V1Mode = tc.v1mode
isStrict, err := forbidEnvironmentsWithReleases(tc.filePath, tc.helmState) isStrict, err := forbidEnvironmentsWithReleases(tc.filePath, tc.content)
require.Equal(t, tc.isStrict, isStrict, "expected isStrict=%v, got=%v", tc.isStrict, isStrict) require.Equal(t, tc.isStrict, isStrict, "expected isStrict=%v, got=%v", tc.isStrict, isStrict)
if tc.expectedErr { if tc.expectedErr {
require.ErrorIsf(t, err, EnvironmentsAndReleasesWithinSameYamlPartErr, "expected error=%v, got=%v", EnvironmentsAndReleasesWithinSameYamlPartErr, err) require.ErrorIsf(t, err, EnvironmentsAndReleasesWithinSameYamlPartErr, "expected error=%v, got=%v", EnvironmentsAndReleasesWithinSameYamlPartErr, err)
@ -79,3 +77,146 @@ func TestForbidEnvironmentsWithReleases(t *testing.T) {
}) })
} }
} }
func TestIsTopOrderKey(t *testing.T) {
tests := []struct {
name string
item string
want bool
}{
{
name: "is top order key[bases]",
item: "bases",
want: true,
},
{
name: "is top order key[environments]",
item: "environments",
want: true,
},
{
name: "is top order key[releases]",
item: "releases",
want: true,
},
{
name: "not top order key[helmDefaults]",
item: "helmDefaults",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isTopOrderKey(tt.item); got != tt.want {
t.Errorf("isTopOrderKey() = %v, want %v", got, tt.want)
}
})
}
}
func TestTopConfigKeysVerifier(t *testing.T) {
tests := []struct {
name string
helmfileContent []byte
wantErr bool
wantStrict bool
}{
{
name: "no error when correct order[full items]",
helmfileContent: []byte("bases:\nenvironments:\nreleases:\n"),
wantErr: false,
},
{
name: "no error when correct order 00",
helmfileContent: []byte("bases:\nva:\nve:\nreleases:\n"),
wantErr: false,
},
{
name: "no error when correct order 01",
helmfileContent: []byte("a:\ne:\n"),
wantErr: false,
},
{
name: "error when not correct order 00",
helmfileContent: []byte("environments:\nbases:\n"),
wantErr: true,
},
{
name: "error when not correct order 01",
helmfileContent: []byte("environments:\nhelmDefaults:\nbases:\n"),
wantErr: true,
},
{
name: "error when not correct order 02",
helmfileContent: []byte("helmDefaults:\nenvironments:\nbases:\n"),
wantErr: true,
},
{
name: "error when not correct order 03",
helmfileContent: []byte("environments:\nva:\nve:\nbases:\n"),
wantErr: true,
},
{
name: "error when not correct order 04",
helmfileContent: []byte("bases:\nreleases:\nenvironments:\n"),
wantErr: true,
},
{
name: "no error when only has bases",
helmfileContent: []byte("bases:\n"),
},
{
name: "no error when only has environments",
helmfileContent: []byte("environments:\n"),
},
{
name: "no error when only has releases",
helmfileContent: []byte("releases:\n"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isStrict, err := TopConfigKeysVerifier("helmfile.yaml", tt.helmfileContent)
require.Equal(t, tt.wantStrict, isStrict, "expected isStrict=%v, got=%v", tt.wantStrict, isStrict)
if tt.wantErr {
require.Error(t, err, "expected error, got=%v", err)
} else {
require.NoError(t, err, "expected no error but got error: %v", err)
}
})
}
}
func TestTopKeys(t *testing.T) {
tests := []struct {
name string
helmfileContent []byte
hasSeparator bool
want []string
}{
{
name: "get top keys",
helmfileContent: []byte("bases:\nenvironments:\nreleases:\n"),
want: []string{"bases", "environments", "releases"},
},
{
name: "get top keys with ---",
helmfileContent: []byte("bases:\n---\nreleases:\n"),
hasSeparator: true,
want: []string{"bases", "---", "releases"},
},
{
name: "get empty keys",
helmfileContent: []byte(""),
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := TopKeys(tt.helmfileContent, tt.hasSeparator)
require.Equal(t, tt.want, got, "expected %v, got=%v", tt.want, got)
})
}
}

View File

@ -29,7 +29,6 @@ import (
"github.com/helmfile/helmfile/pkg/event" "github.com/helmfile/helmfile/pkg/event"
"github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/policy"
"github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/tmpl" "github.com/helmfile/helmfile/pkg/tmpl"
"github.com/helmfile/helmfile/pkg/yaml" "github.com/helmfile/helmfile/pkg/yaml"
@ -106,14 +105,6 @@ func (hs *HelmState) UnmarshalYAML(unmarshal func(any) error) error {
return err return err
} }
isStrict, err := policy.Checker(hs.FilePath, helmStateInfo)
if err != nil {
if isStrict {
return err
}
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
}
return unmarshal((*helmStateAlias)(hs)) return unmarshal((*helmStateAlias)(hs))
} }

View File

@ -1,5 +1,4 @@
Warning: environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part WARNING: environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part
Warning: environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part
Building dependency release=raw, chart=../../charts/raw-0.0.1 Building dependency release=raw, chart=../../charts/raw-0.0.1
Templating release=raw, chart=../../charts/raw-0.0.1 Templating release=raw, chart=../../charts/raw-0.0.1
--- ---