Skip to content

Commit b73320b

Browse files
mprahlopenshift-merge-bot[bot]
authored andcommitted
Add setting custom messages on configuration policies
Relates: https://issues.redhat.com/browse/ACM-13134 Co-authored-by: Jason Zhang <[email protected]> Signed-off-by: mprahl <[email protected]>
1 parent afb8a69 commit b73320b

File tree

5 files changed

+311
-10
lines changed

5 files changed

+311
-10
lines changed

docs/policygenerator-reference.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ policyDefaults:
3535
# If set to false, only the policy framework specific policy labels and annotations will be copied to the replicated
3636
# policy.
3737
copyPolicyMetadata: true
38+
# Optional. customMessage configures the compliance messages emitted by the configuration policy to use one
39+
# of the specified Go templates based on the current compliance. See the ConfigurationPolicy API documentation for
40+
# more details.
41+
customMessage:
42+
compliant: ""
43+
noncompliant: ""
3844
# Optional. A list of objects that should be in specific compliance states before this policy is applied. Cannot be
3945
# specified when policyDefaults.orderPolicies is set to true.
4046
dependencies:
@@ -253,6 +259,11 @@ policies:
253259
# Optional. (See policyDefaults.namespaceSelector for description.)
254260
# Cannot be specified when policyDefaults.consolidateManifests is set to true.
255261
namespaceSelector: {}
262+
# Optional. (See policyDefaults.customMessage for description.)
263+
# Cannot be specified when policyDefaults.consolidateManifests is set to true.
264+
customMessage:
265+
compliant: ""
266+
noncompliant: ""
256267
# Optional. (See policyDefaults.evaluationInterval for description.)
257268
# Cannot be specified when policyDefaults.consolidateManifests is set to true.
258269
evaluationInterval: {}
@@ -312,6 +323,10 @@ policies:
312323
configurationPolicyAnnotations: {}
313324
# Optional. (See policyDefaults.copyPolicyMetadata for description.)
314325
copyPolicyMetadata: true
326+
# Optional. (See policyDefaults.customMessage for description.)
327+
customMessage:
328+
compliant: ""
329+
noncompliant: ""
315330
# Optional. (See policyDefaults.metadataComplianceType for description.)
316331
metadataComplianceType: ""
317332
# Optional. (See policyDefaults.controls for description.)

internal/plugin.go

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -324,15 +324,13 @@ func getPolicySet(config map[string]interface{}, policySetIndex int) map[string]
324324
return getArrayObject(config, "policySets", policySetIndex)
325325
}
326326

