Skip to content

Commit ce7bb18

Browse files
authored
DVO-146: Improve config YAML reading & some tests (#278)
1 parent cc02c22 commit ce7bb18

File tree

2 files changed

+103
-79
lines changed

2 files changed

+103
-79
lines changed

Diff for: pkg/configmap/configmap_watcher.go

+6-34
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,20 @@ import (
88
"time"
99

1010
"golang.stackrox.io/kube-linter/pkg/config"
11-
"gopkg.in/yaml.v3"
1211

1312
"github.com/app-sre/deployment-validation-operator/pkg/validations"
13+
"github.com/ghodss/yaml"
1414
"github.com/go-logr/logr"
1515
apicorev1 "k8s.io/api/core/v1"
16-
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1716
"k8s.io/client-go/informers"
1817
"k8s.io/client-go/kubernetes"
1918
"k8s.io/client-go/rest"
2019
"k8s.io/client-go/tools/cache"
2120
"sigs.k8s.io/controller-runtime/pkg/log"
2221
)
2322

24-
// this structure mirrors Kube-Linter configuration structure
25-
// it is used as a bridge to unmarshall ConfigMap data
26-
// doc: https://pkg.go.dev/golang.stackrox.io/kube-linter/pkg/config#Config
27-
type KubeLinterChecks struct {
28-
Checks struct {
29-
AddAllBuiltIn bool `yaml:"addAllBuiltIn,omitempty"`
30-
DoNotAutoAddDefaults bool `yaml:"doNotAutoAddDefaults,omitempty"`
31-
Exclude []string `yaml:"exclude,omitempty"`
32-
Include []string `yaml:"include,omitempty"`
33-
IgnorePaths []string `yaml:"ignorePaths,omitempty"`
34-
} `yaml:"checks"`
35-
}
36-
3723
type Watcher struct {
3824
clientset kubernetes.Interface
39-
checks KubeLinterChecks
4025
cfg config.Config
4126
ch chan struct{}
4227
logger logr.Logger
@@ -74,17 +59,6 @@ func NewWatcher(cfg *rest.Config) (*Watcher, error) {
7459
}, nil
7560
}
7661

77-
// GetStaticKubelinterConfig returns the ConfigMap's checks configuration
78-
func (cmw *Watcher) GetStaticKubelinterConfig(ctx context.Context) (config.Config, error) {
79-
cm, err := cmw.clientset.CoreV1().
80-
ConfigMaps(cmw.namespace).Get(ctx, configMapName, v1.GetOptions{})
81-
if err != nil {
82-
return config.Config{}, fmt.Errorf("getting initial configuration: %w", err)
83-
}
84-
85-
return cmw.getKubeLinterConfig(cm.Data[configMapDataAccess])
86-
}
87-
8862
// Start will update the channel structure with new configuration data from ConfigMap update event
8963
func (cmw *Watcher) Start(ctx context.Context) error {
9064
factory := informers.NewSharedInformerFactoryWithOptions(
@@ -106,7 +80,7 @@ func (cmw *Watcher) Start(ctx context.Context) error {
10680
"namespace", newCm.GetNamespace(),
10781
)
10882

109-
cfg, err := cmw.getKubeLinterConfig(newCm.Data[configMapDataAccess])
83+
cfg, err := readConfig(newCm.Data[configMapDataAccess])
11084
if err != nil {
11185
cmw.logger.Error(err, "ConfigMap data format")
11286
return
@@ -130,7 +104,7 @@ func (cmw *Watcher) Start(ctx context.Context) error {
130104
"namespace", newCm.GetNamespace(),
131105
)
132106

133-
cfg, err := cmw.getKubeLinterConfig(newCm.Data[configMapDataAccess])
107+
cfg, err := readConfig(newCm.Data[configMapDataAccess])
134108
if err != nil {
135109
cmw.logger.Error(err, "ConfigMap data format")
136110
return
@@ -172,18 +146,16 @@ func (cmw *Watcher) GetConfig() config.Config {
172146
return cmw.cfg
173147
}
174148

175-
// getKubeLinterConfig returns a valid Kube-linter Config structure
149+
// readConfig returns a valid Kube-linter Config structure
176150
// based on the checks received by the string
177-
func (cmw *Watcher) getKubeLinterConfig(data string) (config.Config, error) {
151+
func readConfig(data string) (config.Config, error) {
178152
var cfg config.Config
179153

180-
err := yaml.Unmarshal([]byte(data), &cmw.checks)
154+
err := yaml.Unmarshal([]byte(data), &cfg, yaml.DisallowUnknownFields)
181155
if err != nil {
182156
return cfg, fmt.Errorf("unmarshalling configmap data: %w", err)
183157
}
184158

185-
cfg.Checks = config.ChecksConfig(cmw.checks.Checks)
186-
187159
return cfg, nil
188160
}
189161

Diff for: pkg/configmap/configmap_watcher_test.go

+97-45
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,125 @@
11
package configmap
22

33
import (
4-
"context"
4+
"fmt"
55
"testing"
66

77
"github.com/stretchr/testify/assert"
88
"golang.stackrox.io/kube-linter/pkg/config"
9-
corev1 "k8s.io/api/core/v1"
10-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11-
"k8s.io/apimachinery/pkg/runtime"
12-
kubefake "k8s.io/client-go/kubernetes/fake"
139
)
1410

15-
func TestStaticConfigMapWatcher(t *testing.T) {
16-
var configMapNamespace = "deployment-validation-operator"
17-
18-
testCases := []struct {
19-
name string
20-
data string
21-
checks config.ChecksConfig
11+
func TestReadConfig(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
configData string
15+
expectedConfig config.Config
16+
expectedError error
2217
}{
2318
{
24-
name: "kube-linter 'doNotAutoAddDefaults' is gathered from configuration",
25-
data: "checks:\n doNotAutoAddDefaults: true",
26-
checks: config.ChecksConfig{
27-
DoNotAutoAddDefaults: true,
19+
name: "Basic valid config",
20+
configData: `
21+
checks:
22+
doNotAutoAddDefaults: false
23+
addAllBuiltIn: true
24+
include:
25+
- "unset-memory-requirements"
26+
- "unset-cpu-requirements"`,
27+
expectedConfig: config.Config{
28+
Checks: config.ChecksConfig{
29+
AddAllBuiltIn: true,
30+
DoNotAutoAddDefaults: false,
31+
Include: []string{"unset-memory-requirements", "unset-cpu-requirements"}, // nolint: lll
32+
},
2833
},
34+
expectedError: nil,
2935
},
3036
{
31-
name: "kube-linter 'addAllBuiltIn' is gathered from configuration",
32-
data: "checks:\n addAllBuiltIn: true",
33-
checks: config.ChecksConfig{
34-
AddAllBuiltIn: true,
37+
name: "Invalid config field \"doNotAutoAddDefaultsAAA\"",
38+
configData: `
39+
checks:
40+
doNotAutoAddDefaultsAAA: false
41+
addAllBuiltIn: true
42+
include:
43+
- "unset-memory-requirements"
44+
- "unset-cpu-requirements"`,
45+
expectedError: fmt.Errorf("unmarshalling configmap data: error unmarshaling JSON: while decoding JSON: json: unknown field \"doNotAutoAddDefaultsAAA\""), // nolint: lll
46+
expectedConfig: config.Config{
47+
Checks: config.ChecksConfig{
48+
AddAllBuiltIn: true,
49+
DoNotAutoAddDefaults: false,
50+
Include: []string{"unset-memory-requirements", "unset-cpu-requirements"}, // nolint: lll
51+
},
3552
},
3653
},
3754
{
38-
name: "kube-linter 'exclude' is gathered from configuration",
39-
data: "checks:\n exclude: [\"check1\", \"check2\"]",
40-
checks: config.ChecksConfig{
41-
Exclude: []string{"check1", "check2"},
55+
name: "Invalid config field \"include\"",
56+
configData: `
57+
checks:
58+
doNotAutoAddDefaults: false
59+
addAllBuiltIn: true
60+
includeaa:
61+
- "unset-memory-requirements"
62+
- "unset-cpu-requirements"`,
63+
expectedError: fmt.Errorf("unmarshalling configmap data: error unmarshaling JSON: while decoding JSON: json: unknown field \"includeaa\""), // nolint: lll
64+
expectedConfig: config.Config{
65+
Checks: config.ChecksConfig{
66+
AddAllBuiltIn: true,
67+
DoNotAutoAddDefaults: false,
68+
},
4269
},
4370
},
4471
{
45-
name: "kube-linter 'include' is gathered from configuration",
46-
data: "checks:\n include: [\"check1\", \"check2\"]",
47-
checks: config.ChecksConfig{
48-
Include: []string{"check1", "check2"},
72+
name: "Valid config with custom check",
73+
configData: `
74+
checks:
75+
doNotAutoAddDefaults: false
76+
addAllBuiltIn: true
77+
include:
78+
- "unset-memory-requirements"
79+
customChecks:
80+
- name: test-minimum-replicas
81+
description: "some description"
82+
remediation: "some remediation"
83+
template: minimum-replicas
84+
params:
85+
minReplicas: 3
86+
scope:
87+
objectKinds:
88+
- DeploymentLike`,
89+
expectedError: nil,
90+
expectedConfig: config.Config{
91+
Checks: config.ChecksConfig{
92+
AddAllBuiltIn: true,
93+
DoNotAutoAddDefaults: false,
94+
Include: []string{"unset-memory-requirements"},
95+
},
96+
CustomChecks: []config.Check{
97+
{
98+
Name: "test-minimum-replicas",
99+
Description: "some description",
100+
Remediation: "some remediation",
101+
Template: "minimum-replicas",
102+
Params: map[string]interface{}{
103+
"minReplicas": float64(3),
104+
},
105+
Scope: &config.ObjectKindsDesc{
106+
ObjectKinds: []string{"DeploymentLike"},
107+
},
108+
},
109+
},
49110
},
50111
},
51112
}
113+
for _, tt := range tests {
114+
t.Run(tt.name, func(t *testing.T) {
52115

53-
for _, testCase := range testCases {
54-
t.Run(testCase.name, func(t *testing.T) {
55-
// Given
56-
cm := &corev1.ConfigMap{
57-
ObjectMeta: metav1.ObjectMeta{Namespace: configMapNamespace, Name: configMapName},
58-
Data: map[string]string{
59-
"deployment-validation-operator-config.yaml": testCase.data,
60-
},
116+
cfg, err := readConfig(tt.configData)
117+
if tt.expectedError != nil {
118+
assert.Equal(t, tt.expectedError.Error(), err.Error())
119+
} else {
120+
assert.NoError(t, err)
61121
}
62-
client := kubefake.NewSimpleClientset([]runtime.Object{cm}...)
63-
mock := Watcher{clientset: client, namespace: configMapNamespace}
64-
65-
// When
66-
test, err := mock.GetStaticKubelinterConfig(context.Background())
67-
68-
// Assert
69-
assert.NoError(t, err)
70-
assert.Equal(t, testCase.checks, test.Checks)
122+
assert.Equal(t, tt.expectedConfig, cfg)
71123
})
72124
}
73125
}

0 commit comments

Comments
 (0)