Skip to content

Commit 0d76896

Browse files
committed
Add the ability to override complianceType at the manifest level
Signed-off-by: mprahl <[email protected]>
1 parent b5900f8 commit 0d76896

File tree

5 files changed

+72
-41
lines changed

5 files changed

+72
-41
lines changed

docs/policygenerator-reference.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ policies:
6565
# Required. Path to a single file or a flat directory of files relative to the
6666
# kustomization.yaml file.
6767
- path: ""
68+
# Optional. (See policy[0].complianceType for description.)
69+
complianceType: "musthave"
6870
# Optional. A Kustomize patch to apply to the manifest(s) at the path. If there
6971
# are multiple manifests, the patch requires the apiVersion, kind, metadata.name,
7072
# and metadata.namespace (if applicable) fields to be set so Kustomize

internal/plugin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
309309
if policy.Standards == nil {
310310
policy.Standards = p.PolicyDefaults.Standards
311311
}
312+
313+
for i := range policy.Manifests {
314+
if policy.Manifests[i].ComplianceType == "" {
315+
policy.Manifests[i].ComplianceType = policy.ComplianceType
316+
}
317+
}
312318
}
313319
}
314320

internal/types/types.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
package types
33

44
type Manifest struct {
5-
Patches []map[string]interface{} `json:"patches,omitempty" yaml:"patches,omitempty"`
6-
Path string `json:"path,omitempty" yaml:"path,omitempty"`
5+
ComplianceType string `json:"complianceType,omitempty" yaml:"complianceType,omitempty"`
6+
Patches []map[string]interface{} `json:"patches,omitempty" yaml:"patches,omitempty"`
7+
Path string `json:"path,omitempty" yaml:"path,omitempty"`
78
}
89

