Skip to content

fix: for target template parsing for complex matchers#5735

Merged
Skarlso merged 4 commits intoexternal-secrets:mainfrom
Skarlso:fix-the-target-spec
Dec 30, 2025
Merged

fix: for target template parsing for complex matchers#5735
Skarlso merged 4 commits intoexternal-secrets:mainfrom
Skarlso:fix-the-target-spec

Conversation

@Skarlso
Copy link
Copy Markdown
Contributor

@Skarlso Skarlso commented Dec 16, 2025

Problem Statement

What is the problem you're trying to solve?

Related Issue

Fixes #5587

Proposed Changes

How do you like to solve the issue and why?

Format

Please ensure that your PR follows the following format for the title:

feat(scope): add new feature
fix(scope): fix bug
docs(scope): update documentation
chore(scope): update build tool or dependencies
ref(scope): refactor code
clean(scope): provider cleanup
test(scope): add tests
perf(scope): improve performance
desig(scope): improve design

Where scope is optionally one of:

  • charts
  • release
  • testing
  • security
  • templating

Checklist

  • I have read the contribution guidelines
  • All commits are signed with git commit --signoff
  • My changes have reasonable test coverage
  • All tests pass with make test
  • I ensured my PR is ready for review with make reviewable

Summary by CodeRabbit

  • New Features

    • Template application can now accept an existing target resource as an optional base so merges preserve pre-existing fields, labels, annotations and metadata.
  • Bug Fixes

    • Eliminates redundant fetches and improves error handling to prevent accidental identifier overwrites during merge/orphan/owner flows.
  • Tests

    • Added tests validating merge semantics, including preservation of existing fields and resource metadata.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions github-actions bot added the kind/bug Categorizes issue or PR as related to a bug. label Dec 16, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 16, 2025

Walkthrough

Controller pre-fetches an existing generic target resource for CreationPolicy values (Merge, Orphan, Owner) and passes that resource as an optional base object into template application, enabling merge-style manifest construction and updated error handling.

Changes

Cohort / File(s) Summary
Controller merge logic
pkg/controllers/externalsecret/externalsecret_controller.go
Pre-fetches the generic target resource when CreationPolicy is Merge, Orphan, or Owner; stores pre-fetched result as baseObj; surfaces non-NotFound fetch errors; passes baseObj into template application; removes redundant subsequent fetches and adjusts control flow.
Template application refactor
pkg/controllers/externalsecret/externalsecret_controller_manifest.go
applyTemplateToManifest(...) signature updated to accept existingObj *unstructured.Unstructured; when provided uses a deep copy of existingObj as the base manifest for merge behavior, otherwise constructs a new object; initializes labels/annotations from base and retains templating flow (simple vs templated).
Tests: updated & new merge test
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go
All calls updated to new applyTemplateToManifest signature (pass existing resource or nil); adds TestApplyTemplateToManifest_MergeBehavior validating merge preserves existing fields (including resourceVersion/uid) and merges template-produced fields.
Imports & module adjustments
pkg/controllers/externalsecret/..., go.mod
Adds/imports unstructured usage and adjusts module/manifest bits to compile with new signatures and behavior.

Sequence Diagram(s)

sequenceDiagram
    actor Ctrl as ExternalSecret Controller
    participant K8s as Kubernetes API
    participant Tmpl as applyTemplateToManifest

    Ctrl->>K8s: GET target resource (if CreationPolicy is Merge/Orphan/Owner)
    alt resource exists
        K8s-->>Ctrl: 200 existing resource
        Ctrl->>Tmpl: applyTemplateToManifest(ctx, es, dataMap, existingObj)
        Tmpl->>Tmpl: deep copy existingObj -> baseObj
        Tmpl->>Tmpl: render template and merge into baseObj
        Tmpl-->>Ctrl: merged manifest
        Ctrl->>K8s: UPDATE resource (using baseObj UID/resourceVersion)
    else not found
        K8s-->>Ctrl: 404 NotFound
        Ctrl->>Tmpl: applyTemplateToManifest(ctx, es, dataMap, nil)
        Tmpl->>Tmpl: build new object from template (set GVK/name/namespace)
        Tmpl-->>Ctrl: new manifest
        Ctrl->>K8s: CREATE resource
    else fetch error
        K8s-->>Ctrl: error
        Ctrl->>Ctrl: mark reconcile failed / record error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • Correct deep-copy and merge semantics in applyTemplateToManifest.
    • Preservation and propagation of UID and resourceVersion when updating merged objects.
    • Error classification for pre-fetch (NotFound vs other errors).
    • Test coverage in TestApplyTemplateToManifest_MergeBehavior for nested maps, nil maps, and templated paths.

