Skip to content

Commit

Permalink
Add support for custom compliance messages in ConfigurationPolicy
Browse files Browse the repository at this point in the history
The new `.spec.customMesssage.Compliant` and `spec.customMessage.noncompliant`
fields of ConfigurationPolicy are now supported. Note that when consolidateManifests
is set to true, the custom message configuration should be set at the policy level
and not at the manifest level.

ref: https://issues.redhat.com/browse/ACM-13134
Signed-off-by: Jason Zhang <[email protected]>
  • Loading branch information
zyjjay committed Aug 21, 2024
1 parent 243dd4a commit e81bc6d
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 0 deletions.
4 changes: 4 additions & 0 deletions internal/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,10 @@ func (p *Plugin) assertValidConfig() error {
return fmt.Errorf(errorMsgFmt, "severity")
}

if manifest.CustomMessage != nil {
return fmt.Errorf(errorMsgFmt, "customMessage")
}

if !reflect.DeepEqual(manifest.ExtraDependencies, policy.ExtraDependencies) {
return fmt.Errorf(errorMsgFmt, "extraDependencies")
}
Expand Down
186 changes: 186 additions & 0 deletions internal/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4533,6 +4533,192 @@ func TestCreatePolicyWithCopyPolicyMetadata(t *testing.T) {
}
}

func TestCreatePolicyWithCustomMessage(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
createConfigMap(t, tmpDir, "configmap.yaml")
createConfigMap(t, tmpDir, "configmap2.yaml")

p := Plugin{}
var err error

p.baseDirectory, err = filepath.EvalSymlinks(tmpDir)
if err != nil {
t.Fatal(err.Error())
}

// With consolidateManifest = false
p.PolicyDefaults.Namespace = "my-policies"
policyConf := types.PolicyConfig{
Name: "policy-app-config",
PolicyOptions: types.PolicyOptions{
ConsolidateManifests: false,
},
Manifests: []types.Manifest{
{
Path: path.Join(tmpDir, "configmap.yaml"),
ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{
CustomMessage: &types.CustomMessage{
Compliant: "{{ range 1 }}",
NonCompliant: "{{ range 1 }}",
},
},
},
{
Path: path.Join(tmpDir, "configmap2.yaml"),
ConfigurationPolicyOptions: types.ConfigurationPolicyOptions{
CustomMessage: &types.CustomMessage{
Compliant: "{{ range 2 }}",
NonCompliant: "{{ range 2 }}",
},
},
},
},
}
p.Policies = append(p.Policies, policyConf)
p.applyDefaults(map[string]interface{}{})

err = p.createPolicy(&policyConf)
if err != nil {
t.Fatal(err.Error())
}

output := p.outputBuffer.String()
expected := `
---
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
annotations:
policy.open-cluster-management.io/categories: ""
policy.open-cluster-management.io/controls: ""
policy.open-cluster-management.io/description: ""
policy.open-cluster-management.io/standards: ""
name: policy-app-config
namespace: my-policies
spec:
copyPolicyMetadata: false
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-app-config
spec:
customMessage:
compliant: '{{ range 1 }}'
noncompliant: '{{ range 1 }}'
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
data:
game.properties: enemies=potato
kind: ConfigMap
metadata:
name: my-configmap
remediationAction: inform
severity: low
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-app-config2
spec:
customMessage:
compliant: '{{ range 2 }}'
noncompliant: '{{ range 2 }}'
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
data:
game.properties: enemies=potato
kind: ConfigMap
metadata:
name: my-configmap
remediationAction: inform
severity: low
remediationAction: inform
`

expected = strings.TrimPrefix(expected, "\n")
assertEqual(t, output, expected)
p.outputBuffer.Reset()

// With consolidateManifest = true
policyConf.PolicyOptions.ConsolidateManifests = true
err = p.assertValidConfig()
expectedErr := "the policy policy-app-config has the customMessage " +
"value set on manifest[0] but consolidateManifests is true"
assertEqual(t, err.Error(), expectedErr)

// Ensure customMessage field is unset at the manifest level
// when consolidateManifest = true to successfully generate a policy
policyConf.Manifests[0].ConfigurationPolicyOptions.CustomMessage = nil
policyConf.Manifests[1].ConfigurationPolicyOptions.CustomMessage = nil
policyConf.ConfigurationPolicyOptions.CustomMessage = &types.CustomMessage{
Compliant: "{{ range }}",
NonCompliant: "{{ range }}",
}

err = p.createPolicy(&policyConf)
if err != nil {
t.Fatal(err.Error())
}

output = p.outputBuffer.String()
expected = `
---
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
annotations:
policy.open-cluster-management.io/categories: ""
policy.open-cluster-management.io/controls: ""
policy.open-cluster-management.io/description: ""
policy.open-cluster-management.io/standards: ""
name: policy-app-config
namespace: my-policies
spec:
copyPolicyMetadata: false
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: policy-app-config
spec:
customMessage:
compliant: '{{ range }}'
noncompliant: '{{ range }}'
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
data:
game.properties: enemies=potato
kind: ConfigMap
metadata:
name: my-configmap
- complianceType: musthave
objectDefinition:
apiVersion: v1
data:
game.properties: enemies=potato
kind: ConfigMap
metadata:
name: my-configmap
remediationAction: ""
severity: ""
`

expected = strings.TrimPrefix(expected, "\n")
assertEqual(t, output, expected)
}

// Test Patching a CR object, "MyCr", containing a list of profile objects.
// Patching profile interface name and (not profile) recommend
// - metadata:
Expand Down
6 changes: 6 additions & 0 deletions internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type ConfigurationPolicyOptions struct {
PruneObjectBehavior string `json:"pruneObjectBehavior,omitempty" yaml:"pruneObjectBehavior,omitempty"`
RecordDiff string `json:"recordDiff,omitempty" yaml:"recordDiff,omitempty"`
RecreateOption string `json:"recreateOption,omitempty" yaml:"recreateOption,omitempty"`
CustomMessage *CustomMessage `json:"customMessage,omitempty" yaml:"customMessage,omitempty"`
}

type GatekeeperOptions struct {
Expand Down Expand Up @@ -107,6 +108,11 @@ type EvaluationInterval struct {
NonCompliant string `json:"noncompliant,omitempty" yaml:"noncompliant,omitempty"`
}

type CustomMessage struct {
Compliant string `json:"compliant,omitempty" yaml:"compliant,omitempty"`
NonCompliant string `json:"noncompliant,omitempty" yaml:"noncompliant,omitempty"`
}

// PolicyConfig represents a policy entry in the PolicyGenerator configuration.
type PolicyConfig struct {
PolicyOptions `json:",inline" yaml:",inline"`
Expand Down
11 changes: 11 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,22 @@ func buildPolicyTemplate(
configPolicyOptionsOverrides *types.ConfigurationPolicyOptions,
configPolicyName string,
) map[string]interface{} {
var customMessage *types.CustomMessage
if policyConf.ConsolidateManifests {
customMessage = policyConf.CustomMessage
} else {
customMessage = configPolicyOptionsOverrides.CustomMessage
}

policySpec := map[string]interface{}{
"remediationAction": policyConf.RemediationAction,
"severity": policyConf.Severity,
}

if customMessage != nil {
policySpec["customMessage"] = customMessage
}

switch objectTemplates.(type) {
case string:
policySpec["object-templates-raw"] = objectTemplates
Expand Down

0 comments on commit e81bc6d

Please sign in to comment.