feat: add workload wiring for resource dependencies - #3404
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/templating/context.md (1)
535-637:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDocument the resource-dependency half of
dependenciesend-to-end.This section still reads as connection-only. It omits
dependencies.resources,dependencies.volumeMounts,dependencies.volumes, and the newdependencies.toContainerVolumeMounts()/dependencies.toVolumes()helpers. The example on Lines 601-606 also dropsvalueFrom, so secret/configMap-backed dependency envs are documented incorrectly.Also applies to: 852-868
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/templating/context.md` around lines 535 - 637, Update the dependencies docs to cover resource-dependency fields and helpers end-to-end: add descriptions and examples for dependencies.resources, dependencies.volumeMounts, dependencies.volumes and document the new helper methods dependencies.toContainerVolumeMounts() and dependencies.toVolumes(); update the examples (including the env example around lines ~601) to show EnvVar.valueFrom usage (secretKeyRef/configMapKeyRef) as well as literal value, and ensure the table and example YAML reflect that dependencies.envVars is merged from both connection items and resource dependencies and that items/envVars are never null; reference symbols to change: dependencies.resources, dependencies.volumeMounts, dependencies.volumes, dependencies.toContainerVolumeMounts(), dependencies.toVolumes(), and EnvVar.valueFrom.
🧹 Nitpick comments (1)
internal/controller/releasebinding/controller_resourcedependencies.go (1)
216-222: ⚡ Quick winConsider checking
cond.ObservedGenerationagainstrrb.Generationbefore treating the provider as Ready.
isResourceReleaseBindingReadyonly inspectscond.Status. If the providerResourceReleaseBinding's spec is updated (e.g., outputs change) but its controller has not yet reconciled the new generation, the previously-writtenReady=Truecondition is still present and the consumer here will accept it and dispatch potentially-stalerrb.Status.Outputsinto the renderer. Adding anObservedGeneration == Generationcheck makes the readiness gate robust to provider-controller lag and aligns with the PR's stated goal of waiting for "full steady-state on the provider".♻️ Proposed defensive check
func isResourceReleaseBindingReady(rrb *openchoreov1alpha1.ResourceReleaseBinding) bool { cond := meta.FindStatusCondition(rrb.Status.Conditions, string(resourcereleasebinding.ConditionReady)) if cond == nil { return false } - return cond.Status == metav1.ConditionTrue + if cond.ObservedGeneration != 0 && cond.ObservedGeneration < rrb.Generation { + return false + } + return cond.Status == metav1.ConditionTrue }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/releasebinding/controller_resourcedependencies.go` around lines 216 - 222, Update isResourceReleaseBindingReady to also verify that the condition's ObservedGeneration matches the ResourceReleaseBinding's current Generation before returning true: locate isResourceReleaseBindingReady(rrb *openchoreov1alpha1.ResourceReleaseBinding), after retrieving cond via meta.FindStatusCondition check cond != nil, then require cond.Status == metav1.ConditionTrue AND cond.ObservedGeneration == rrb.Generation (treat missing/zero ObservedGeneration as not current). This ensures the function only treats a provider as Ready when the controller has reconciled the current spec/generation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/templating/context.md`:
- Around line 722-734: The TraitContext example wrongly includes
dataplane.publicVirtualHost which is not part of the DataPlaneData shape used by
TraitContext/ComponentContext; remove the publicVirtualHost entry from the YAML
example under dataplane and update the prose to only show secretStore and
observabilityPlaneRef (keeping observabilityPlaneRef fields kind and name).
Locate the example block labeled "dataplane:" in docs/templating/context.md and
delete the publicVirtualHost line and its comment, ensuring references to
ComponentContext, TraitContext, and DataPlaneData remain consistent.
In `@install/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yaml`:
- Around line 234-249: The CRD currently allows empty strings for envBindings
and fileBindings values; to fix, update the Go API type that declares the
envBindings and fileBindings fields (the map fields on the ResourceType/Workload
spec type) so the map value cannot be empty: create a small aliased type like
NonEmptyString string with a kubebuilder validation marker //
+kubebuilder:validation:MinLength=1 and replace map[string]string with
map[string]NonEmptyString for the EnvBindings and FileBindings fields (or add
the equivalent kubebuilder validation marker directly if your toolchain supports
per-value validation), then regenerate the CRDs under
install/helm/openchoreo-control-plane/crds/.
In `@internal/pipeline/component/context/resourcedependency.go`:
- Around line 93-132: The loop over fileBindingNames (using dep.FileBindings and
outputByName) must detect and reject duplicate mount paths before appending to
item.VolumeMounts; add a small set (map[string]struct{}) like mountPathSeen and,
for each outputName, check mountPath := dep.FileBindings[outputName] and if
mountPath is already in the set return a clear dependency error (e.g.,
fmt.Errorf("%w: %s", ErrDuplicateMountPath, mountPath) — create
ErrDuplicateMountPath if it doesn’t exist); only when mountPath is new add it to
the set and proceed with existing logic that builds volumes (volumes map,
volumeKey, resourceDepVolumeName) and appends VolumeMountEntry to
item.VolumeMounts.
---
Outside diff comments:
In `@docs/templating/context.md`:
- Around line 535-637: Update the dependencies docs to cover resource-dependency
fields and helpers end-to-end: add descriptions and examples for
dependencies.resources, dependencies.volumeMounts, dependencies.volumes and
document the new helper methods dependencies.toContainerVolumeMounts() and
dependencies.toVolumes(); update the examples (including the env example around
lines ~601) to show EnvVar.valueFrom usage (secretKeyRef/configMapKeyRef) as
well as literal value, and ensure the table and example YAML reflect that
dependencies.envVars is merged from both connection items and resource
dependencies and that items/envVars are never null; reference symbols to change:
dependencies.resources, dependencies.volumeMounts, dependencies.volumes,
dependencies.toContainerVolumeMounts(), dependencies.toVolumes(), and
EnvVar.valueFrom.
---
Nitpick comments:
In `@internal/controller/releasebinding/controller_resourcedependencies.go`:
- Around line 216-222: Update isResourceReleaseBindingReady to also verify that
the condition's ObservedGeneration matches the ResourceReleaseBinding's current
Generation before returning true: locate isResourceReleaseBindingReady(rrb
*openchoreov1alpha1.ResourceReleaseBinding), after retrieving cond via
meta.FindStatusCondition check cond != nil, then require cond.Status ==
metav1.ConditionTrue AND cond.ObservedGeneration == rrb.Generation (treat
missing/zero ObservedGeneration as not current). This ensures the function only
treats a provider as Ready when the controller has reconciled the current
spec/generation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 38a52623-89c3-4dd5-ae1e-019ad010f6cb
⛔ Files ignored due to path filters (4)
api/v1alpha1/zz_generated.deepcopy.gois excluded by!**/zz_generated.*config/crd/bases/openchoreo.dev_componentreleases.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_releasebindings.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_workloads.yamlis excluded by!config/crd/bases/**
📒 Files selected for processing (36)
api/v1alpha1/releasebinding_types.goapi/v1alpha1/workload_types.godocs/templating/context.mdinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_releasebindings.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yamlinternal/controller/releasebinding/controller.gointernal/controller/releasebinding/controller_conditions.gointernal/controller/releasebinding/controller_connections.gointernal/controller/releasebinding/controller_integration_test.gointernal/controller/releasebinding/controller_resourcedependencies.gointernal/controller/releasebinding/controller_resourcedependencies_integration_test.gointernal/controller/releasebinding/controller_resourcedependencies_test.gointernal/controller/releasebinding/controller_status.gointernal/controller/releasebinding/controller_watch.gointernal/controller/releasebinding/suite_test.gointernal/controller/watch.gointernal/controller/watch_test.gointernal/pipeline/component/context/builder_test.gointernal/pipeline/component/context/cel_extensions.gointernal/pipeline/component/context/cel_extensions_test.gointernal/pipeline/component/context/cel_return_types.gointernal/pipeline/component/context/resourcedependency.gointernal/pipeline/component/context/resourcedependency_test.gointernal/pipeline/component/context/types.gointernal/pipeline/component/context/types_test.gointernal/pipeline/component/pipeline.gointernal/pipeline/component/types.gointernal/validation/component/cel_env_test.gosamples/component-types/component-with-configs/component-with-configs.yamlsamples/component-types/component-with-embedded-traits/component-with-embedded-traits.yamlsamples/getting-started/all.yamlsamples/getting-started/component-types/scheduled-task.yamlsamples/getting-started/component-types/service.yamlsamples/getting-started/component-types/webapp.yamlsamples/getting-started/component-types/worker.yaml
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/v1alpha1/workload_types.go`:
- Around line 242-243: The current CEL validation on EnvBindings only checks map
values, allowing empty keys; change the kubebuilder XValidation rule on
EnvBindings to require both non-empty keys and non-empty values by using a
predicate like self.all(k, k != '' && self[k].size() > 0) (replace the existing
rule expression), and make the same change to the other map binding field in the
same struct that has the similar validation to ensure keys cannot be empty there
either.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1a77b075-015f-45ed-89d6-f7066b97d315
⛔ Files ignored due to path filters (2)
config/crd/bases/openchoreo.dev_componentreleases.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_workloads.yamlis excluded by!config/crd/bases/**
📒 Files selected for processing (6)
api/v1alpha1/workload_types.godocs/templating/context.mdinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yamlinternal/controller/releasebinding/controller_resourcedependencies.gointernal/controller/releasebinding/controller_resourcedependencies_test.go
✅ Files skipped from review due to trivial changes (1)
- docs/templating/context.md
🚧 Files skipped from review as they are similar to previous changes (4)
- install/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yaml
- internal/controller/releasebinding/controller_resourcedependencies.go
- install/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yaml
- internal/controller/releasebinding/controller_resourcedependencies_test.go
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
api/v1alpha1/workload_types.go (1)
242-250:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate dependency binding map keys as non-empty
Line 242 and Line 249 only validate map values. Empty output-name keys are still admitted and can produce unresolvable dependency bindings. Validate both keys and values.
Suggested patch
- // +kubebuilder:validation:XValidation:rule="self.all(k, self[k].size() > 0)",message="envBindings values (env var names) cannot be empty" + // +kubebuilder:validation:XValidation:rule="self.all(k, k.size() > 0 && self[k].size() > 0)",message="envBindings keys (output names) and values (env var names) cannot be empty" EnvBindings map[string]string `json:"envBindings,omitempty"` @@ - // +kubebuilder:validation:XValidation:rule="self.all(k, self[k].size() > 0)",message="fileBindings values (mount paths) cannot be empty" + // +kubebuilder:validation:XValidation:rule="self.all(k, k.size() > 0 && self[k].size() > 0)",message="fileBindings keys (output names) and values (mount paths) cannot be empty" FileBindings map[string]string `json:"fileBindings,omitempty"`#!/bin/bash # Verify current rule only checks map values. rg -n 'XValidation:rule="self\.all\(k, self\[k\]\.size\(\) > 0\)"|EnvBindings|FileBindings' api/v1alpha1/workload_types.go -C2As per coding guidelines, "Verify kubebuilder validation tags are consistent and safe."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/v1alpha1/workload_types.go` around lines 242 - 250, The XValidation tags on the EnvBindings and FileBindings fields only check map values; update the kubebuilder validation rule for both EnvBindings and FileBindings so it asserts both the map key and the map value are non-empty (i.e., check k.size() > 0 and self[k].size() > 0 in the rule expression) so empty output-name keys are rejected; locate the annotations on the EnvBindings and FileBindings fields and replace the existing rule string accordingly.
🧹 Nitpick comments (1)
internal/pipeline/component/context/resourcedependency_test.go (1)
270-273: ⚡ Quick winHandle
BuildResourceDependencyItemerrors explicitly in this determinism test.The test currently discards errors, which can mask regressions and lead to less actionable failures when indexing into
Volumes.Proposed patch
- first, _ := BuildResourceDependencyItem(dep, outputs) + first, err := BuildResourceDependencyItem(dep, outputs) + require.NoError(t, err) + require.Len(t, first.Volumes, 1) for i := 0; i < 10; i++ { - again, _ := BuildResourceDependencyItem(dep, outputs) + again, err := BuildResourceDependencyItem(dep, outputs) + require.NoError(t, err) + require.Len(t, again.Volumes, 1) require.Equal(t, first.Volumes[0].Name, again.Volumes[0].Name) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/pipeline/component/context/resourcedependency_test.go` around lines 270 - 273, The test currently ignores errors from BuildResourceDependencyItem and then indexes into Volumes, which can hide failures; change the calls to capture and assert on the error (e.g., first, err := BuildResourceDependencyItem(dep, outputs) with require.NoError(t, err) and inside the loop again, err := BuildResourceDependencyItem(dep, outputs) with require.NoError(t, err)) before comparing first.Volumes[0].Name and again.Volumes[0].Name to ensure the test fails clearly if BuildResourceDependencyItem returns an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/templating/context.md`:
- Around line 699-708: The snippet's env only includes dependency envs,
contradicting the heading; update the template so the container env merges
configuration-derived envs with dependency envs (e.g., use
configurations.toContainerEnvs() + dependencies.toContainerEnvs()) or explicitly
document/ADD envFrom as needed; change the env line in the template (referencing
configurations.toContainerEnvs() and dependencies.toContainerEnvs()) to reflect
the combined projection so configurations are not dropped.
In `@install/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yaml`:
- Around line 242-256: The CEL validations only check map values; update the Go
API types that define envBindings and fileBindings (the map fields named
EnvBindings/FileBindings or similar in the workload API struct) to enforce
non-empty keys by adding a validation tag or marker (e.g.,
kubebuilder:validation:MinLength=1 for map keys or a
+kubebuilder:validation:XValidation rule that ensures self.keys().all(k,
k.size() > 0)), then regenerate the CRD so the generated YAML includes CEL rules
that assert both keys and values are non-empty for envBindings and fileBindings.
Make sure the code changes reference the exact field names EnvBindings and
FileBindings (or envBindings/fileBindings in the Go struct) and run the CRD
generation step to update
install/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yaml.
In
`@internal/controller/releasebinding/controller_resourcedependencies_integration_test.go`:
- Around line 270-276: The Ready condition in the test fixtures doesn't set
ObservedGeneration, so isResourceReleaseBindingReady() rejects the condition;
update the metav1.Condition used for rrb.Status.Conditions (and the similar
fixture at the other block) to include ObservedGeneration equal to
rrb.Generation (i.e., set ObservedGeneration: rrb.Generation) before calling
k8sClient.Status().Update so the condition is accepted as for the current
generation.
In `@internal/controller/releasebinding/controller_watch.go`:
- Around line 173-225: The predicate currently only triggers on Status.Outputs
or Ready.Status flips (resourceReleaseBindingOutputsChangedPredicate ->
UpdateFunc / readyConditionStatusChanged), but misses metadata.generation and
Ready.ObservedGeneration changes so consumers stay stale; update the UpdateFunc
to also return true when oldRRB.GetGeneration() != newRRB.GetGeneration() or
when the Ready condition's ObservedGeneration differs between old and new
(similar to how isResourceReleaseBindingReady uses ObservedGeneration), i.e.,
extract the Ready condition from oldRRB and newRRB and compare their
ObservedGeneration fields and short-circuit true if they differ.
In `@internal/pipeline/component/context/types.go`:
- Around line 403-416: The flattener appends every resource's Volumes into
mergedVolumes, causing duplicate VolumeEntry names when the same dependency is
referenced twice; modify the loop that builds mergedVolumes (in the same
function handling data.Resources and mergedVolumes) to deduplicate by
VolumeEntry.Name: keep a local map[string]struct{} (e.g., seenVolumeNames) and
only append a VolumeEntry to mergedVolumes if its Name is not in the map, then
mark it seen; leave each resource's item.Volumes unchanged and ensure you
reference the VolumeEntry.Name field when checking uniqueness (this will prevent
duplicate volume names produced by BuildResourceDependencyItem).
---
Duplicate comments:
In `@api/v1alpha1/workload_types.go`:
- Around line 242-250: The XValidation tags on the EnvBindings and FileBindings
fields only check map values; update the kubebuilder validation rule for both
EnvBindings and FileBindings so it asserts both the map key and the map value
are non-empty (i.e., check k.size() > 0 and self[k].size() > 0 in the rule
expression) so empty output-name keys are rejected; locate the annotations on
the EnvBindings and FileBindings fields and replace the existing rule string
accordingly.
---
Nitpick comments:
In `@internal/pipeline/component/context/resourcedependency_test.go`:
- Around line 270-273: The test currently ignores errors from
BuildResourceDependencyItem and then indexes into Volumes, which can hide
failures; change the calls to capture and assert on the error (e.g., first, err
:= BuildResourceDependencyItem(dep, outputs) with require.NoError(t, err) and
inside the loop again, err := BuildResourceDependencyItem(dep, outputs) with
require.NoError(t, err)) before comparing first.Volumes[0].Name and
again.Volumes[0].Name to ensure the test fails clearly if
BuildResourceDependencyItem returns an error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c119f6a4-57bf-41ca-8e91-a26d0efd5943
⛔ Files ignored due to path filters (4)
api/v1alpha1/zz_generated.deepcopy.gois excluded by!**/zz_generated.*config/crd/bases/openchoreo.dev_componentreleases.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_releasebindings.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_workloads.yamlis excluded by!config/crd/bases/**
📒 Files selected for processing (36)
api/v1alpha1/releasebinding_types.goapi/v1alpha1/workload_types.godocs/templating/context.mdinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_releasebindings.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yamlinternal/controller/releasebinding/controller.gointernal/controller/releasebinding/controller_conditions.gointernal/controller/releasebinding/controller_connections.gointernal/controller/releasebinding/controller_integration_test.gointernal/controller/releasebinding/controller_resourcedependencies.gointernal/controller/releasebinding/controller_resourcedependencies_integration_test.gointernal/controller/releasebinding/controller_resourcedependencies_test.gointernal/controller/releasebinding/controller_status.gointernal/controller/releasebinding/controller_watch.gointernal/controller/releasebinding/suite_test.gointernal/controller/watch.gointernal/controller/watch_test.gointernal/pipeline/component/context/builder_test.gointernal/pipeline/component/context/cel_extensions.gointernal/pipeline/component/context/cel_extensions_test.gointernal/pipeline/component/context/cel_return_types.gointernal/pipeline/component/context/resourcedependency.gointernal/pipeline/component/context/resourcedependency_test.gointernal/pipeline/component/context/types.gointernal/pipeline/component/context/types_test.gointernal/pipeline/component/pipeline.gointernal/pipeline/component/types.gointernal/validation/component/cel_env_test.gosamples/component-types/component-with-configs/component-with-configs.yamlsamples/component-types/component-with-embedded-traits/component-with-embedded-traits.yamlsamples/getting-started/all.yamlsamples/getting-started/component-types/scheduled-task.yamlsamples/getting-started/component-types/service.yamlsamples/getting-started/component-types/webapp.yamlsamples/getting-started/component-types/worker.yaml
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/controller/releasebinding/controller_watch.go (2)
53-67: ⚡ Quick winConsider validating target fields before indexing.
indexResourceDependencyTargetsbuilds index keys without checking whethert.Namespace,t.Project,t.ResourceName, ort.Environmentare non-empty. IfStatus.ResourceDependencyTargetscontains a malformed entry (e.g., emptyProject), the resulting partial key (e.g.,"ns//resource/env") will be indexed and could incorrectly match queries from other malformed providers. The query-side guard at lines 248–250 prevents querying with partial keys, but a symmetric guard here would prevent indexing them in the first place.While the controller should write well-formed status, adding a defensive check mirrors the rationale in the
findConsumerReleaseBindingsForResourceReleaseBindingcomment and improves robustness against bugs or races.Proposed validation
func indexResourceDependencyTargets(rb *openchoreov1alpha1.ReleaseBinding) []string { if len(rb.Status.ResourceDependencyTargets) == 0 { return nil } seen := make(map[string]struct{}) var keys []string for _, t := range rb.Status.ResourceDependencyTargets { + // Skip malformed targets to avoid partial-key matches. + if t.Namespace == "" || t.Project == "" || t.ResourceName == "" || t.Environment == "" { + continue + } key := makeResourceDependencyTargetKey(t.Namespace, t.Project, t.ResourceName, t.Environment) if _, ok := seen[key]; !ok { seen[key] = struct{}{} keys = append(keys, key) } } return keys }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/releasebinding/controller_watch.go` around lines 53 - 67, indexResourceDependencyTargets currently builds keys from rb.Status.ResourceDependencyTargets without validating fields which can create partial keys (e.g., "ns//resource/env"); update indexResourceDependencyTargets to skip any target t where t.Namespace, t.Project, t.ResourceName or t.Environment are empty before calling makeResourceDependencyTargetKey, optionally emitting a warning via the controller logger when a malformed target is encountered, and keep deduplication via the seen map unchanged so only well-formed keys are indexed.
51-51: ⚡ Quick winClarify the comment wording.
The comment states "Exported helper kept package-private," but the function is unexported (lowercase identifier). The phrasing is contradictory.
Suggested revision
-// Exported helper kept package-private so unit tests can register the same indexer the +// Package-private helper so unit tests can register the same indexer the // production setup uses on the manager's cache.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/controller/releasebinding/controller_watch.go` at line 51, The comment above the package-private helper in controller_watch.go is contradictory; change it to clearly state that the helper is intentionally unexported (lowercase) so unit tests can register the same indexer — e.g., "keep this helper unexported so unit tests can register the same indexer" — and update the comment near the helper function (the unexported indexer registration helper) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/controller/releasebinding/controller_watch.go`:
- Around line 53-67: indexResourceDependencyTargets currently builds keys from
rb.Status.ResourceDependencyTargets without validating fields which can create
partial keys (e.g., "ns//resource/env"); update indexResourceDependencyTargets
to skip any target t where t.Namespace, t.Project, t.ResourceName or
t.Environment are empty before calling makeResourceDependencyTargetKey,
optionally emitting a warning via the controller logger when a malformed target
is encountered, and keep deduplication via the seen map unchanged so only
well-formed keys are indexed.
- Line 51: The comment above the package-private helper in controller_watch.go
is contradictory; change it to clearly state that the helper is intentionally
unexported (lowercase) so unit tests can register the same indexer — e.g., "keep
this helper unexported so unit tests can register the same indexer" — and update
the comment near the helper function (the unexported indexer registration
helper) accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ef03f6b8-a80d-4f5c-b542-9bc59e9d564e
⛔ Files ignored due to path filters (2)
config/crd/bases/openchoreo.dev_componentreleases.yamlis excluded by!config/crd/bases/**config/crd/bases/openchoreo.dev_workloads.yamlis excluded by!config/crd/bases/**
📒 Files selected for processing (7)
api/v1alpha1/workload_types.godocs/templating/context.mdinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yamlinstall/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yamlinternal/controller/releasebinding/controller_resourcedependencies_integration_test.gointernal/controller/releasebinding/controller_resourcedependencies_test.gointernal/controller/releasebinding/controller_watch.go
🚧 Files skipped from review as they are similar to previous changes (6)
- install/helm/openchoreo-control-plane/crds/openchoreo.dev_componentreleases.yaml
- install/helm/openchoreo-control-plane/crds/openchoreo.dev_workloads.yaml
- internal/controller/releasebinding/controller_resourcedependencies_integration_test.go
- api/v1alpha1/workload_types.go
- docs/templating/context.md
- internal/controller/releasebinding/controller_resourcedependencies_test.go
- Add WorkloadResourceDependency on Workload.spec.dependencies.resources[] with ref, envBindings (output to env var name) and fileBindings (output to mount path) - Add ResourceDependencyTarget on ReleaseBindingStatus as the reverse-watch index source for ResourceReleaseBinding-to-consumer enqueue - Add PendingResourceDependency on ReleaseBindingStatus for diagnostic surface - Add GetDependencyResources getter on WorkloadTemplateSpec - Regenerate DeepCopy and CRDs (Workload, ReleaseBinding, ComponentRelease) Signed-off-by: Miraj Abeysekara <[email protected]>
- BuildResourceDependencyItem dispatches per-output on source kind: value -> literal env, secretKeyRef / configMapKeyRef -> valueFrom env, fileBindings -> volume + mount with subPath
- Deterministic volume names via r-{fnv1a32(ref:kind:name)} keep the namespace disjoint from configurations.toVolumes() (file-mount-*) and from sibling deps that share a Secret/ConfigMap
- Volume dedup per (dep.Ref, sourceKind, sourceName) so multiple files from the same Secret share one volume with multiple mounts
- ErrOutputNotResolved / ErrInvalidFileBinding sentinels let callers distinguish wait-for-resolve from permanent misconfiguration
- 20 unit tests covering dispatch matrix, dedup, deterministic ordering, length bounds, and r-/file-mount- prefix isolation
Signed-off-by: Miraj Abeysekara <[email protected]>
- Replace ConnectionEnvVar with corev1.EnvVar throughout pipeline-context so the merged dependencies.envVars can carry valueFrom-shaped resource outputs alongside literal endpoint env vars - Extend ConnectionsContextData with Resources (per-resource view), VolumeMounts and Volumes (merged across resources); add Resources to ConnectionsData - Extend RenderInput with ResourceDependencyItems and thread it through pipeline.go into ConnectionsData.Resources - Update endpoint-side buildEnvVarsForConnection to return []corev1.EnvVar - Update existing test sites (cel_extensions_test, builder_test) for the rename and the new dependency-shape keys - Add 7 TestNewDependenciesContextData_ResourceMerge subtests covering per-item view, merged env vars, volume merging, empty/nil handling, and endpoint-only backward compat - Update docs/templating/context.md to describe the EnvVar shape with valueFrom Signed-off-by: Miraj Abeysekara <[email protected]>
Signed-off-by: Miraj Abeysekara <[email protected]>
- Add ConditionResourceDependenciesReady plus three reasons (AllResourceDependenciesReady, ResourceDependenciesPending, NoResourceDependencies) mirroring the endpoint-side ConnectionsResolved precedent - Add IndexKeyResourceReleaseBindingOwnerEnv composite index over (project, resource, environment) on ResourceReleaseBinding, with MakeResourceReleaseBindingOwnerEnvKey and IndexResourceReleaseBindingOwnerEnv helpers, registered in SetupSharedIndexes - Add pure helpers buildResourceDependencyTargets, allResourceDependenciesResolved, setResourceDependenciesCondition for the resolver and condition wiring that will land next; mirrors controller_connections.go shape - 17 unit tests covering target derivation, resolved-gate semantics, condition branches, key-builder, and the round-trip invariant that the consumer-side target key matches what the index extracts from a provider RRB; empty-field guards on the index function Signed-off-by: Miraj Abeysekara <[email protected]>
- resolveResourceDependency does the per-dep API hop via the (project, resource, environment) field index, gates on the provider's Ready condition using the typed resourcereleasebinding.ConditionReady, and dispatches outputs through BuildResourceDependencyItem to produce a ResourceDependencyItem - resolveResourceDependencies orchestrates per-dep and aborts on the first transient API error so the caller requeues without acting on a partial view - Failure modes surface as PendingResourceDependency entries with descriptive Reason: provider missing, multiple providers (defensive), provider not Ready, ErrOutputNotResolved, ErrInvalidFileBinding - 9 fake-client subtests across TestResolveResourceDependency and TestResolveResourceDependencies cover each failure path including an interceptor-injected list error Signed-off-by: Miraj Abeysekara <[email protected]>
…ator - Extend setReadyCondition to find ConditionResourceDependenciesReady, treat it as optional (absent = pass, mirroring ConnectionsResolved), and fold it into the all-true gate - Priority order when multiple sub-conditions are False: ConnectionsResolved -> ResourceDependenciesReady -> ResourcesReady; locked by two priority tests so a future reorder is caught - 5 TestSetReadyConditionWithResourceDependencies subtests cover: deps False blocks Ready with its reason, deps True yields Ready, deps absent does not block (backward compat), connections wins over resource deps, resource deps wins over resources ready Signed-off-by: Miraj Abeysekara <[email protected]>
…ncile - Resolve resource dependencies after the existing connection resolution: populate Status.ResourceDependencyTargets and Status.PendingResourceDependencies, thread the resolved items into RenderInput.ResourceDependencyItems for the pipeline - Add a resource-dep stability guard mirroring the connections guard: when any provider ResourceReleaseBinding is missing, not Ready, or has not yet populated a referenced output, set ConditionResourceDependenciesReady=False and return early without marking ReleaseSynced=True Signed-off-by: Miraj Abeysekara <[email protected]>
- Add resourceDependencyTargetsIndex over ReleaseBinding.Status.ResourceDependencyTargets so consumer enqueue is O(1) by (namespace, project, resourceName, environment) key
- Add resourceReleaseBindingOutputsChangedPredicate that fires only on Status.Outputs changes and Ready condition flips; Synced and other status churn are filtered out so consumers are not re-enqueued for events they don't care about
- Add findConsumerReleaseBindingsForResourceReleaseBinding mapper with an empty-Owner guard mirroring IndexResourceReleaseBindingOwnerEnv
- Wire Watches(&ResourceReleaseBinding{}, ...) into SetupWithManager with a comment documenting why the two consumer-side watches don't form a cycle
- 19 subtests covering key builder, indexer (dedup, empty handling), mapper (positive, no consumers, wrong type, malformed RRB), predicate branches, and a round-trip test that locks consumer-side buildResourceDependencyTargets against the mapper's key construction
Signed-off-by: Miraj Abeysekara <[email protected]>
- Add EnvVarEntry, EnvVarSourceEntry, KeyRef typed structs that mirror corev1.EnvVar/EnvVarSource/KeyRef for the subset of shapes the platform emits; JSON-compatible so the rendered Pod spec is unchanged
- Switch ResourceDependencyItem, ConnectionItem, ConnectionsContextData fields and buildEnvVarsForConnection from corev1 types to the local EnvVarEntry / VolumeEntry / VolumeMountEntry. configurations.toX() and dependencies.toX() now return the same CEL element type so the canonical CCT shape ${configurations.toX() + dependencies.toX()} type-checks in the validation environment
- Add omitempty to VolumeMountEntry.SubPath so JSON serialization matches corev1.VolumeMount
- Lock the concat invariant with TestBuildComponentCELEnv_ConfigurationsDependenciesConcat covering toVolumes, toContainerVolumeMounts, and toContainerEnvs self-concat
Signed-off-by: Miraj Abeysekara <[email protected]>
- Extract populateResourceDependencyStatus helper to consolidate the three-step status-write dance for resource deps; keeps reconcileRelease readable. Add nolint:gocyclo on reconcileRelease (matches precedent on setResourcesReadyStatus and workflowrun's reconcile) since the chain is at threshold + 1 and the complexity is structural
- Drop unparam fields from newProviderRRB test helper; project always "proj" and environment always "dev" across all call sites, so hardcode them in the helper
- Switch mergedVolumeMounts and mergedVolumes in newDependenciesContextData from []T{} to make([]T, 0, len(data.Resources)) for prealloc
Signed-off-by: Miraj Abeysekara <[email protected]>
Ginkgo specs running against envtest go in _integration_test.go per the convention used by 13 other controllers (Pattern B). Pure rename, no content change. Signed-off-by: Miraj Abeysekara <[email protected]>
- Add manager-backed cached client to the suite via ctrl.NewManager + controller.SetupSharedIndexes; covers the 6 types the shared indexes span. Direct k8sClient stays for existing read-after-write specs; k8sCachedClient is for the new field-index resolver-path specs. - Add gate-engages spec: workload with dependencies.resources but no matching provider RRB engages the gate, populates Pending status, blocks ReleaseSynced. - Add end-to-end env-var spec: provider RRB Ready with a value-kind output threads through to the rendered Deployment's container.env. - Add concat-volumes coexistence spec: container.files + dependencies.fileBindings render side-by-side under configurations.toX() + dependencies.toX(); 3 volumes with disjoint file-mount-* and r-* prefixes; locks JSON-compat between the typed structs shared by both CEL helpers. Signed-off-by: Miraj Abeysekara <[email protected]>
- Update getting-started CCTs (service, webapp, worker, scheduled-task) and the inline copies in all.yaml to render ${configurations.toX() + dependencies.toX()} for volumes and volumeMounts
- Update the two component-types samples (component-with-configs, component-with-embedded-traits) for consistency
- Without this, fileBindings on a resource dependency produces a Pod whose volumeMounts reference volume names absent from volumes
- No-op for samples without resource deps; the empty concat elides cleanly from the rendered manifest
- Live-validated on k3d-openchoreo: 3-volume Pod with mounts readable in the container, and url-shortener sample (4 components with endpoint deps) renders unchanged
Signed-off-by: Miraj Abeysekara <[email protected]>
Signed-off-by: Miraj Abeysekara <[email protected]>
Signed-off-by: Miraj Abeysekara <[email protected]>
Signed-off-by: Miraj Abeysekara <[email protected]>
- Add CEL XValidation on Workload.spec.dependencies.resources[].envBindings and .fileBindings rejecting empty map values at admission - Regenerate CRDs (config/crd/bases + helm-shipped copies); embedded workload spec on ComponentRelease picks up the validation as expected Signed-off-by: Miraj Abeysekara <[email protected]>
- isResourceReleaseBindingReady now requires cond.ObservedGeneration == rrb.Generation, so a stale Ready from a prior reconcile no longer gates the consumer's resolver while the provider is mid-reconcile - Add TestIsResourceReleaseBindingReady covering ready+current, ready+stale, not-ready, and missing-condition Signed-off-by: Miraj Abeysekara <[email protected]>
- Fix TraitContext dataplane subsection: drop the non-existent publicVirtualHost field and reference the ComponentContext dataplane shape, consistent with sibling subsections - Expand ComponentContext dependencies: document the resources per-item view plus the merged volumeMounts and volumes lists; add ResourceDependencyItem, VolumeMount, and Volume tables; show valueFrom in the env-projection example; add a combined configurations + dependencies example - Add helpers dependencies.toContainerVolumeMounts() and dependencies.toVolumes() to the helper-methods table - Mirror the new shape in the TraitContext dependencies subsection Signed-off-by: Miraj Abeysekara <[email protected]>
The XValidation rule on envBindings/fileBindings was rejected by kube-apiserver during CRD install: estimated rule cost exceeded the per-rule budget by 15.7x. Without an iteration cap, the cost estimator assumes worst-case map size. - Add MaxProperties=50 to both maps (matches the existing MaxItems=50 cap on Resources []WorkloadResourceDependency) - Tighten the XValidation rule to also reject empty map keys (output names), not just empty values. Empty keys would silently produce ambiguous bindings downstream. The combined rule doubles per-iteration work but stays well under budget with the new bound - Regenerate CRDs (config/crd/bases + helm-shipped copies); the bound and rule flow through to the embedded workload spec on ComponentRelease as well Signed-off-by: Miraj Abeysekara <[email protected]>
The previous predicate fired only on Status.Outputs change or Ready.Status flip. With isResourceReleaseBindingReady now requiring cond.ObservedGeneration == rrb.Generation, two new transitions need to re-enqueue consumers: - A spec edit that bumps metadata.generation (Ready stays True but ObservedGeneration becomes stale → consumers gate off) - A controller catch-up that advances Ready.ObservedGeneration without flipping Status (consumers were gated off and must re-evaluate) Without these, consumers can stay ReleaseSynced=True against stale provider state until some unrelated event triggers reconcile. - Add Generation comparison to the predicate's UpdateFunc - Rename readyConditionStatusChanged to readyConditionChanged and extend it to compare ObservedGeneration as well - Add fires_on_generation_change and fires_on_ready_observed_generation_change subtests Signed-off-by: Miraj Abeysekara <[email protected]>
isResourceReleaseBindingReady now requires the Ready condition's ObservedGeneration to match rrb.Generation. envtest assigns Generation=1 on Create, but the integration fixtures left ObservedGeneration at 0, so the consumer resolver treated providers as not-ready and ReleaseSynced=True assertions timed out. Signed-off-by: Miraj Abeysekara <[email protected]>
The example heading promised configurations + dependencies, but the body only injected dependency env vars. Configuration-derived envs land via envFrom (configMapRef + secretRef), so the projection needs both env (deps) and envFrom (configurations) to match the heading. Signed-off-by: Miraj Abeysekara <[email protected]>
Purpose
Wire workloads to consume managed-resource outputs declared via
dependencies.resources[]. Builds on the resource abstraction CRDs from #3392 and mirrors the existing endpoint-dependency pattern (dependencies.endpoints[].envBindings).Approach
API
WorkloadResourceDependencyonWorkload.spec.dependencies.resources[]withref,envBindings, andfileBindingsResourceDependencyTargetandPendingResourceDependencyonReleaseBindingStatusfor reverse-watch and diagnosticsPipeline (renderer)
value:becomesenv.value;secretKeyRef:/configMapKeyRef:becomeenv.valueFrom.*fileBindingssynthesize one volume + mount per(ref, sourceKind, sourceName), deduped, with deterministic FNV-1a names under anr-prefix disjoint from the configurations side'sfile-mount-prefix${dependencies.toContainerVolumeMounts()}and${dependencies.toVolumes()}${configurations.toX() + dependencies.toX()}is type-compatible in CELController (releasebinding)
IndexKeyResourceReleaseBindingOwnerEnv. Resolver hops per dep, gates on the provider's typedConditionReady, threads outputs through the rendererConditionResourceDependenciesReadyfolded intoReadywith priorityConnectionsResolved > ResourceDependenciesReady > ResourcesReadyResourceReleaseBindingvia a target index plus an outputs-changed predicate. Stability guard blocksReleaseSynced=Truewhile any dep is pendingSamples
component-with-*samples render${configurations.toX() + dependencies.toX()}for volumes and volumeMounts so workloads with both configurations and resource dependencies produce a valid PodRelated Issues
Closes #3334
Related #3107
Checklist
backport/<release-branch>label if this should be backported (e.g.,backport/release-v1.0)