Suggested labels

area/templating

Poem

🐰 I found a base and gave it a squeeze,
Merged old roots with new template breeze,
Labels kept safe, UID snug and warm,
New fields hopped in, no sign of a storm,
A small rabbit cheers the manifest transform!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete with placeholder sections (Problem Statement, Proposed Changes) left unfilled, though it does reference the related issue #5587. Fill in the Problem Statement and Proposed Changes sections to explain what issue is being fixed and how the pre-fetch/merge logic addresses the nested path targeting problems.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title mentions 'target template parsing for complex matchers' which is partially related to the changeset. However, the actual changes focus on implementing pre-fetch logic and merge-based template application for CreationPolicy (Merge/Orphan/Owner), not on parsing complexity or matchers. Clarify the title to better reflect the main changes: implementing pre-fetch and merge-based template application logic for CreationPolicy handling.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code changes implement pre-fetch and merge-based template application (baseObj parameter, merge logic in applyTemplateToManifest) which addresses issue #5587's nested path targeting problems by ensuring existing resource fields are preserved during template application.
Out of Scope Changes check ✅ Passed All changes are directly scoped to solving the linked issue #5587: pre-fetch logic for CreationPolicy, merge-based template application, and test coverage for merge behavior are all within scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1)

170-193: Consider reordering logic to avoid unnecessary assignments when existingObj is provided.

