Skip to content

Commit d780b0e

Browse files
Report Pending status despite hub template error
A previous change allowing hub-templates for all policy types, and handling any hub-template errors in this controller, caused a change in behavior. Before that change, when a policy both had a hub-template error *and* an unsatisfied dependency, the policy would be marked as Pending. The change made the hub-template error have priority. This commit gives the Pending status priority, and adds a test for that specific behavior. It also handles ConfigurationPolicies particularly carefully: setting `PruneObjectBehavior` to `None` when the template will be deleted specifically because of a hub-template error. Refs: - https://issues.redhat.com/browse/ACM-11530 Signed-off-by: Justin Kulikauskas <[email protected]>
1 parent 6125983 commit d780b0e

File tree

6 files changed

+256
-80
lines changed

6 files changed

+256
-80
lines changed

controllers/templatesync/template_sync.go

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import (
5050

5151
const (
5252
ControllerName string = "policy-template-sync"
53+
54+
hubTmplErrorKey = "policy.open-cluster-management.io/hub-templates-error"
5355
)
5456

5557
var log = ctrl.Log.WithName(ControllerName)
@@ -458,17 +460,6 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
458460
continue
459461
}
460462

461-
// check for hub template error
462-
if errAnno := metaObj.GetAnnotations()["policy.open-cluster-management.io/hub-templates-error"]; errAnno != "" {
463-
_ = r.emitTemplateError(ctx, instance, tIndex, tName, isClusterScoped, errAnno)
464-
465-
tLogger.Error(k8serrors.NewBadRequest(errAnno), "Failed to process the policy template")
466-
467-
policyUserErrorsCounter.WithLabelValues(instance.Name, tName, "format-error").Inc()
468-
469-
continue
470-
}
471-
472463
dependencyFailures := r.processDependencies(ctx, dClient, discoveryClient, templateDeps, tLogger)
473464

