Skip to content

Commit 34be92a

Browse files
dhaiducekopenshift-merge-bot[bot]
authored andcommitted
feat: allow skipObject to override mapping errors
If a CRD does not exist on a cluster, previously that would take precedence over any Go template processing. This update runs the Go template resolver in a generic way to check whether `skipObject` had been called. ref: https://issues.redhat.com/browse/ACM-23563 Assisted-by: Cursor IDE using claude-4.5-sonnet Signed-off-by: Dale Haiducek <[email protected]>
1 parent 5c83c5b commit 34be92a

File tree

5 files changed

+427
-53
lines changed

5 files changed

+427
-53
lines changed

controllers/configurationpolicy_controller.go

Lines changed: 35 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,8 +1357,37 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
13571357
return nil, nil, errEvent, nil
13581358
}
13591359

1360+
skippedObjMsg := "All objects of kind %s were skipped by the `skipObject` template function"
1361+
13601362
scopedGVR, err := r.getMapping(objGVK, plc, index)
13611363
if err != nil {
1364+
// Parse templates in a generic way to check whether skipObject was called
1365+
if tmplResolver != nil && templates.HasTemplate(objectT.ObjectDefinition.Raw, "", true) {
1366+
// Provide a full but empty context to prevent template errors
1367+
templateContext := struct {
1368+
Object map[string]any
1369+
ObjectNamespace string
1370+
ObjectName string
1371+
}{Object: map[string]any{}, ObjectNamespace: "", ObjectName: ""}
1372+
1373+
_, skipObject, _ := resolveGoTemplates(
1374+
objectT.ObjectDefinition.Raw,
1375+
templateContext,
1376+
tmplResolver,
1377+
resolveOptions,
1378+
)
1379+
1380+
if skipObject {
1381+
event := &objectTmplEvalEvent{
1382+
compliant: true,
1383+
reason: "",
1384+
message: fmt.Sprintf(skippedObjMsg, objGVK.Kind),
1385+
}
1386+
1387+
return []*unstructured.Unstructured{}, nil, event, nil
1388+
}
1389+
}
1390+
13621391
errEvent := &objectTmplEvalEvent{
13631392
compliant: false,
13641393
reason: "K8s error",
@@ -1650,63 +1679,16 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
16501679
if tmplResolver != nil && hasTemplate && desiredObj == nil { //nolint:gocritic
16511680
r.processedPolicyCache.Delete(plc.GetUID())
16521681

1653-
var templateContext any
1654-
16551682
// Remove managedFields because it has a key that's just a dot,
16561683
// which is problematic in the template library
16571684
unstructured.RemoveNestedField(obj.Object, "metadata", "managedFields")
16581685

1659-
// Only populate context variables as they are available:
1660-
switch {
1661-
case name != "" && ns != "":
1662-
// - Namespaced object with metadata.name or objectSelector
1663-
templateContext = struct {
1664-
Object map[string]any
1665-
ObjectNamespace string
1666-
ObjectName string
1667-
}{Object: obj.Object, ObjectNamespace: ns, ObjectName: name}
1668-
1669-
case name != "":
1670-
// - Cluster-scoped object with metadata.name or objectSelector
1671-
templateContext = struct {
1672-
Object map[string]any
1673-
ObjectName string
1674-
}{Object: obj.Object, ObjectName: name}
1675-
1676-
case ns != "":
1677-
// - Unnamed namespaced object
1678-
templateContext = struct {
1679-
ObjectNamespace string
1680-
}{ObjectNamespace: ns}
1681-
}
1682-
1683-
skipObject := false
1684-
1685-
resolveOptions.CustomFunctions = map[string]any{
1686-
"skipObject": func(skips ...any) (empty string, err error) {
1687-
switch len(skips) {
1688-
case 0:
1689-
skipObject = true
1690-
case 1:
1691-
if !skipObject {
1692-
if skip, ok := skips[0].(bool); ok {
1693-
skipObject = skip
1694-
} else {
1695-
err = fmt.Errorf(
1696-
"expected boolean but received '%v'", skips[0])
1697-
}
1698-
}
1699-
default:
1700-
err = fmt.Errorf(
1701-
"expected one optional boolean argument but received %d arguments", len(skips))
1702-
}
1703-
1704-
return empty, err
1705-
},
1706-
}
1686+
// Get the template context for resolving Go templates
1687+
templateContext := getTemplateContext(obj.Object, name, ns)
17071688

1708-
resolvedTemplate, err := tmplResolver.ResolveTemplate(
1709-
objectT.ObjectDefinition.Raw, templateContext, resolveOptions,
1689+
// Resolve the Go templates
1690+
resolvedTemplate, skipObject, err := resolveGoTemplates(
1691+
objectT.ObjectDefinition.Raw, templateContext, tmplResolver, resolveOptions,
17101692
)
17111693

17121694
if skipObject {
@@ -1844,7 +1826,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
18441826

18451827
switch {
18461828
case skipObjectCalled:
1847-
msg = "All objects of kind %s were skipped by the `skipObject` template function"
1829+
msg = skippedObjMsg
18481830
case objectSelector != nil:
18491831
msg = "No objects of kind %s were matched from the policy objectSelector"
18501832
default:

controllers/configurationpolicy_utils.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
gocmp "github.com/google/go-cmp/cmp"
1515
"github.com/pmezard/go-difflib/difflib"
16+
templates "github.com/stolostron/go-template-utils/v7/pkg/templates"
1617
depclient "github.com/stolostron/kubernetes-dependency-watches/client"
1718
apiRes "k8s.io/apimachinery/pkg/api/resource"
1819
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -847,3 +848,80 @@ func generateDiff(existingObj, updatedObj *unstructured.Unstructured, fullDiffs
847848

848849
return diff, nil
849850
}
851+
852+
// getTemplateContext constructs a context object for Go template resolution. It
853+
// selectively includes the object, name, and namespace depending on what
854+
// information is available.
855+
func getTemplateContext(
856+
obj map[string]any,
857+
name string,
858+
ns string,
859+
) (templateContext any) {
860+
// Only populate context variables as they are available:
861+
switch {
862+
case name != "" && ns != "":
863+
// - Namespaced object with metadata.name or objectSelector
864+
return struct {
865+
Object map[string]any
866+
ObjectNamespace string
867+
ObjectName string
868+
}{Object: obj, ObjectNamespace: ns, ObjectName: name}
869+
870+
case name != "":
871+
// - Cluster-scoped object with metadata.name or objectSelector
872+
return struct {
873+
Object map[string]any
874+
ObjectName string
875+
}{Object: obj, ObjectName: name}
876+
877+
case ns != "":
878+
// - Unnamed namespaced object
879+
return struct {
880+
ObjectNamespace string
881+
}{ObjectNamespace: ns}
882+
}
883+
884+
return nil
885+
}
886+
887+
// resolveGoTemplates resolves Go templates in the given raw object using the
888+
// provided template context and template resolver. It returns the resolved
889+
// template, a boolean indicating whether the object should be skipped, and an
890+
// error if any occurred.
891+
func resolveGoTemplates(
892+
rawObj []byte,
893+
templateContext any,
894+
tmplResolver *templates.TemplateResolver,
895+
resolveOptions *templates.ResolveOptions,
896+
) (
897+
resolvedTemplate templates.TemplateResult,
898+
skipObject bool,
899+
err error,
900+
) {
901+
resolveOptions.CustomFunctions = map[string]any{
902+
"skipObject": func(skips ...any) (empty string, err error) {
903+
switch len(skips) {
904+
case 0:
905+
skipObject = true
906+
case 1:
907+
if !skipObject {
908+
if skip, ok := skips[0].(bool); ok {
909+
skipObject = skip
910+
} else {
911+
err = fmt.Errorf(
912+
"expected boolean but received '%v'", skips[0])
913+
}
914+
}
915+
default:
916+
err = fmt.Errorf(
917+
"expected one optional boolean argument but received %d arguments", len(skips))
918+
}
919+
920+
return empty, err
921+
},
922+
}
923+
924+
resolvedTemplate, err = tmplResolver.ResolveTemplate(rawObj, templateContext, resolveOptions)
925+
926+
return resolvedTemplate, skipObject, err
927+
}

0 commit comments

Comments
 (0)