When existingObj is provided, the initial assignments on lines 176-179 (setting GVK, Name, Namespace) are immediately discarded by the obj = existingObj.DeepCopy() on line 183. While functionally correct since the existing object already contains this metadata, this could be clearer:

 func (r *Reconciler) applyTemplateToManifest(ctx context.Context, es *esv1.ExternalSecret, dataMap map[string][]byte, existingObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
 	gvk := getTargetGVK(es)
 
-	obj := &unstructured.Unstructured{}
-	obj.SetGroupVersionKind(gvk)
-	obj.SetName(getTargetName(es))
-	obj.SetNamespace(es.Namespace)
-
+	var obj *unstructured.Unstructured
 	if existingObj != nil {
 		// use the existing object for merge behavior if it exists
 		obj = existingObj.DeepCopy()
+	} else {
+		obj = &unstructured.Unstructured{}
+		obj.SetGroupVersionKind(gvk)
+		obj.SetName(getTargetName(es))
+		obj.SetNamespace(es.Namespace)
 	}

This makes it explicit that the metadata initialization only happens for new objects.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8513f88 and 308fb38.

📒 Files selected for processing (3)
  • pkg/controllers/externalsecret/externalsecret_controller.go (2 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)
apis/externalsecrets/v1/externalsecret_types.go (5)
  • ExternalSecret (676-682)
  • ManifestReference (200-210)
  • ExternalSecretTemplate (95-116)
  • TemplateEngineV2 (135-135)
  • TemplateFrom (140-154)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (2)
pkg/controllers/externalsecret/externalsecret_controller.go (1)
  • Reconciler (138-154)
apis/externalsecrets/v1/externalsecret_types.go (1)
  • ExternalSecret (676-682)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: publish-artifacts (Dockerfile, CGO_ENABLED=0, amd64 arm64 s390x ppc64le, linux/amd64,linux/arm64,... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0, amd64 arm64 ppc64le, linux/amd64,linux/arm64,li... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0 GOEXPERIMENT=boringcrypto, amd64 ppc64le, linux/... / Build and Publish
  • GitHub Check: check-diff
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: Analyze project (actions, none)
  • GitHub Check: Analyze project (go, autobuild)
🔇 Additional comments (9)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1)

186-207: LGTM!

The label and annotation handling correctly:

  1. Preserves existing values from the base object when provided
  2. Initializes empty maps when needed
  3. Merges template metadata on top
  4. Always applies the managed label
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (4)

372-372: LGTM!

Test signature correctly updated to pass nil for the new existingObj parameter, maintaining the original test behavior.


434-434: LGTM!

Test signature correctly updated.


603-603: LGTM!

Test signature correctly updated.


631-723: LGTM! Comprehensive merge behavior test.

The test effectively validates that:

  1. Pre-existing fields (spec.type, spec.slack.channel, spec.slack.username) are preserved
  2. Template-derived fields (spec.slack.api_url) are correctly added
  3. The existing object's metadata (resourceVersion, uid) is preserved through DeepCopy
pkg/controllers/externalsecret/externalsecret_controller.go (4)

36-36: LGTM!

Import added for the new unstructured.Unstructured usage.


650-661: LGTM!

The existing resource fetch logic correctly:

  1. Only fetches for policies that need it (Merge, Orphan, Owner)
  2. Treats NotFound as a valid state (resource doesn't exist yet)
  3. Fails reconciliation for other errors

663-671: Verify: Template merge behavior only for CreatePolicyMerge.

The baseObj is only set for CreatePolicyMerge, meaning:

  • Merge policy: Templates are applied to the existing object, preserving existing fields
  • Orphan/Owner policies: Templates are applied to a fresh object (though ResourceVersion/UID are copied later for updates)

This appears intentional for "complex matchers" fix mentioned in the PR title, but please confirm this matches the expected behavior for Orphan/Owner policies.


683-700: LGTM!

The creation policy handling is correct:

  • Merge policy returns early if resource doesn't exist (lines 685-688)
  • Orphan/Owner policies correctly propagate ResourceVersion and UID from existing resource to enable optimistic concurrency control on updates (lines 694-695)
  • Creates new resource if it doesn't exist (line 698)

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)

631-716: Good test coverage for the merge behavior fix.

This test validates the core fix for issue #5587 by:

  1. Setting up an existing resource with pre-populated fields (spec.type, spec.slack.channel, spec.slack.username)
  2. Applying a template targeting a nested path (spec.slack) with a new field (api_url)
  3. Verifying that existing fields are preserved while the template-derived field is correctly added

Consider adding a test case for the scenario where existingObj has no spec field at all, to verify the merge handles initialization of nested paths correctly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 308fb38 and 2d0fed3.

📒 Files selected for processing (3)
  • pkg/controllers/externalsecret/externalsecret_controller.go (2 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/controllers/externalsecret/externalsecret_controller.go (2)
apis/externalsecrets/v1beta1/externalsecret_types.go (3)
  • CreatePolicyMerge (52-52)
  • CreatePolicyOrphan (49-49)
  • CreatePolicyOwner (45-45)
apis/externalsecrets/v1/externalsecret_types.go (4)
  • CreatePolicyMerge (52-52)
  • CreatePolicyOrphan (49-49)
  • CreatePolicyOwner (45-45)
  • ConditionReasonResourceSyncedError (636-636)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0 GOEXPERIMENT=boringcrypto, amd64 ppc64le, linux/... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile, CGO_ENABLED=0, amd64 arm64 s390x ppc64le, linux/amd64,linux/arm64,... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0, amd64 arm64 ppc64le, linux/amd64,linux/arm64,li... / Build and Publish
  • GitHub Check: check-diff
  • GitHub Check: lint
  • GitHub Check: unit-tests
  • GitHub Check: Analyze project (go, autobuild)
🔇 Additional comments (8)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (3)

372-372: LGTM!

Correctly updated to pass nil for the new existingObj parameter when no pre-existing resource should be used.


434-434: LGTM!

Correctly updated to pass nil for the new existingObj parameter.


603-603: LGTM!

Correctly updated to pass nil for the new existingObj parameter.

pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1)

170-194: LGTM - Clean implementation of merge behavior.

The implementation correctly:

  1. Uses DeepCopy() to avoid mutating the original existing object
  2. Preserves labels and annotations from the existing resource
  3. Ensures non-nil maps for labels/annotations before manipulation
  4. Always adds the managed label for tracking

Minor observation: Lines 176-179 set GVK, Name, and Namespace on obj before the existingObj check, which are then overwritten by the deep copy. This is harmless but slightly redundant when existingObj is provided.

pkg/controllers/externalsecret/externalsecret_controller.go (4)

36-36: LGTM!

The unstructured import is correctly added to support the new baseObj parameter of type *unstructured.Unstructured.


650-668: LGTM! Fetching existing resource for base object handling.

The logic correctly:

  1. Fetches the existing resource for Merge/Orphan/Owner policies
  2. Only passes the existing resource as baseObj for Merge policy, enabling templates to be applied on top of existing data
  3. Continues to use existing in the switch statement for ResourceVersion/UID propagation in Orphan/Owner paths

This addresses the issue where templates were creating new objects instead of merging into existing resources.


683-700: LGTM! Switch statement correctly integrates with the fetched resource.

The integration is correct:

  • Merge: Uses obj (template applied on existing resource) for update, or returns early if resource doesn't exist
  • Orphan/Owner: Preserves ResourceVersion/UID from existing resource for updates, or creates new resource

This maintains the previous behavior for Orphan/Owner while enabling proper merge behavior for the Merge policy.


671-675: applyTemplateToManifest correctly handles nil baseObj.

The function implementation (lines 181-184) explicitly checks for nil and creates a fresh object when existingObj is nil. For non-nil values (Merge policy with existing resources), it deep copies the existing object. Both code paths are properly handled.

annotations := make(map[string]string)
if existingObj != nil {
// use the existing object for merge behavior if it exists
obj = existingObj.DeepCopy()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not exactly sure but is the existingObj set with GVK or does that still need to be set here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing Object here comes from this function:

func (r *Reconciler) getGenericResource(ctx context.Context, log logr.Logger, es *esv1.ExternalSecret) (*unstructured.Unstructured, error) {
	gvk := getTargetGVK(es)

	resource := &unstructured.Unstructured{}
	resource.SetGroupVersionKind(gvk)

There, the GVK is being set.

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
@Skarlso Skarlso force-pushed the fix-the-target-spec branch from 624d739 to 1399c8b Compare December 18, 2025 06:05
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)

631-716: Excellent test coverage for the merge behavior fix.

This test directly validates the fix for issue #5587 by verifying that:

  • Existing fields (spec.type, spec.slack.channel, spec.slack.username) are preserved
  • Template-derived fields (spec.slack.api_url) are correctly added

The test effectively proves the template targeting spec.slack with api_url: {{ .url }} no longer produces duplicate path segments like spec.slack.slack.

Consider adding assertions for metadata preservation

The existing resource has resourceVersion and uid set. Since DeepCopy is used, these should be preserved. Adding assertions would strengthen confidence in the implementation:

+	// Verify metadata is preserved from existing resource
+	assert.Equal(t, "12345", result.GetResourceVersion(), "resourceVersion should be preserved")
+	assert.Equal(t, "test-uid-123", string(result.GetUID()), "uid should be preserved")
+
 	t.Logf("Result spec: %+v", result.Object["spec"])
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d0fed3 and 1399c8b.

📒 Files selected for processing (3)
  • pkg/controllers/externalsecret/externalsecret_controller.go (2 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1)
apis/externalsecrets/v1/externalsecret_types.go (1)
  • ExternalSecret (676-682)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0, amd64 arm64 ppc64le, linux/amd64,linux/arm64,li... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile, CGO_ENABLED=0, amd64 arm64 s390x ppc64le, linux/amd64,linux/arm64,... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0 GOEXPERIMENT=boringcrypto, amd64 ppc64le, linux/... / Build and Publish
  • GitHub Check: unit-tests
  • GitHub Check: check-diff
  • GitHub Check: lint
  • GitHub Check: Analyze project (go, autobuild)
🔇 Additional comments (4)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1)

170-193: LGTM! Clean implementation of the merge behavior.

The optional existingObj parameter allows templates to be applied to an existing resource for merge scenarios. The implementation correctly:

  • Uses DeepCopy to avoid mutating the original object
  • Initializes labels/annotations maps when nil to prevent nil map assignment panics
  • Falls back to creating a new object when existingObj is nil
pkg/controllers/externalsecret/externalsecret_controller.go (2)

650-671: LGTM! Well-structured pre-fetch and merge logic.

The implementation correctly:

  • Pre-fetches existing resources for all policies that may need them (Merge, Orphan, Owner)
  • Only passes the existing resource as baseObj for Merge policy, preserving the distinct semantics:
    • Merge: Apply template on top of existing resource (preserves existing fields)
    • Orphan/Owner: Replace resource entirely with template output (uses existing only for ResourceVersion/UID)
  • Properly handles NotFound errors without failing the reconciliation

683-699: Logic for Merge vs Orphan/Owner is correct.

For Merge (lines 683-691): The obj was built on top of the existing resource, preserving fields not touched by the template.

For Orphan/Owner (lines 692-699): The obj was built from scratch, and only the ResourceVersion/UID are copied from the existing resource to enable the update operation. This correctly implements a "replace" semantic rather than a merge.

pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)

