Skip to content

Commit 49e1893

Browse files
dhaiducekopenshift-merge-robot
authored andcommitted
Add LabelSelector to NamespaceSelector
Signed-off-by: Dale Haiducek <[email protected]>
1 parent e6768c4 commit 49e1893

File tree

5 files changed

+216
-6
lines changed

5 files changed

+216
-6
lines changed

internal/plugin.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,11 +533,12 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
533533
}
534534
}
535535

536-
// Only use defaults when when both include and exclude are not set on the policy
536+
// Only use defaults when when the namespaceSelector is not set on the policy
537537
nsSelector := policy.NamespaceSelector
538538
defNsSelector := p.PolicyDefaults.NamespaceSelector
539539

540-
if nsSelector.Exclude == nil && nsSelector.Include == nil {
540+
if nsSelector.Exclude == nil && nsSelector.Include == nil &&
541+
nsSelector.MatchLabels == nil && nsSelector.MatchExpressions == nil {
541542
policy.NamespaceSelector = defNsSelector
542543
}
543544

internal/plugin_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import (
66
"io/ioutil"
77
"path"
88
"path/filepath"
9+
"reflect"
910
"strings"
1011
"testing"
1112

13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1214
"open-cluster-management.io/ocm-kustomize-generator-plugins/internal/types"
1315
)
1416

@@ -2365,3 +2367,103 @@ func TestCreatePolicyWithConfigPolicyAnnotations(t *testing.T) {
23652367
})
23662368
}
23672369
}
2370+
2371+
func TestCreatePolicyWithNamespaceSelector(t *testing.T) {
2372+
t.Parallel()
2373+
tmpDir := t.TempDir()
2374+
createConfigMap(t, tmpDir, "configmap.yaml")
2375+
2376+
tests := map[string]struct {
2377+
name string
2378+
namespaceSelector types.NamespaceSelector
2379+
}{
2380+
"nil-selector": {namespaceSelector: types.NamespaceSelector{}},
2381+
"empty-selector-values": {
2382+
namespaceSelector: types.NamespaceSelector{
2383+
Include: []string{},
2384+
Exclude: []string{},
2385+
MatchLabels: &map[string]string{},
2386+
MatchExpressions: &[]metav1.LabelSelectorRequirement{},
2387+
},
2388+
},
2389+
"completely-filled-values": {
2390+
namespaceSelector: types.NamespaceSelector{
2391+
Include: []string{"test-ns-1", "test-ns-2"},
2392+
Exclude: []string{"*-ns-[1]"},
2393+
MatchLabels: &map[string]string{
2394+
"testing": "is awesome",
2395+
},
2396+
MatchExpressions: &[]metav1.LabelSelectorRequirement{{
2397+
Key: "door",
2398+
Operator: "Exists",
2399+
}},
2400+
},
2401+
},
2402+
"include-exclude-only": {
2403+
namespaceSelector: types.NamespaceSelector{
2404+
Include: []string{"test-ns-1", "test-ns-2"},
2405+
Exclude: []string{"*-ns-[1]"},
2406+
},
2407+
},
2408+
"label-selectors-only": {
2409+
namespaceSelector: types.NamespaceSelector{
2410+
MatchLabels: &map[string]string{
2411+
"testing": "is awesome",
2412+
},
2413+
MatchExpressions: &[]metav1.LabelSelectorRequirement{{
2414+
Key: "door",
2415+
Operator: "Exists",
2416+
}},
2417+
},
2418+
},
2419+
}
2420+
2421+
for name, test := range tests {
2422+
test := test
2423+
2424+
t.Run(name, func(t *testing.T) {
2425+
t.Parallel()
2426+
2427+
p := Plugin{}
2428+
p.PolicyDefaults.Namespace = "my-policies"
2429+
p.PolicyDefaults.NamespaceSelector = types.NamespaceSelector{
2430+
MatchLabels: &map[string]string{},
2431+
}
2432+
policyConf := types.PolicyConfig{
2433+
Name: "policy-app-config", Manifests: []types.Manifest{
2434+
{Path: path.Join(tmpDir, "configmap.yaml")},
2435+
},
2436+
}
2437+
policyConf.NamespaceSelector = test.namespaceSelector
2438+
2439+
p.Policies = append(p.Policies, policyConf)
2440+
p.applyDefaults(map[string]interface{}{})
2441+
2442+
err := p.createPolicy(&p.Policies[0])
2443+
if err != nil {
2444+
t.Fatal(err.Error())
2445+
}
2446+
2447+
output := p.outputBuffer.Bytes()
2448+
policyManifests, err := unmarshalManifestBytes(output)
2449+
if err != nil {
2450+
t.Fatal(err.Error())
2451+
}
2452+
// nolint: forcetypeassert
2453+
spec := (*policyManifests)[0]["spec"].(map[string]interface{})
2454+
policyTemplates := spec["policy-templates"].([]interface{})
2455+
// nolint: forcetypeassert
2456+
configPolicy := policyTemplates[0].(map[string]interface{})["objectDefinition"].(map[string]interface{})
2457+
// nolint: forcetypeassert
2458+
configPolicySpec := configPolicy["spec"].(map[string]interface{})
2459+
// nolint: forcetypeassert
2460+
configPolicySelector := configPolicySpec["namespaceSelector"].(map[string]interface{})
2461+
2462+
if reflect.DeepEqual(test.namespaceSelector, types.NamespaceSelector{}) {
2463+
assertSelectorEqual(t, configPolicySelector, p.PolicyDefaults.NamespaceSelector)
2464+
} else {
2465+
assertSelectorEqual(t, configPolicySelector, test.namespaceSelector)
2466+
}
2467+
})
2468+
}
2469+
}

