Skip to content

Commit fa6c249

Browse files
committed
fix: compliant with mustnothave and objectselector
When a mustnothave ConfigurationPolicy returns objects from the objectSelector but no objects match the policy, the status wasn't populated. This populates a compliant status for this case. ref: https://issues.redhat.com/browse/ACM-25562 Signed-off-by: Dale Haiducek <[email protected]>
1 parent 34be92a commit fa6c249

File tree

6 files changed

+155
-30
lines changed

6 files changed

+155
-30
lines changed

controllers/configurationpolicy_controller.go

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,9 +1037,15 @@ func (r *ConfigurationPolicyReconciler) handleObjectTemplates(plc *policyv1.Conf
10371037
resolverToUse = tmplResolver
10381038
}
10391039

1040-
desiredObjects, scopedGVR, errEvent, err := r.determineDesiredObjects(
1040+
desiredObjects, scopedGVR, determinedRelatedObjects, errEvent, err := r.determineDesiredObjects(
10411041
plc, index, objectT, resolverToUse, resolveOptions,
10421042
)
1043+
1044+
// Merge the related objects returned from determineDesiredObjects into the outer relatedObjects
1045+
for _, object := range determinedRelatedObjects {
1046+
relatedObjects = addOrUpdateRelatedObject(relatedObjects, object)
1047+
}
1048+
10431049
if err != nil {
10441050
// Return all mapping and templating errors encountered and let the caller decide if the errors should be
10451051
// retried
@@ -1319,6 +1325,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13191325
) (
13201326
[]*unstructured.Unstructured,
13211327
*depclient.ScopedGVR,
1328+
[]policyv1.RelatedObject,
13221329
*objectTmplEvalEvent,
13231330
error,
13241331
) {
@@ -1339,7 +1346,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13391346
message: "Error parsing the namespace from the object definition",
13401347
}
13411348

1342-
return nil, nil, errEvent, err
1349+
return nil, nil, nil, errEvent, err
13431350
}
13441351

13451352
objGVK := parsedMinMetadata.GroupVersionKind()
@@ -1354,7 +1361,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13541361
),
13551362
}
13561363

1357-
return nil, nil, errEvent, nil
1364+
return nil, nil, nil, errEvent, nil
13581365
}
13591366

13601367
skippedObjMsg := "All objects of kind %s were skipped by the `skipObject` template function"
@@ -1384,7 +1391,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13841391
message: fmt.Sprintf(skippedObjMsg, objGVK.Kind),
13851392
}
13861393

1387-
return []*unstructured.Unstructured{}, nil, event, nil
1394+
return []*unstructured.Unstructured{}, nil, nil, event, nil
13881395
}
13891396
}
13901397

@@ -1394,7 +1401,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13941401
message: err.Error(),
13951402
}
13961403

1397-
return nil, nil, errEvent, err
1404+
return nil, nil, nil, errEvent, err
13981405
}
13991406

14001407
// Set up relevant object namespace-name-objects map to populate with the
@@ -1451,7 +1458,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
14511458
message: msg,
14521459
}
14531460

1454-
return nil, &scopedGVR, errEvent, err
1461+
return nil, &scopedGVR, nil, errEvent, err
14551462
}
14561463

14571464
// Fetch object when:
@@ -1516,7 +1523,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
15161523

15171524
errEvent := &objectTmplEvalEvent{false, "K8s missing namespace", msg}
15181525

1519-
return nil, &scopedGVR, errEvent, nil
1526+
return nil, &scopedGVR, nil, errEvent, nil
15201527
}
15211528

15221529
// Fetch related objects from the cluster
@@ -1540,7 +1547,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
15401547
message: msg,
15411548
}
15421549

1543-
return nil, &scopedGVR, errEvent, err
1550+
return nil, &scopedGVR, nil, errEvent, err
15441551
}
15451552
} else {
15461553
existingObj, _ = getObject(desiredNs, desiredName, scopedGVR, r.TargetK8sDynamicClient)
@@ -1569,7 +1576,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
15691576
message: msg,
15701577
}
15711578