372-372: Updated call signature looks correct.

Passing nil for the new existingObj parameter maintains the original behavior for these tests.

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
@Skarlso Skarlso force-pushed the fix-the-target-spec branch from 1399c8b to 3a35bea Compare December 18, 2025 11:51
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)

631-717: Consider additional edge case tests (optional).

While the current test comprehensively validates the merge behavior, you might consider adding tests for:

  1. Overwriting existing fields: Template sets a field that already exists in existingObj (e.g., changing spec.slack.channel)
  2. Creating new nested paths: Template targets a path that doesn't exist in existingObj (e.g., spec.newSection.field)

These would provide additional confidence in the template engine's merge semantics, though they can be deferred if the engine is already tested separately.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1399c8b and 3a35bea.

📒 Files selected for processing (2)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest.go (1 hunks)
  • pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (2)
pkg/controllers/externalsecret/externalsecret_controller.go (1)
  • Reconciler (138-154)
apis/externalsecrets/v1/externalsecret_types.go (1)
  • ExternalSecret (676-682)
pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (1)
apis/externalsecrets/v1/externalsecret_types.go (7)
  • ExternalSecret (676-682)
  • ExternalSecretSpec (503-535)
  • ExternalSecretTarget (214-249)
  • ManifestReference (200-210)
  • ExternalSecretTemplate (95-116)
  • TemplateEngineV2 (135-135)
  • TemplateFrom (140-154)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0 GOEXPERIMENT=boringcrypto, amd64 ppc64le, linux/... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile, CGO_ENABLED=0, amd64 arm64 s390x ppc64le, linux/amd64,linux/arm64,... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0, amd64 arm64 ppc64le, linux/amd64,linux/arm64,li... / Build and Publish
  • GitHub Check: lint
  • GitHub Check: check-diff
  • GitHub Check: unit-tests
  • GitHub Check: Analyze project (go, autobuild)