910
type NamespaceSelector struct {

internal/utils.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import (
1515
"gopkg.in/yaml.v3"
1616
)
1717

18-
// getManifests will get all of the manifest files associated with the input policy configuration.
19-
// An error is returned if a manifest path cannot be read.
20-
func getManifests(policyConf *types.PolicyConfig) ([]map[string]interface{}, error) {
21-
manifests := []map[string]interface{}{}
18+
// getManifests will get all of the manifest files associated with the input policy configuration
19+
// separated by policyConf.Manifests entries. An error is returned if a manifest path cannot
20+
// be read.
21+
func getManifests(policyConf *types.PolicyConfig) ([][]map[string]interface{}, error) {
22+
manifests := [][]map[string]interface{}{}
2223
for _, manifest := range policyConf.Manifests {
2324
manifestPaths := []string{}
2425
manifestFiles := []map[string]interface{}{}
@@ -107,7 +108,7 @@ func getManifests(policyConf *types.PolicyConfig) ([]map[string]interface{}, err
107108
manifestFiles = *patchedFiles
108109
}
109110

110-
manifests = append(manifests, manifestFiles...)
111+
manifests = append(manifests, manifestFiles)
111112
}
112113

113114
return manifests, nil
@@ -120,42 +121,45 @@ func getManifests(policyConf *types.PolicyConfig) ([]map[string]interface{}, err
120121
// that each template includes a single manifest specified in policyConf.
121122
// An error is returned if one or more manifests cannot be read or are invalid.
122123
func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]map[string]interface{}, error) {
123-
manifests, err := getManifests(policyConf)
124+
manifestGroups, err := getManifests(policyConf)
124125
if err != nil {
125126
return nil, err
126127
}
127128

128-
if len(manifests) == 0 {
129-
return nil, fmt.Errorf(
130-
"the policy %s must specify at least one non-empty manifest file", policyConf.Name,
131-
)
132-
}
133-
134-
objectTemplatesLength := len(manifests)
129+
objectTemplatesLength := len(manifestGroups)
135130
policyTemplatesLength := 1
136131
if !policyConf.ConsolidateManifests {
137-
policyTemplatesLength = len(manifests)
132+
policyTemplatesLength = len(manifestGroups)
138133
objectTemplatesLength = 0
139134
}
140135
objectTemplates := make([]map[string]interface{}, 0, objectTemplatesLength)
141136
policyTemplates := make([]map[string]map[string]interface{}, 0, policyTemplatesLength)
142-
for _, manifest := range manifests {
143-
objTemplate := map[string]interface{}{
144-
"complianceType": policyConf.ComplianceType,
145-
"objectDefinition": manifest,
146-
}
147-
if policyConf.ConsolidateManifests {
148-
// put all objTemplate with manifest into single consolidated objectTemplates object
149-
objectTemplates = append(objectTemplates, objTemplate)
150-
} else {
151-
// casting each objTemplate with manifest to objectTemplates type
152-
// build policyTemplate for each objectTemplates
153-
policyTemplate := buildPolicyTemplate(policyConf, &[]map[string]interface{}{objTemplate})
154-
setNamespaceSelector(policyConf, policyTemplate)
155-
policyTemplates = append(policyTemplates, *policyTemplate)
137+
for i, manifestGroup := range manifestGroups {
138+
complianceType := policyConf.Manifests[i].ComplianceType
139+
for _, manifest := range manifestGroup {
140+
objTemplate := map[string]interface{}{
141+
"complianceType": complianceType,
142+
"objectDefinition": manifest,
143+
}
144+
if policyConf.ConsolidateManifests {
145+
// put all objTemplate with manifest into single consolidated objectTemplates object
146+
objectTemplates = append(objectTemplates, objTemplate)
147+
} else {
148+
// casting each objTemplate with manifest to objectTemplates type
149+
// build policyTemplate for each objectTemplates
150+
policyTemplate := buildPolicyTemplate(policyConf, &[]map[string]interface{}{objTemplate})
151+
setNamespaceSelector(policyConf, policyTemplate)
152+
policyTemplates = append(policyTemplates, *policyTemplate)
153+
}
156154
}
157155
}
158156

157+
if len(policyTemplates) == 0 && len(objectTemplates) == 0 {
158+
return nil, fmt.Errorf(
159+
"the policy %s must specify at least one non-empty manifest file", policyConf.Name,
160+
)
161+
}
162+
159163
// just build one policyTemplate by using the above consolidated objectTemplates
160164
if policyConf.ConsolidateManifests {
161165
policyTemplate := buildPolicyTemplate(policyConf, &objectTemplates)
@@ -164,8 +168,10 @@ func getPolicyTemplates(policyConf *types.PolicyConfig) ([]map[string]map[string
164168
}
165169

166170
// check the enabled expanders and add additional policy templates
167-
expandedPolicyTemplates := handleExpanders(manifests, policyConf)
168-
policyTemplates = append(policyTemplates, expandedPolicyTemplates...)
171+
for _, manifestGroup := range manifestGroups {
172+
expandedPolicyTemplates := handleExpanders(manifestGroup, policyConf)
173+
policyTemplates = append(policyTemplates, expandedPolicyTemplates...)
174+
}
169175

170176
return policyTemplates, nil
171177
}

internal/utils_test.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestGetPolicyTemplate(t *testing.T) {
3030
t.Parallel()
3131
tmpDir := t.TempDir()
3232
manifestFiles := []types.Manifest{}
33+
manifestFilesMustNotHave := []types.Manifest{}
3334
for i, enemy := range []string{"goldfish", "potato"} {
3435
manifestPath := path.Join(tmpDir, fmt.Sprintf("configmap%d.yaml", i))
3536
manifestYAML := fmt.Sprintf(
@@ -48,7 +49,14 @@ data:
4849
t.Fatalf("Failed to write %s", manifestPath)
4950
}
5051

51-
manifestFiles = append(manifestFiles, types.Manifest{Path: manifestPath})
52+
// The applyDefaults method would normally fill in ComplianceType on each manifest entry.
53+
manifestFiles = append(
54+
manifestFiles, types.Manifest{ComplianceType: "musthave", Path: manifestPath},
55+
)
56+
manifestFilesMustNotHave = append(
57+
manifestFilesMustNotHave,
58+
types.Manifest{ComplianceType: "mustnothave", Path: manifestPath},
59+
)
5260
}
5361

5462
// Write a bogus file to ensure it is not picked up when creating the policy
@@ -60,9 +68,17 @@ data:
6068
}
6169

6270
// Test both passing in individual files and a flat directory
63-
tests := []struct{ Manifests []types.Manifest }{
64-
{Manifests: manifestFiles},
65-
{Manifests: []types.Manifest{{Path: tmpDir}}},
71+
tests := []struct {
72+
ExpectedComplianceType string
73+
Manifests []types.Manifest
74+
}{
75+
{ExpectedComplianceType: "musthave", Manifests: manifestFiles},
76+
{ExpectedComplianceType: "mustnothave", Manifests: manifestFilesMustNotHave},
77+
// The applyDefaults method would normally fill in ComplianceType on each manifest entry.
78+
{
79+
ExpectedComplianceType: "musthave",
80+
Manifests: []types.Manifest{{ComplianceType: "musthave", Path: tmpDir}},
81+
},
6682
}
6783
// test ConsolidateManifests = true (default case)
6884
// policyTemplates will have only one policyTemplate
@@ -97,13 +113,13 @@ data:
97113
t.Fatal("The object-templates field is an invalid format")
98114
}
99115
assertEqual(t, len(objTemplates), 2)
100-
assertEqual(t, objTemplates[0]["complianceType"], "musthave")
116+
assertEqual(t, objTemplates[0]["complianceType"], test.ExpectedComplianceType)
101117
kind1, ok := objTemplates[0]["objectDefinition"].(map[string]interface{})["kind"]
102118
if !ok {
103119
t.Fatal("The objectDefinition field is an invalid format")
104120
}
105121
assertEqual(t, kind1, "ConfigMap")
106-
assertEqual(t, objTemplates[1]["complianceType"], "musthave")
122+
assertEqual(t, objTemplates[1]["complianceType"], test.ExpectedComplianceType)
107123
kind2, ok := objTemplates[1]["objectDefinition"].(map[string]interface{})["kind"]
108124
if !ok {
109125
t.Fatal("The objectDefinition field is an invalid format")
@@ -143,7 +159,7 @@ data:
143159
t.Fatal("The object-templates field is an invalid format")
144160
}
145161
assertEqual(t, len(objTemplates1), 1)
146-
assertEqual(t, objTemplates1[0]["complianceType"], "musthave")
162+
assertEqual(t, objTemplates1[0]["complianceType"], test.ExpectedComplianceType)
147163
kind1, ok := objTemplates1[0]["objectDefinition"].(map[string]interface{})["kind"]
148164
if !ok {
149165
t.Fatal("The objectDefinition field is an invalid format")
@@ -164,7 +180,7 @@ data:
164180
t.Fatal("The object-templates field is an invalid format")
165181
}
166182
assertEqual(t, len(objTemplates2), 1)
167-
assertEqual(t, objTemplates2[0]["complianceType"], "musthave")
183+
assertEqual(t, objTemplates2[0]["complianceType"], test.ExpectedComplianceType)
168184
kind2, ok := objTemplates2[0]["objectDefinition"].(map[string]interface{})["kind"]
169185
if !ok {
170186
t.Fatal("The objectDefinition field is an invalid format")
@@ -481,7 +497,7 @@ func TestGetPolicyTemplateInvalidPath(t *testing.T) {
481497
manifestPath := path.Join(tmpDir, "does-not-exist.yaml")
482498
policyConf := types.PolicyConfig{
483499
ComplianceType: "musthave",
484-
Manifests: []types.Manifest{{Path: manifestPath}},
500+
Manifests: []types.Manifest{{ComplianceType: "musthave", Path: manifestPath}},
485501
Name: "policy-app-config",
486502
RemediationAction: "inform",
487503
Severity: "low",

0 commit comments

Comments
 (0)