1572-
return nil, &scopedGVR, errEvent, err
1579+
return nil, &scopedGVR, nil, errEvent, err
15731580
}
15741581

15751582
listOpts := metav1.ListOptions{
@@ -1612,7 +1619,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
16121619
message: msg,
16131620
}
16141621

1615-
return nil, &scopedGVR, errEvent, err
1622+
return nil, &scopedGVR, nil, errEvent, err
16161623
}
16171624

16181625
// Populate objects from objectSelector results
@@ -1713,7 +1720,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17131720
message: complianceMsg,
17141721
}
17151722

1716-
return nil, &scopedGVR, errEvent, err
1723+
return nil, &scopedGVR, nil, errEvent, err
17171724
}
17181725

17191726
if objectT.RecordDiff == "" && resolvedTemplate.HasSensitiveData {
@@ -1753,7 +1760,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17531760
index, plc.Name, err),
17541761
}
17551762

1756-
return nil, &scopedGVR, errEvent, err
1763+
return nil, &scopedGVR, nil, errEvent, err
17571764
}
17581765

17591766
// Populate the namespace and name if applicable
@@ -1780,7 +1787,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17801787
"from the namespace selector after template resolution",
17811788
}
17821789

1783-
return nil, &scopedGVR, errEvent, nil
1790+
return nil, &scopedGVR, nil, errEvent, nil
17841791
}
17851792

17861793
// Error if the namespace is templated and returns empty.
@@ -1799,7 +1806,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17991806
),
18001807
}
18011808

1802-
return nil, &scopedGVR, errEvent, nil
1809+
return nil, &scopedGVR, nil, errEvent, nil
18031810
}
18041811

18051812
// Error if the name doesn't match the parsed name from the objectSelector
@@ -1811,7 +1818,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18111818
"from the object selector after template resolution",
18121819
}
18131820

1814-
return nil, &scopedGVR, errEvent, nil
1821+
return nil, &scopedGVR, nil, errEvent, nil
18151822
}
18161823

18171824
desiredObjects = append(desiredObjects, desiredObj)
@@ -1839,7 +1846,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18391846
message: fmt.Sprintf(msg, objGVK.Kind),
18401847
}
18411848

1842-
return nil, &scopedGVR, event, nil
1849+
return nil, &scopedGVR, nil, event, nil
18431850
}
18441851

18451852
// For mustnothave with no name and with an object selector, filter the "desired" objects
@@ -1862,10 +1869,59 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18621869
}
18631870
}
18641871

1865-
return targetedObjects, &scopedGVR, nil, nil
1872+
// If no objects were matched, return a compliant event since this is
1873+
// mustnothave and no objects were found.
1874+
if len(targetedObjects) == 0 {
1875+
var namespaces []string
1876+
var relatedObjects []policyv1.RelatedObject
1877+
1878+
// Populate the related objects list with the objects
1879+
// that were matched by the object selector.
1880+
for ns := range relevantNsNames {
1881+
namespaces = append(namespaces, ns)
1882+
1883+
for name := range relevantNsNames[ns] {
1884+
related := addRelatedObjects(
1885+
true,
1886+
scopedGVR,
1887+
objGVK.Kind,
1888+
ns,
1889+
[]string{name},
1890+
"",
1891+
nil,
1892+
)
1893+
1894+
for _, object := range related {
1895+
relatedObjects = addOrUpdateRelatedObject(relatedObjects, object)
1896+
}
1897+
}
1898+
}
1899+
1900+
sort.Strings(namespaces)
1901+
1902+
nsMsg := "namespace"
1903+
if len(namespaces) > 1 {
1904+
nsMsg = "namespaces:"
1905+
}
1906+
1907+
event := &objectTmplEvalEvent{
1908+
compliant: true,
1909+
reason: "",
1910+
message: fmt.Sprintf(
1911+
"%s missing as expected in %s %s",
1912+
scopedGVR.Resource,
1913+
nsMsg,
1914+
strings.Join(namespaces, ", "),
1915+
),
1916+
}
1917+
1918+
return nil, &scopedGVR, relatedObjects, event, nil
1919+
}
1920+
1921+
return targetedObjects, &scopedGVR, nil, nil, nil
18661922
}
18671923