474465
// Instantiate a dynamic client -- if it's a clusterwide resource, then leave off the namespace
@@ -522,26 +513,37 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
522513
// Attempt to fetch the resource
523514
eObject, err := res.Get(ctx, tName, metav1.GetOptions{})
524515
if err != nil {
525-
if len(dependencyFailures) > 0 {
526-
// template must be pending, do not create it
527-
emitErr := r.emitTemplatePending(ctx, instance, tIndex, tName, isClusterScoped,
528-
generatePendingMsg(dependencyFailures))
529-
if emitErr != nil {
530-
resultError = emitErr
516+
// not found should consider creating it
517+
if k8serrors.IsNotFound(err) {
518+
if len(dependencyFailures) > 0 {
519+
// template must be pending, do not create it
520+
emitErr := r.emitTemplatePending(ctx, instance, tIndex, tName, isClusterScoped,
521+
generatePendingMsg(dependencyFailures))
522+
if emitErr != nil {
523+
resultError = emitErr
524+
525+
continue
526+
}
527+
528+
tLogger.Info("Dependencies were not satisfied for the policy template",
529+
"namespace", instance.GetNamespace(),
530+
"kind", gvk.Kind,
531+
)
531532

532533
continue
533534
}
534535

535-
tLogger.Info("Dependencies were not satisfied for the policy template",
536-
"namespace", instance.GetNamespace(),
537-
"kind", gvk.Kind,
538-
)
536+
// check for hub template error before creating
537+
if errAnno := metaObj.GetAnnotations()[hubTmplErrorKey]; errAnno != "" {
538+
_ = r.emitTemplateError(ctx, instance, tIndex, tName, isClusterScoped, errAnno)
539539

540-
continue
541-
}
540+
tLogger.Error(k8serrors.NewBadRequest(errAnno), "Failed to process the policy template")
541+
542+
policyUserErrorsCounter.WithLabelValues(instance.Name, tName, "format-error").Inc()
543+
544+
continue
545+
}
542546

543-
// not found should create it
544-
if k8serrors.IsNotFound(err) {
545547
// Handle setting the owner reference (this is skipped for clusterwide objects since our
546548
// namespaced policy can't own a clusterwide object)
547549
if !isClusterScoped {
@@ -676,6 +678,45 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, request reconcile.Requ
676678
continue
677679
}
678680

681+
// check for hub template error
682+
if errAnno := metaObj.GetAnnotations()[hubTmplErrorKey]; errAnno != "" {
683+
_ = r.emitTemplateError(ctx, instance, tIndex, tName, isClusterScoped, errAnno)
684+
685+
tLogger.Error(k8serrors.NewBadRequest(errAnno), "Failed to process the policy template")
686+
687+
policyUserErrorsCounter.WithLabelValues(instance.Name, tName, "format-error").Inc()
688+
689+
if rsrc.Resource == "configurationpolicies" {
690+
// Patch it so that it doesn't clean up resources in the case of a formatting error
691+
jsonPatch := []byte(`[{"op":"replace","path":"/spec/pruneObjectBehavior","value":"None"}]`)
692+
693+
_, err := res.Patch(ctx, tName, types.JSONPatchType, jsonPatch, metav1.PatchOptions{})
694+
if err != nil {
695+
tLogger.Error(err, "Failed to patch a ConfigurationPolicy that entered hub-template-error state",
696+
"namespace", instance.GetNamespace(),
697+
"name", tName,
698+
)
699+
700+
resultError = err
701+
702+
continue
703+
}
704+
}
705+
706+
err = res.Delete(ctx, tName, metav1.DeleteOptions{})
707+
if err != nil {
708+
tLogger.Error(err, "Failed to delete a template that entered hub-template-error state",
709+
"namespace", instance.GetNamespace(),
710+
"name", tName,
711+
)
712+
policySystemErrorsCounter.WithLabelValues(instance.Name, tName, "delete-error").Inc()
713+
714+
resultError = err
715+
}
716+
717+
continue
718+
}
719+
679720
// Handle owner references: Owned objects should be labeled with the parent policy name, and
680721
// namespaced objects should have the policy for an owner reference (cluster scoped object will
681722
// not have this owner reference because a namespaced object can't own a cluster scoped object).

test/e2e/case10_error_test.go

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
. "github.com/onsi/ginkgo/v2"
1111
. "github.com/onsi/gomega"
12+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1415
"k8s.io/apimachinery/pkg/runtime"
@@ -255,32 +256,111 @@ var _ = Describe("Test error handling", func() {
255256
return found
256257
}, defaultTimeoutSeconds*2, 1).Should(BeFalse())
257258
})
258-
It("should throw a noncompliance event for hub template errors", func() {
259-
By("Deploying a test policy CRD")
260-
_, err := kubectlManaged("apply", "-f", yamlBasePath+"mock-crd.yaml")
261-
Expect(err).ToNot(HaveOccurred())
262-
DeferCleanup(func() error {
263-
_, err := kubectlManaged("delete", "-f", yamlBasePath+"mock-crd.yaml")
264-
265-
return err
259+
Describe("testing hub template errors cases", Ordered, func() {
260+
AfterEach(func(ctx SpecContext) {
261+
cfgInt := clientManagedDynamic.Resource(gvrConfigurationPolicy).Namespace(clusterNamespace)
262+
Eventually(func() error {
263+
_, err := cfgInt.Patch(ctx, "case10-bad-hubtemplate-notyet", types.JSONPatchType,
264+
[]byte(`[{"op":"replace","path":"/metadata/finalizers","value":[]}]`),
265+
metav1.PatchOptions{})
266+
if k8serrors.IsNotFound(err) {
267+
return nil
268+
}
269+
270+
return err
271+
}, defaultTimeoutSeconds, 1).ShouldNot(HaveOccurred())
266272
})
267273

268-
hubApplyPolicy("case10-bad-hubtemplate",
269-
yamlBasePath+"error-hubtemplate.yaml")
270-
271-
By("Checking for the error event")
272-
Eventually(
273-
checkForEvent("case10-bad-hubtemplate", "must be aboveground"),
274-
defaultTimeoutSeconds,
275-
1,
276-
).Should(BeTrue())
274+
It("should throw a noncompliance event for hub template errors", func() {
275+
hubApplyPolicy("case10-bad-hubtemplate",
276+
yamlBasePath+"error-hubtemplate.yaml")
277+
278+
By("Checking for the error event")
279+
Eventually(
280+
checkForEvent("case10-bad-hubtemplate", "must be aboveground"),
281+
defaultTimeoutSeconds,
282+
1,
283+
).Should(BeTrue())
284+
285+
By("Checking for the compliance message formatting")
286+
Eventually(
287+
checkForEvent("case10-bad-hubtemplate", nonCompliantPrefix+nonCompliantPrefix),
288+
defaultTimeoutSeconds,
289+
1,
290+
).Should(BeFalse())
291+
})
292+
It("should patch the pruneObjectBehavior to None during a hub-template-error", func(ctx SpecContext) {
293+
hubApplyPolicy("case10-bad-hubtemplate-notyet",
294+
yamlBasePath+"error-hubtemplate-notyet.yaml")
295+
296+
cfgInt := clientManagedDynamic.Resource(gvrConfigurationPolicy).Namespace(clusterNamespace)
297+
298+
By("Checking the pruneObjectBehavior")
299+
Eventually(func() string {
300+
cfgpol, err := cfgInt.Get(ctx, "case10-bad-hubtemplate-notyet", metav1.GetOptions{})
301+
if err != nil {
302+
return ""
303+
}
304+
305+
prune, found, err := unstructured.NestedString(cfgpol.Object, "spec", "pruneObjectBehavior")
306+
if !found || err != nil {
307+
return ""
308+
}
309+
310+
return prune
311+
}, defaultTimeoutSeconds, 1).Should(Equal("DeleteAll"))
312+
313+
By("Applying a finalizer to the configuration policy")
314+
Eventually(func() error {
315+
_, err := cfgInt.Patch(ctx, "case10-bad-hubtemplate-notyet", types.JSONPatchType,
316+
[]byte(`[{"op":"replace","path":"/metadata/finalizers","value":["policy.test/hold"]}]`),
317+
metav1.PatchOptions{})
318+
319+
return err
320+
}, defaultTimeoutSeconds, 1).ShouldNot(HaveOccurred())
321+
322+
polInt := clientHubDynamic.Resource(gvrPolicy).Namespace(clusterNamespaceOnHub)
323+
324+
By("Adding the hub-template-error annotation to the policy")
325+
Eventually(func() error {
326+
annoPatch := []byte(`[{"op":"replace",` +
327+
`"path":"/spec/policy-templates/0/objectDefinition/metadata/annotations",` +
328+
`"value":{"policy.open-cluster-management.io/hub-templates-error": "must be aboveground"}}]`)
329+
_, err := polInt.Patch(ctx, "case10-bad-hubtemplate-notyet", types.JSONPatchType,
330+
annoPatch, metav1.PatchOptions{})
331+
332+
return err
333+
}, defaultTimeoutSeconds, 1).ShouldNot(HaveOccurred())
334+
335+
By("Checking for the error event")
336+
Eventually(
337+
checkForEvent("case10-bad-hubtemplate-notyet", "must be aboveground"),
338+
defaultTimeoutSeconds,
339+
1,
340+
).Should(BeTrue())
341+
342+
By("Checking the pruneObjectBehavior")
343+
Eventually(func() string {
344+
cfgpol, err := cfgInt.Get(ctx, "case10-bad-hubtemplate-notyet", metav1.GetOptions{})
345+
if err != nil {
346+
return ""
347+
}
348+
349+
prune, found, err := unstructured.NestedString(cfgpol.Object, "spec", "pruneObjectBehavior")
350+
if !found || err != nil {
351+
return ""
352+
}
353+
354+
return prune
355+
}, defaultTimeoutSeconds, 1).Should(Equal("None"))
356+
})
357+
It("should mark the policy as pending instead of throwing a hub-template error event", func() {
358+
hubApplyPolicy("case10-bad-hubtemplate-pending",
359+
yamlBasePath+"error-hubtemplate-pending.yaml")
277360

278-
By("Checking for the compliance message formatting")
279-
Eventually(
280-
checkForEvent("case10-bad-hubtemplate", nonCompliantPrefix+nonCompliantPrefix),
281-
defaultTimeoutSeconds,
282-
1,
283-
).Should(BeFalse())
361+
Eventually(checkCompliance("case10-bad-hubtemplate-pending"),
362+
defaultTimeoutSeconds, 1).Should(Equal("Pending"))
363+
})
284364
})
285365
It("should throw a noncompliance event if the template object is invalid", func() {
286366
hubApplyPolicy("case10-invalid-severity",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
apiVersion: policy.open-cluster-management.io/v1
2+
kind: Policy
3+
metadata:
4+
name: case10-bad-hubtemplate-notyet
5+
labels:
6+
policy.open-cluster-management.io/cluster-name: managed
7+
policy.open-cluster-management.io/cluster-namespace: managed
8+
policy.open-cluster-management.io/root-policy: case10-bad-hubtemplate
9+
spec:
10+
remediationAction: inform
11+
disabled: false
12+
policy-templates:
13+
- objectDefinition:
14+
apiVersion: policy.open-cluster-management.io/v1
15+
kind: ConfigurationPolicy
16+
metadata:
17+
name: case10-bad-hubtemplate-notyet
18+
# annotations:
19+
# policy.open-cluster-management.io/hub-templates-error: "must be aboveground"
20+
spec:
21+
pruneObjectBehavior: DeleteAll
22+
object-templates:
23+
- complianceType: musthave
24+
objectDefinition:
25+
apiVersion: v1
26+
kind: Pod
27+
metadata:
28+
name: nginx-pod-e2e
29+
namespace: default
30+
annotations:
31+
policy.test/location: 'I come from {{hub the land down under hub}}'
32+
spec:
33+
containers:
34+
- name: nginx
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
apiVersion: policy.open-cluster-management.io/v1
2+
kind: Policy
3+
metadata:
4+
name: case10-bad-hubtemplate-pending
5+
labels:
6+
policy.open-cluster-management.io/cluster-name: managed
7+
policy.open-cluster-management.io/cluster-namespace: managed
8+
policy.open-cluster-management.io/root-policy: case10-bad-hubtemplate
9+
spec:
10+
remediationAction: inform
11+
disabled: false
12+
dependencies:
13+
- apiVersion: policy.open-cluster-management.io/v1
14+
kind: Policy
15+
name: australia
16+
namespace: default
17+
compliance: Compliant
18+
policy-templates:
19+
- objectDefinition:
20+
apiVersion: policy.open-cluster-management.io/v1
21+
kind: ConfigurationPolicy
22+
metadata:
23+
name: case10-bad-hubtemplate-pending
24+
annotations:
25+
policy.open-cluster-management.io/hub-templates-error: "must be aboveground"
26+
spec:
27+
object-templates:
28+
- complianceType: musthave
29+
objectDefinition:
30+
apiVersion: v1
31+
kind: Pod
32+
metadata:
33+
name: nginx-pod-e2e
34+
namespace: default
35+
annotations:
36+
policy.test/location: 'I come from {{hub the land down under hub}}'
37+
spec:
38+
containers:
39+
- name: nginx

test/resources/case10_template_sync_error_test/error-hubtemplate.yaml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,22 @@ spec:
1212
policy-templates:
1313
- objectDefinition:
1414
apiVersion: policy.open-cluster-management.io/v1
15-
kind: MockPolicy
15+
kind: ConfigurationPolicy
1616
metadata:
1717
name: case10-bad-hubtemplate-policy
1818
annotations:
1919
policy.open-cluster-management.io/hub-templates-error: "must be aboveground"
2020
spec:
21-
foo: 'I come from {{hub the land down under hub}}'
21+
object-templates:
22+
- complianceType: musthave
23+
objectDefinition:
24+
apiVersion: v1
25+
kind: Pod
26+
metadata:
27+
name: nginx-pod-e2e
28+
namespace: default
29+
annotations:
30+
policy.test/location: 'I come from {{hub the land down under hub}}'
31+
spec:
32+
containers:
33+
- name: nginx

test/resources/case10_template_sync_error_test/mock-crd.yaml

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)