327-
// getEvaluationInterval will return the evaluation interval of specified policy in the Policy Generator configuration
328-
// YAML.
329-
func isEvaluationIntervalSet(config map[string]interface{}, policyIndex int, complianceType string) bool {
327+
func isComplianceConfigSet(config map[string]interface{}, policyIndex int, field, complianceType string) bool {
330328
policy := getPolicy(config, policyIndex)
331329
if policy == nil {
332330
return false
333331
}
334332

335-
evaluationInterval, ok := policy["evaluationInterval"].(map[string]interface{})
333+
evaluationInterval, ok := policy[field].(map[string]interface{})
336334
if !ok {
337335
return false
338336
}
@@ -342,10 +340,20 @@ func isEvaluationIntervalSet(config map[string]interface{}, policyIndex int, com
342340
return set
343341
}
344342

345-
// isEvaluationIntervalSetManifest will return whether the evaluation interval of the specified manifest
346-
// of the specified policy is set in the Policy Generator configuration YAML.
347-
func isEvaluationIntervalSetManifest(
348-
config map[string]interface{}, policyIndex int, manifestIndex int, complianceType string,
343+
// isEvaluationIntervalSet will return if the evaluation interval is set for the specified policy in the Policy
344+
// Generator configuration YAML.
345+
func isEvaluationIntervalSet(config map[string]interface{}, policyIndex int, complianceType string) bool {
346+
return isComplianceConfigSet(config, policyIndex, "evaluationInterval", complianceType)
347+
}
348+
349+
// isCustomMessageSet will return if the custom message is set for the specified policy in the Policy
350+
// Generator configuration YAML.
351+
func isCustomMessageSet(config map[string]interface{}, policyIndex int, complianceType string) bool {
352+
return isComplianceConfigSet(config, policyIndex, "customMessage", complianceType)
353+
}
354+
355+
func isComplianceConfigSetManifest(
356+
config map[string]interface{}, policyIndex int, manifestIndex int, field, complianceType string,
349357
) bool {
350358
policy := getPolicy(config, policyIndex)
351359
if policy == nil {
@@ -366,16 +374,32 @@ func isEvaluationIntervalSetManifest(
366374
return false
367375
}
368376

369-
evaluationInterval, ok := manifest["evaluationInterval"].(map[string]interface{})
377+
fieldValue, ok := manifest[field].(map[string]interface{})
370378
if !ok {
371379
return false
372380
}
373381

374-
_, set := evaluationInterval[complianceType].(string)
382+
_, set := fieldValue[complianceType].(string)
375383

376384
return set
377385
}
378386

387+
// isEvaluationIntervalSetManifest will return whether the evaluation interval of the specified manifest
388+
// of the specified policy is set in the Policy Generator configuration YAML.
389+
func isEvaluationIntervalSetManifest(
390+
config map[string]interface{}, policyIndex int, manifestIndex int, complianceType string,
391+
) bool {
392+
return isComplianceConfigSetManifest(config, policyIndex, manifestIndex, "evaluationInterval", complianceType)
393+
}
394+
395+
// isCustomMessageSetManifest will return whether the compliance message of the specified manifest
396+
// of the specified policy is set in the Policy Generator configuration YAML.
397+
func isCustomMessageSetManifest(
398+
config map[string]interface{}, policyIndex int, manifestIndex int, complianceType string,
399+
) bool {
400+
return isComplianceConfigSetManifest(config, policyIndex, manifestIndex, "customMessage", complianceType)
401+
}
402+
379403
func isPolicyFieldSet(config map[string]interface{}, policyIndex int, field string) bool {
380404
policy := getPolicy(config, policyIndex)
381405
if policy == nil {
@@ -598,6 +622,21 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
598622
}
599623
}
600624

625+
// Only use the policyDefault customMessage value when it's not explicitly set on the policy.
626+
if policy.CustomMessage.Compliant == "" {
627+
set := isCustomMessageSet(unmarshaledConfig, i, "compliant")
628+
if !set {
629+
policy.CustomMessage.Compliant = p.PolicyDefaults.CustomMessage.Compliant
630+
}
631+
}
632+
633+
if policy.CustomMessage.NonCompliant == "" {
634+
set := isCustomMessageSet(unmarshaledConfig, i, "noncompliant")
635+
if !set {
636+
policy.CustomMessage.NonCompliant = p.PolicyDefaults.CustomMessage.NonCompliant
637+
}
638+
}
639+
601640
if policy.PruneObjectBehavior == "" {
602641
policy.PruneObjectBehavior = p.PolicyDefaults.PruneObjectBehavior
603642
}
@@ -727,6 +766,20 @@ func (p *Plugin) applyDefaults(unmarshaledConfig map[string]interface{}) {
727766
}
728767
}
729768

769+
if manifest.CustomMessage.Compliant == "" {
770+
set := isCustomMessageSetManifest(unmarshaledConfig, i, j, "compliant")
771+
if !set {
772+
manifest.CustomMessage.Compliant = policy.CustomMessage.Compliant
773+
}
774+
}
775+
776+
if manifest.CustomMessage.NonCompliant == "" {
777+
set := isCustomMessageSetManifest(unmarshaledConfig, i, j, "noncompliant")
778+
if !set {
779+
manifest.CustomMessage.NonCompliant = policy.CustomMessage.NonCompliant
780+
}
781+
}
782+
730783
selector := manifest.NamespaceSelector
731784
if selector.Exclude == nil && selector.Include == nil &&
732785
selector.MatchLabels == nil && selector.MatchExpressions == nil {
@@ -1067,6 +1120,10 @@ func (p *Plugin) assertValidConfig() error {
10671120
return fmt.Errorf(errorMsgFmt, "evaluationInterval")
10681121
}
10691122

1123+
if !reflect.DeepEqual(manifest.CustomMessage, policy.CustomMessage) {
1124+
return fmt.Errorf(errorMsgFmt, "customMessage")
1125+
}
1126+
10701127
if !reflect.DeepEqual(manifest.NamespaceSelector, policy.NamespaceSelector) {
10711128
return fmt.Errorf(errorMsgFmt, "namespaceSelector")
10721129
}

internal/plugin_test.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4650,6 +4650,213 @@ func TestCreatePolicyWithCopyPolicyMetadata(t *testing.T) {
46504650
}
46514651
}
46524652

4653+
func TestCreatePolicyWithCustomMessage(t *testing.T) {
4654+
t.Parallel()
4655+
tmpDir := t.TempDir()
4656+
createConfigMap(t, tmpDir, "configmap.yaml")
4657+
createConfigMap(t, tmpDir, "configmap2.yaml")
4658+
4659+
p := Plugin{}
4660+
var err error
4661+
4662+
p.baseDirectory, err = filepath.EvalSymlinks(tmpDir)
4663+
if err != nil {
4664+
t.Fatal(err.Error())
4665+
}
4666+
4667+
p.PolicyDefaults.Namespace = "my-policies"
4668+
p.PolicyDefaults.CustomMessage = types.CustomMessage{
4669+
Compliant: "{{ default }}",
4670+
NonCompliant: "{{ default }}",
4671+
}
4672+
4673+
policyConf := types.PolicyConfig{
4674+
Name: "policy-app-config",
4675+
PolicyOptions: types.PolicyOptions{
4676+
ConsolidateManifests: false,
4677+
},
4678+
Manifests: []types.Manifest{
4679+
{
4680+
Path: path.Join(tmpDir, "configmap.yaml"),
4681+
},
4682+
{
4683+
Path: path.Join(tmpDir, "configmap2.yaml"),
4684+
},
4685+
},
4686+
}
4687+
p.Policies = append(p.Policies, policyConf)
4688+
4689+
// Ensure values are correctly propagated/overridden
4690+
p.applyDefaults(map[string]interface{}{})
4691+
assertEqual(t, policyConf.Manifests[0].ConfigurationPolicyOptions.CustomMessage.Compliant, "{{ default }}")
4692+
assertEqual(t, policyConf.Manifests[0].ConfigurationPolicyOptions.CustomMessage.NonCompliant, "{{ default }}")
4693+
assertEqual(t, policyConf.Manifests[1].ConfigurationPolicyOptions.CustomMessage.Compliant, "{{ default }}")
4694+
assertEqual(t, policyConf.Manifests[1].ConfigurationPolicyOptions.CustomMessage.NonCompliant, "{{ default }}")
4695+
4696+
// With consolidateManifest = false
4697+
policyConf.ConfigurationPolicyOptions = types.ConfigurationPolicyOptions{
4698+
CustomMessage: types.CustomMessage{
4699+
Compliant: "{{ root }}",
4700+
NonCompliant: "{{ root }}",
4701+
},
4702+
}
4703+
policyConf.Manifests[0].ConfigurationPolicyOptions = types.ConfigurationPolicyOptions{
4704+
CustomMessage: types.CustomMessage{
4705+
Compliant: "{{ manifest1 }}",
4706+
NonCompliant: "{{ manifest1 }}",
4707+
},
4708+
}
4709+
policyConf.Manifests[1].ConfigurationPolicyOptions = types.ConfigurationPolicyOptions{
4710+
CustomMessage: types.CustomMessage{
4711+
Compliant: "{{ manifest2 }}",
4712+
NonCompliant: "{{ manifest2 }}",
4713+
},
4714+
}
4715+
4716+
p.applyDefaults(map[string]interface{}{})
4717+
4718+
err = p.createPolicy(&policyConf)
4719+
if err != nil {
4720+
t.Fatal(err.Error())
4721+
}
4722+
4723+
output := p.outputBuffer.String()
4724+
expected := `
4725+
---
4726+
apiVersion: policy.open-cluster-management.io/v1
4727+
kind: Policy
4728+
metadata:
4729+
annotations:
4730+
policy.open-cluster-management.io/categories: ""
4731+
policy.open-cluster-management.io/controls: ""
4732+
policy.open-cluster-management.io/description: ""
4733+
policy.open-cluster-management.io/standards: ""
4734+
name: policy-app-config
4735+
namespace: my-policies
4736+
spec:
4737+
copyPolicyMetadata: false
4738+
disabled: false
4739+
policy-templates:
4740+
- objectDefinition:
4741+
apiVersion: policy.open-cluster-management.io/v1
4742+
kind: ConfigurationPolicy
4743+
metadata:
4744+
name: policy-app-config
4745+
spec:
4746+
customMessage:
4747+
compliant: '{{ manifest1 }}'
4748+
noncompliant: '{{ manifest1 }}'
4749+
object-templates:
4750+
- complianceType: musthave
4751+
objectDefinition:
4752+
apiVersion: v1
4753+
data:
4754+
game.properties: enemies=potato
4755+
kind: ConfigMap
4756+
metadata:
4757+
name: my-configmap
4758+
remediationAction: inform
4759+
severity: low
4760+
- objectDefinition:
4761+
apiVersion: policy.open-cluster-management.io/v1
4762+
kind: ConfigurationPolicy
4763+
metadata:
4764+
name: policy-app-config2
4765+
spec:
4766+
customMessage:
4767+
compliant: '{{ manifest2 }}'
4768+
noncompliant: '{{ manifest2 }}'
4769+
object-templates:
4770+
- complianceType: musthave
4771+
objectDefinition:
4772+
apiVersion: v1
4773+
data:
4774+
game.properties: enemies=potato
4775+
kind: ConfigMap
4776+
metadata:
4777+
name: my-configmap
4778+
remediationAction: inform
4779+
severity: low
4780+
remediationAction: inform
4781+
`
4782+
4783+
expected = strings.TrimPrefix(expected, "\n")
4784+
assertEqual(t, output, expected)
4785+
p.outputBuffer.Reset()
4786+
4787+
// With consolidateManifest = true
4788+
policyConf.PolicyOptions.ConsolidateManifests = true
4789+
err = p.assertValidConfig()
4790+
expectedErr := "the policy policy-app-config has the customMessage " +
4791+
"value set on manifest[0] but consolidateManifests is true"
4792+
assertEqual(t, err.Error(), expectedErr)
4793+
4794+
// Note: customMessage field at the manifest level must be set to
4795+
// the same value as in the policy level when consolidateManifest = true
4796+
// to successfully generate a policy. If customMessage field is unset
4797+
// at the manifest level, applyDefaults() can be used to populate this field
4798+
// if it's set at the policyDefaults or policy level.
4799+
policyConf.Manifests[0].ConfigurationPolicyOptions.CustomMessage.Compliant = "{{ root }}"
4800+
policyConf.Manifests[0].ConfigurationPolicyOptions.CustomMessage.NonCompliant = "{{ root }}"
4801+
policyConf.Manifests[1].ConfigurationPolicyOptions.CustomMessage.Compliant = "{{ root }}"
4802+
policyConf.Manifests[1].ConfigurationPolicyOptions.CustomMessage.NonCompliant = "{{ root }}"
4803+
4804+
err = p.createPolicy(&policyConf)
4805+
if err != nil {
4806+
t.Fatal(err.Error())
4807+
}
4808+
4809+
output = p.outputBuffer.String()
4810+
expected = `
4811+
---
4812+
apiVersion: policy.open-cluster-management.io/v1
4813+
kind: Policy
4814+
metadata:
4815+
annotations:
4816+
policy.open-cluster-management.io/categories: ""
4817+
policy.open-cluster-management.io/controls: ""
4818+
policy.open-cluster-management.io/description: ""
4819+
policy.open-cluster-management.io/standards: ""
4820+
name: policy-app-config
4821+
namespace: my-policies
4822+
spec:
4823+
copyPolicyMetadata: false
4824+
disabled: false
4825+
policy-templates:
4826+
- objectDefinition:
4827+
apiVersion: policy.open-cluster-management.io/v1
4828+
kind: ConfigurationPolicy
4829+
metadata:
4830+
name: policy-app-config
4831+
spec:
4832+
customMessage:
4833+
compliant: '{{ root }}'
4834+
noncompliant: '{{ root }}'
4835+
object-templates:
4836+
- complianceType: musthave
4837+
objectDefinition:
4838+
apiVersion: v1
4839+
data:
4840+
game.properties: enemies=potato
4841+
kind: ConfigMap
4842+
metadata:
4843+
name: my-configmap
4844+
- complianceType: musthave
4845+
objectDefinition:
4846+
apiVersion: v1
4847+
data:
4848+
game.properties: enemies=potato
4849+
kind: ConfigMap
4850+
metadata:
4851+
name: my-configmap
4852+
remediationAction: ""
4853+
severity: ""
4854+
`
4855+
4856+
expected = strings.TrimPrefix(expected, "\n")
4857+
assertEqual(t, output, expected)
4858+
}
4859+
46534860
// Test Patching a CR object, "MyCr", containing a list of profile objects.
46544861
// Patching profile interface name and (not profile) recommend
46554862
// - metadata:

0 commit comments

Comments
 (0)