internal/types/types.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
// Copyright Contributors to the Open Cluster Management project
22
package types
33

4+
import (
5+
"fmt"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
)
9+
410
type Manifest struct {
511
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
612
MetadataComplianceType string `json:"metadataComplianceType,omitempty" yaml:"metadataComplianceType,omitempty"`
@@ -10,8 +16,28 @@ type Manifest struct {
1016
}
1117

1218
type NamespaceSelector struct {
13-
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
14-
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
19+
Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
20+
Include []string `json:"include,omitempty" yaml:"include,omitempty"`
21+
MatchLabels *map[string]string `json:"matchLabels,omitempty" yaml:"matchLabels,omitempty"`
22+
MatchExpressions *[]metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" yaml:"matchExpressions,omitempty"`
23+
}
24+
25+
// Define String() so that the LabelSelector is dereferenced in the logs
26+
func (t NamespaceSelector) String() string {
27+
fmtSelectorStr := "{include:%s,exclude:%s,matchLabels:%+v,matchExpressions:%+v}"
28+
if t.MatchLabels == nil && t.MatchExpressions == nil {
29+
return fmt.Sprintf(fmtSelectorStr, t.Include, t.Exclude, nil, nil)
30+
}
31+
32+
if t.MatchLabels == nil {
33+
return fmt.Sprintf(fmtSelectorStr, t.Include, t.Exclude, nil, *t.MatchExpressions)
34+
}
35+
36+
if t.MatchExpressions == nil {
37+
return fmt.Sprintf(fmtSelectorStr, t.Include, t.Exclude, *t.MatchLabels, nil)
38+
}
39+
40+
return fmt.Sprintf(fmtSelectorStr, t.Include, t.Exclude, *t.MatchLabels, *t.MatchExpressions)
1541
}
1642

1743
type PlacementConfig struct {

internal/utils.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,13 @@ func isPolicyTypeManifest(manifest map[string]interface{}) (bool, error) {
235235

236236
// setNamespaceSelector sets the namespace selector, if set, on the input policy template.
237237
func setNamespaceSelector(policyConf *types.PolicyConfig, policyTemplate *map[string]map[string]interface{}) {
238-
if policyConf.NamespaceSelector.Exclude != nil || policyConf.NamespaceSelector.Include != nil {
238+
selector := policyConf.NamespaceSelector
239+
if selector.Exclude != nil ||
240+
selector.Include != nil ||
241+
selector.MatchLabels != nil ||
242+
selector.MatchExpressions != nil {
239243
spec := (*policyTemplate)["objectDefinition"]["spec"].(map[string]interface{})
240-
spec["namespaceSelector"] = policyConf.NamespaceSelector
244+
spec["namespaceSelector"] = selector
241245
}
242246
}
243247

internal/utils_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,83 @@ func assertReflectEqual(t *testing.T, a interface{}, b interface{}) {
3030
}
3131
}
3232

33+
func assertSelectorEqual(t *testing.T, a map[string]interface{}, b types.NamespaceSelector) {
34+
t.Helper()
35+
36+
if !compareSelectors(a, b) {
37+
t.Fatalf("%s != %s", a, b)
38+
}
39+
}
40+
41+
func compareStringArrays(a []interface{}, b []string) bool {
42+
// Account for when b is []string(nil)
43+
if len(a) == 0 && len(b) == 0 {
44+
return true
45+
}
46+
47+
// Create a string array from []interface{}
48+
aTyped := make([]string, len(a))
49+
for i, val := range a {
50+
aTyped[i] = val.(string)
51+
}
52+
53+
return reflect.DeepEqual(aTyped, b)
54+
}
55+
56+
func compareSelectors(a map[string]interface{}, b types.NamespaceSelector) bool {
57+
if includeA, ok := a["include"].([]interface{}); ok {
58+
if !compareStringArrays(includeA, b.Include) {
59+
return false
60+
}
61+
} else if len(b.Include) != 0 {
62+
return false
63+
}
64+
65+
if excludeA, ok := a["exclude"].([]interface{}); ok {
66+
if !compareStringArrays(excludeA, b.Exclude) {
67+
return false
68+
}
69+
} else if len(b.Exclude) != 0 {
70+
return false
71+
}
72+
73+
if matchLabelsA, ok := a["matchLabels"].(map[string]string); ok {
74+
if !reflect.DeepEqual(matchLabelsA, b.MatchLabels) {
75+
return false
76+
}
77+
} else if matchLabelsA != nil && b.MatchLabels != nil {
78+
return false
79+
}
80+
81+
if matchExpressionsA, ok := a["matchExpressions"].([]interface{}); ok {
82+
if a["matchExpressions"] != b.MatchExpressions {
83+
if b.MatchExpressions == nil {
84+
return false
85+
}
86+
87+
if len(matchExpressionsA) != len(*b.MatchExpressions) {
88+
return false
89+
}
90+
91+
for i := range matchExpressionsA {
92+
meA := matchExpressionsA[i]
93+
valuesA := meA.(map[string]interface{})["values"].([]interface{})
94+
meB := (*b.MatchExpressions)[i]
95+
96+
if meA.(map[string]interface{})["key"].(string) != meB.Key ||
97+
meA.(map[string]interface{})["operator"].(string) != string(meB.Operator) ||
98+
!compareStringArrays(valuesA, meB.Values) {
99+
return false
100+
}
101+
}
102+
}
103+
} else if matchExpressionsA != nil && b.MatchExpressions != nil {
104+
return false
105+
}
106+
107+
return true
108+
}
109+
33110
func TestGetPolicyTemplate(t *testing.T) {
34111
t.Parallel()
35112
tmpDir := t.TempDir()

0 commit comments

Comments
 (0)