🔇 Additional comments (4)
pkg/controllers/externalsecret/externalsecret_controller_manifest.go (2)

171-184: LGTM: Merge behavior implementation is correct.

The function now properly supports merge-based template application by accepting an optional existingObj. The logic correctly:

  • Uses DeepCopy() to prevent mutation of the existing resource
  • Falls back to creating a new object when existingObj is nil
  • Preserves metadata like resourceVersion and UID from the existing resource

This addresses the issue described in #5587 where path-targeted templates were producing "unknown field" errors due to incorrect path construction.


186-193: LGTM: Safe initialization of labels and annotations.

The nil checks ensure that GetLabels() and GetAnnotations() returning nil don't cause panics when attempting to merge template metadata in subsequent lines.

pkg/controllers/externalsecret/externalsecret_controller_manifest_test.go (2)

372-372: LGTM: Test calls updated correctly.

All existing test calls properly pass nil for the new existingObj parameter, preserving the original behavior where templates are applied to newly created objects.

Also applies to: 434-434, 603-603


631-717: Excellent test coverage for merge behavior!

This test validates the fix for issue #5587 by:

  • Creating an existing resource with pre-populated fields (spec.type, spec.slack.channel, spec.slack.username)
  • Applying a template targeting spec.slack with a new api_url field
  • Verifying that existing fields are preserved while the new field is added
  • Ensuring metadata like resourceVersion and UID are preserved