1868-
return desiredObjects, &scopedGVR, nil, nil
1924+
return desiredObjects, &scopedGVR, nil, nil, nil
18691925
}
18701926

18711927
// batchedEvents combines compliance events into batches that should be emitted in order. For example,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
relatedObjects:
2+
- compliant: Compliant
3+
object:
4+
apiVersion: v1
5+
kind: ConfigMap
6+
metadata:
7+
name: good-configmap
8+
namespace: default
9+
compliancyDetails:
10+
- Compliant: Compliant
11+
conditions:
12+
- message: configmaps missing as expected in namespace default
13+
status: "True"
14+
type: notification
15+
compliant: Compliant
16+
history:
17+
- message: Compliant; notification - configmaps missing as expected in namespace default
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
namespace: default
5+
name: good-configmap
6+
labels:
7+
isLower: 'true'
8+
privileged: 'no'
9+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Status compare:
2+
.compliancyDetails[0] matches
3+
.compliancyDetails matches
4+
.compliant: 'Compliant' does match 'Compliant'
5+
.history[0] matches
6+
.history matches
7+
.relatedObjects[0] matches
8+
.relatedObjects matches
9+
Expected status matches the actual status
10+
11+
# Diffs:
12+
v1 ConfigMap default/good-configmap:
13+
14+
# Compliance messages:
15+
Compliant; notification - configmaps missing as expected in namespace default
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: policy.open-cluster-management.io/v1
2+
kind: ConfigurationPolicy
3+
metadata:
4+
name: policy-mustnothave-objselector
5+
spec:
6+
object-templates:
7+
- complianceType: mustnothave
8+
objectDefinition:
9+
apiVersion: v1
10+
kind: ConfigMap
11+
metadata:
12+
labels:
13+
isLower: 'true'
14+
privileged: 'yes'
15+
namespace: default
16+
objectSelector:
17+
matchLabels:
18+
isLower: 'true'
19+
recordDiff: InStatus
20+
recreateOption: None
21+
pruneObjectBehavior: DeleteAll
22+
remediationAction: inform
23+
severity: low

test/dryrun/no_name/with_object_selector/no_name_obj_sel_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ import (
77
"open-cluster-management.io/config-policy-controller/test/dryrun"
88
)
99

10-
//go:embed musthave_mixed_noncompliant/*
11-
var musthaveMixedNoncompliant embed.FS
10+
var (
11+
//go:embed musthave_mixed_noncompliant/*
12+
musthaveMixedNoncompliant embed.FS
13+
//go:embed mustnothave_mixed_noncompliant/*
14+
mustnothaveMixedNoncompliant embed.FS
15+
//go:embed mustnothave_unmatched/*
16+
mustnothaveUnmatched embed.FS
1217

13-
func TestMusthaveMixedNonCompliant(t *testing.T) {
14-
t.Run("Test only selected and incorrect objects are marked as violations",
15-
dryrun.Run(musthaveMixedNoncompliant))
16-
}
17-
18-
//go:embed mustnothave_mixed_noncompliant/*
19-
var mustnothaveMixedNoncompliant embed.FS
18+
testCases = map[string]embed.FS{
19+
"Test only selected and incorrect objects are marked as violations": musthaveMixedNoncompliant,
20+
"Test only selected and matched objects are marked as violations": mustnothaveMixedNoncompliant,
21+
"Test no matched objects for mustnothave is compliant": mustnothaveUnmatched,
22+
}
23+
)
2024

21-
func TestMustnothaveMixedNonCompliant(t *testing.T) {
22-
t.Run("Test only selected and matched objects are marked as violations",
23-
dryrun.Run(mustnothaveMixedNoncompliant))
25+
func TestNoNameObjSelector(t *testing.T) {
26+
for name, testFiles := range testCases {
27+
t.Run(name, dryrun.Run(testFiles))
28+
}
2429
}

0 commit comments

Comments
 (0)