Skip to content

Commit 1e86265

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 5c83c5b commit 1e86265

File tree

6 files changed

+154
-29
lines changed

6 files changed

+154
-29
lines changed

controllers/configurationpolicy_controller.go

Lines changed: 73 additions & 17 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
scopedGVR, err := r.getMapping(objGVK, plc, index)
@@ -1365,7 +1372,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13651372
message: err.Error(),
13661373
}
13671374

1368-
return nil, nil, errEvent, err
1375+
return nil, nil, nil, errEvent, err
13691376
}
13701377

13711378
// Set up relevant object namespace-name-objects map to populate with the
@@ -1422,7 +1429,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
14221429
message: msg,
14231430
}
14241431

1425-
return nil, &scopedGVR, errEvent, err
1432+
return nil, &scopedGVR, nil, errEvent, err
14261433
}
14271434

14281435
// Fetch object when:
@@ -1487,7 +1494,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
14871494

14881495
errEvent := &objectTmplEvalEvent{false, "K8s missing namespace", msg}
14891496

1490-
return nil, &scopedGVR, errEvent, nil
1497+
return nil, &scopedGVR, nil, errEvent, nil
14911498
}
14921499

14931500
// Fetch related objects from the cluster
@@ -1511,7 +1518,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
15111518
message: msg,
15121519
}
15131520

1514-
return nil, &scopedGVR, errEvent, err
1521+
return nil, &scopedGVR, nil, errEvent, err
15151522
}
15161523
} else {
15171524
existingObj, _ = getObject(desiredNs, desiredName, scopedGVR, r.TargetK8sDynamicClient)
@@ -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

15461553
listOpts := metav1.ListOptions{
@@ -1583,7 +1590,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
15831590
message: msg,
15841591
}
15851592

1586-
return nil, &scopedGVR, errEvent, err
1593+
return nil, &scopedGVR, nil, errEvent, err
15871594
}
15881595

15891596
// Populate objects from objectSelector results
@@ -1731,7 +1738,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17311738
message: complianceMsg,
17321739
}
17331740

1734-
return nil, &scopedGVR, errEvent, err
1741+
return nil, &scopedGVR, nil, errEvent, err
17351742
}
17361743

17371744
if objectT.RecordDiff == "" && resolvedTemplate.HasSensitiveData {
@@ -1771,7 +1778,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17711778
index, plc.Name, err),
17721779
}
17731780

1774-
return nil, &scopedGVR, errEvent, err
1781+
return nil, &scopedGVR, nil, errEvent, err
17751782
}
17761783

17771784
// Populate the namespace and name if applicable
@@ -1798,7 +1805,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
17981805
"from the namespace selector after template resolution",
17991806
}
18001807

1801-
return nil, &scopedGVR, errEvent, nil
1808+
return nil, &scopedGVR, nil, errEvent, nil
18021809
}
18031810

18041811
// Error if the namespace is templated and returns empty.
@@ -1817,7 +1824,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18171824
),
18181825
}
18191826

1820-
return nil, &scopedGVR, errEvent, nil
1827+
return nil, &scopedGVR, nil, errEvent, nil
18211828
}
18221829

18231830
// Error if the name doesn't match the parsed name from the objectSelector
@@ -1829,7 +1836,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18291836
"from the object selector after template resolution",
18301837
}
18311838

1832-
return nil, &scopedGVR, errEvent, nil
1839+
return nil, &scopedGVR, nil, errEvent, nil
18331840
}
18341841

18351842
desiredObjects = append(desiredObjects, desiredObj)
@@ -1857,7 +1864,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18571864
message: fmt.Sprintf(msg, objGVK.Kind),
18581865
}
18591866

1860-
return nil, &scopedGVR, event, nil
1867+
return nil, &scopedGVR, nil, event, nil
18611868
}
18621869

18631870
// For mustnothave with no name and with an object selector, filter the "desired" objects
@@ -1880,10 +1887,59 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18801887
}
18811888
}
18821889

1883-
return targetedObjects, &scopedGVR, nil, nil
1890+
// If no objects were matched, return a compliant event since this is
1891+
// mustnothave and no objects were found.
1892+
if len(targetedObjects) == 0 {
1893+
var namespaces []string
1894+
var relatedObjects []policyv1.RelatedObject
1895+
1896+
// Populate the related objects list with the objects
1897+
// that were matched by the object selector.
1898+
for ns := range relevantNsNames {
1899+
namespaces = append(namespaces, ns)
1900+
1901+
for name := range relevantNsNames[ns] {
1902+
related := addRelatedObjects(
1903+
true,
1904+
scopedGVR,
1905+
objGVK.Kind,
1906+
ns,
1907+
[]string{name},
1908+
"",
1909+
nil,
1910+
)
1911+
1912+
for _, object := range related {
1913+
relatedObjects = addOrUpdateRelatedObject(relatedObjects, object)
1914+
}
1915+
}
1916+
}
1917+
1918+
sort.Strings(namespaces)
1919+
1920+
nsMsg := "namespace"
1921+
if len(namespaces) > 1 {
1922+
nsMsg = "namespaces:"
1923+
}
1924+
1925+
event := &objectTmplEvalEvent{
1926+
compliant: true,
1927+
reason: "",
1928+
message: fmt.Sprintf(
1929+
"%s missing as expected in %s %s",
1930+
scopedGVR.Resource,
1931+
nsMsg,
1932+
strings.Join(namespaces, ", "),
1933+
),
1934+
}
1935+
1936+
return nil, &scopedGVR, relatedObjects, event, nil
1937+
}
1938+
1939+
return targetedObjects, &scopedGVR, nil, nil, nil
18841940
}
18851941

1886-
return desiredObjects, &scopedGVR, nil, nil
1942+
return desiredObjects, &scopedGVR, nil, nil, nil
18871943
}
18881944

18891945
// 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)