feat(controller): resolve Promotion.spec.target into promotion context - #6791
Open
jsbroks wants to merge 1 commit into
Open
feat(controller): resolve Promotion.spec.target into promotion context#6791jsbroks wants to merge 1 commit into
jsbroks wants to merge 1 commit into
Conversation
The promotion engine has been able to expose a Target's params and labels to step expressions as the top-level `target` variable, but nothing ever populated Context.Target, so `target.*` never resolved. Load the Target named by a Promotion's spec.target and build a TargetContext from it, decoding spec.params from JSON into the plain values expressions operate on. Promotions that name no Target promote to the Stage itself and carry no target context, so any reference to `target.*` fails exactly as it did before Targets existed. This is what allows one Stage's promotion process to behave differently per destination rather than running byte-identical steps everywhere. Signed-off-by: Justin Brooks <justin.brooks@akuity.io>
✅ Deploy Preview for docs-kargo-io ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
jsbroks
marked this pull request as ready for review
August 11, 2026 17:09
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6791 +/- ##
==========================================
+ Coverage 30.84% 30.96% +0.11%
==========================================
Files 826 827 +1
Lines 80666 80724 +58
==========================================
+ Hits 24885 24997 +112
+ Misses 54327 54262 -65
- Partials 1454 1465 +11 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
thomastaylor312
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The promotion engine has been able to expose a Target's params and labels to step expressions as the top-level
targetvariable since #6608 —BuildCtxMappopulatesenv["target"]fromContext.Target, andpromotion.TargetContexthas existed alongside it. But nothing ever setContext.Target, so${{ target.params.* }}never resolved and the plumbing was unreachable.This PR closes that gap: the Promotions controller now loads the Target named by a Promotion's
spec.targetand builds aTargetContextfrom it.Without this, every Promotion fanned out across a Stage's Targets would run byte-identical steps — per-destination parameterization is the entire point of Targets, so this is the piece that turns the fan-out into a feature.
Scope: the Promotion → Target read path only. No Stage-side selector resolution, no
Target.status.ownedBy, no webhooks, no PromotionSet involvement. Those follow in later PRs.What changed
pkg/api/target.go—GetTarget, mirroring the existingGetStage/GetWarehousehelpers.pkg/promotion/promotion.go—NewTargetContext(*kargoapi.Target)decodesspec.paramsfrommap[string]apiextensionsv1.JSONinto the plain values expressions operate on, and clones labels so the context does not alias the fetched object. Adds theWithTargetContextOption.pkg/controller/promotions/promotions.go—resolveTargetContextloads the Target and returnsnilfor Promotions that name none. Wired intopromote()at theNewContextcall.Promotions that name no Target promote to the Stage itself and carry no target context, so a reference to
target.*fails exactly as it did before Targets existed. Behavior for every existing Promotion is unchanged.Notes for reviewers
Why conversion is a separate constructor rather than folded into
WithTarget. The JSON decode can fail, andContextOptionhas no way to report an error. KeepingNewTargetContextseparate lets the controller surface a clear error on the Promotion instead of silently dropping params.Governance is deliberately not validated here. The controller fails a Promotion whose
spec.targetnames a nonexistent Target, but will currently promote to a Target the referenced Stage does not govern. Checking that requires evaluating the Stage'stargetSelectors, which belongs in the admission webhook rather than duplicated in the controller — otherwise two places own the same rule. No privilege escalation is involved (users already haveget/list/watchon Targets in their own Project), but it is a correctness gap to close when the webhook lands.One behavior worth knowing, now pinned by a test. Numeric params reach a step as
int, not thefloat64they are decoded to, because step config is round-tripped through YAML inStepEvaluator.Config. This is consistent with how every other config value behaves, so nothing changed — but it is now asserted rather than assumed.Testing
TestGetTarget— not-found, client error, and success.TestNewTargetContext— nil Target, scalar/nested/absent params, malformed params, label copying, and that decoded params surviveDeepCopy(which relies onruntime.DeepCopyJSONand would panic on anything that is not a plain JSON type).TestWithTarget— nil leaves the Context target-less; non-nil is set.Test_reconciler_resolveTargetContext— including that a Promotion naming no Target performs no read at all.Test_reconciler_promote_targetContext— asserts the resolved context actually reaches the engine, which is the real integration point.TestStepEvaluator_Config_target— end-to-end through the real evaluator:${{ target.params.branch }},${{ target.params.ingress.host }}, and${{ target.labels.region }}resolve in step config, and atarget.*reference still errors when there is no Target.make lint-gois clean. Affected packages pass under-race.pkg/promotion/runner/builtinhas one failure locally (Test_kustomizeBuilder_run/successful_build_with_HelmChartInflationGenerator), confirmed viagit stashto fail identically on a clean tree — a local environment issue, unrelated to this change.Checklist
Eligibility
kind/proposal,needs discussion,needs research,maintainer only,area/security,size/large,size/x-large,size/xx-large).Quality
AI Use Disclosure
This PR was written:
Sign-Off
All commits:
git commit -s) (required)git commit -S) (encouraged)