This directly addresses the reported problem where targeting spec.slack would incorrectly create paths like spec.slack.slack or spec.slack.api_url.url.

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

On-behalf-of: Gergely Brautigam <gergely.brautigam@sap.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/controllers/externalsecret/externalsecret_controller.go (1)

650-668: Pre-fetch and baseObj logic correctly addresses the Merge policy requirements.

The logic properly:

  1. Pre-fetches existing resource for policies that need it (Merge/Orphan/Owner)
  2. Sets baseObj only for Merge policy when the resource exists
  3. Keeps baseObj nil for Owner/Orphan, ensuring templates apply to fresh objects

This correctly addresses the concern from previous review about accidentally causing a merge for Owner policy.

One optional consideration: Line 666 could also check existing.GetUID() != "" for extra defensive coding, though this is handled downstream at line 685 for Merge policy.

🔎 Optional defensive check
 var baseObj *unstructured.Unstructured
-if externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyMerge && existing != nil {
+if externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyMerge && existing != nil && existing.GetUID() != "" {
     baseObj = existing
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a35bea and 9407821.

📒 Files selected for processing (1)
  • pkg/controllers/externalsecret/externalsecret_controller.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/controllers/externalsecret/externalsecret_controller.go (2)
apis/externalsecrets/v1/externalsecret_types.go (4)
  • CreatePolicyMerge (52-52)
  • CreatePolicyOrphan (49-49)
  • CreatePolicyOwner (45-45)
  • ConditionReasonResourceSyncedError (636-636)
apis/externalsecrets/v1beta1/externalsecret_types.go (3)
  • CreatePolicyMerge (52-52)
  • CreatePolicyOrphan (49-49)
  • CreatePolicyOwner (45-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0 GOEXPERIMENT=boringcrypto, amd64 ppc64le, linux/... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile.ubi, CGO_ENABLED=0, amd64 arm64 ppc64le, linux/amd64,linux/arm64,li... / Build and Publish
  • GitHub Check: publish-artifacts (Dockerfile, CGO_ENABLED=0, amd64 arm64 s390x ppc64le, linux/amd64,linux/arm64,... / Build and Publish
  • GitHub Check: unit-tests
  • GitHub Check: check-diff
  • GitHub Check: lint
  • GitHub Check: Analyze project (go, autobuild)
🔇 Additional comments (2)
pkg/controllers/externalsecret/externalsecret_controller.go (2)

36-36: Import addition looks good.

The unstructured import is correctly added to support the new baseObj parameter used in the template application logic.


670-675: Passing baseObj to applyTemplateToManifest fixes template path targeting.

The implementation correctly handles the nil case for non-Merge policies and applies templates onto the existing resource for Merge policy, resolving the duplicated field path segments reported in issue #5587.

@sonarqubecloud
Copy link
Copy Markdown

@Skarlso Skarlso merged commit a6558ca into external-secrets:main Dec 30, 2025
30 checks passed
@Skarlso Skarlso deleted the fix-the-target-spec branch December 30, 2025 21:12
dsp0x4 pushed a commit to dsp0x4/external-secrets that referenced this pull request Mar 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/bug Categorizes issue or PR as related to a bug. size/m

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

alpha feature "Targeting Custom Resources" - "Advanced Path Targeting" templating works not as expected

3 participants