Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tekton/config-policy-controller-acm-215-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
build.appstudio.redhat.com/target_branch: '{{target_branch}}'
pipelinesascode.tekton.dev/cancel-in-progress: "true"
pipelinesascode.tekton.dev/max-keep-runs: "3"
pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch == "main"
pipelinesascode.tekton.dev/on-cel-expression: event == "pull_request" && target_branch == "release-2.15"
creationTimestamp:
labels:
appstudio.openshift.io/application: release-acm-215
Expand Down
88 changes: 35 additions & 53 deletions controllers/configurationpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -1357,8 +1357,37 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
return nil, nil, errEvent, nil
}

skippedObjMsg := "All objects of kind %s were skipped by the `skipObject` template function"

scopedGVR, err := r.getMapping(objGVK, plc, index)
if err != nil {
// Parse templates in a generic way to check whether skipObject was called
if tmplResolver != nil && templates.HasTemplate(objectT.ObjectDefinition.Raw, "", true) {
// Provide a full but empty context to prevent template errors
templateContext := struct {
Object map[string]any
ObjectNamespace string
ObjectName string
}{Object: map[string]any{}, ObjectNamespace: "", ObjectName: ""}

_, skipObject, _ := resolveGoTemplates(
objectT.ObjectDefinition.Raw,
templateContext,
tmplResolver,
resolveOptions,
)

if skipObject {
event := &objectTmplEvalEvent{
compliant: true,
reason: "",
message: fmt.Sprintf(skippedObjMsg, objGVK.Kind),
}

return []*unstructured.Unstructured{}, nil, event, nil
}
}

errEvent := &objectTmplEvalEvent{
compliant: false,
reason: "K8s error",
Expand Down Expand Up @@ -1650,63 +1679,16 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(
if tmplResolver != nil && hasTemplate && desiredObj == nil { //nolint:gocritic
r.processedPolicyCache.Delete(plc.GetUID())

var templateContext any

// Remove managedFields because it has a key that's just a dot,
// which is problematic in the template library
unstructured.RemoveNestedField(obj.Object, "metadata", "managedFields")

// Only populate context variables as they are available:
switch {
case name != "" && ns != "":
// - Namespaced object with metadata.name or objectSelector
templateContext = struct {
Object map[string]any
ObjectNamespace string
ObjectName string
}{Object: obj.Object, ObjectNamespace: ns, ObjectName: name}

case name != "":
// - Cluster-scoped object with metadata.name or objectSelector
templateContext = struct {
Object map[string]any
ObjectName string
}{Object: obj.Object, ObjectName: name}

case ns != "":
// - Unnamed namespaced object
templateContext = struct {
ObjectNamespace string
}{ObjectNamespace: ns}
}

skipObject := false

resolveOptions.CustomFunctions = map[string]any{
"skipObject": func(skips ...any) (empty string, err error) {
switch len(skips) {
case 0:
skipObject = true
case 1:
if !skipObject {
if skip, ok := skips[0].(bool); ok {
skipObject = skip
} else {
err = fmt.Errorf(
"expected boolean but received '%v'", skips[0])
}
}
default:
err = fmt.Errorf(
"expected one optional boolean argument but received %d arguments", len(skips))
}

return empty, err
},
}
// Get the template context for resolving Go templates
templateContext := getTemplateContext(obj.Object, name, ns)

resolvedTemplate, err := tmplResolver.ResolveTemplate(
objectT.ObjectDefinition.Raw, templateContext, resolveOptions,
// Resolve the Go templates
resolvedTemplate, skipObject, err := resolveGoTemplates(
objectT.ObjectDefinition.Raw, templateContext, tmplResolver, resolveOptions,
)

if skipObject {
Expand Down Expand Up @@ -1844,7 +1826,7 @@ func (r *ConfigurationPolicyReconciler) determineDesiredObjects(

switch {
case skipObjectCalled:
msg = "All objects of kind %s were skipped by the `skipObject` template function"
msg = skippedObjMsg
case objectSelector != nil:
msg = "No objects of kind %s were matched from the policy objectSelector"
default:
Expand Down
78 changes: 78 additions & 0 deletions controllers/configurationpolicy_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

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

return diff, nil
}

// getTemplateContext constructs a context object for Go template resolution. It
// selectively includes the object, name, and namespace depending on what
// information is available.
func getTemplateContext(
obj map[string]any,
name string,
ns string,
) (templateContext any) {
// Only populate context variables as they are available:
switch {
case name != "" && ns != "":
// - Namespaced object with metadata.name or objectSelector
return struct {
Object map[string]any
ObjectNamespace string
ObjectName string
}{Object: obj, ObjectNamespace: ns, ObjectName: name}

case name != "":
// - Cluster-scoped object with metadata.name or objectSelector
return struct {
Object map[string]any
ObjectName string
}{Object: obj, ObjectName: name}

case ns != "":
// - Unnamed namespaced object
return struct {
ObjectNamespace string
}{ObjectNamespace: ns}
}

return nil
}

// resolveGoTemplates resolves Go templates in the given raw object using the
// provided template context and template resolver. It returns the resolved
// template, a boolean indicating whether the object should be skipped, and an
// error if any occurred.
func resolveGoTemplates(
rawObj []byte,
templateContext any,
tmplResolver *templates.TemplateResolver,
resolveOptions *templates.ResolveOptions,
) (
resolvedTemplate templates.TemplateResult,
skipObject bool,
err error,
) {
resolveOptions.CustomFunctions = map[string]any{
"skipObject": func(skips ...any) (empty string, err error) {
switch len(skips) {
case 0:
skipObject = true
case 1:
if !skipObject {
if skip, ok := skips[0].(bool); ok {
skipObject = skip
} else {
err = fmt.Errorf(
"expected boolean but received '%v'", skips[0])
}
}
default:
err = fmt.Errorf(
"expected one optional boolean argument but received %d arguments", len(skips))
}

return empty, err
},
}

resolvedTemplate, err = tmplResolver.ResolveTemplate(rawObj, templateContext, resolveOptions)

return resolvedTemplate, skipObject, err
}
Loading