Skip to content

feat(pluginpresets): validate PluginDefinition existence in PluginPreset reconciler#1862

Open
IvoGoman wants to merge 1 commit intomainfrom
feat/pluginpreset-definition-validation
Open

feat(pluginpresets): validate PluginDefinition existence in PluginPreset reconciler#1862
IvoGoman wants to merge 1 commit intomainfrom
feat/pluginpreset-definition-validation

Conversation

@IvoGoman
Copy link
Copy Markdown
Contributor

@IvoGoman IvoGoman commented Mar 19, 2026

Description

Ensures e.g. changes bringing new PluginDefinition via Catalog does not break a HelmRelease if a PluginPreset using it comes with the same change

Removes strict existence validation of referenced (Cluster-)PluginDefinition and values in PluginPreset webhook. Webhook only does structural validation of PluginOptionValues & ClusterOptionOverrides.

Add early validation in PluginPreset reconciliation to check whether the
referenced PluginDefinition or ClusterPluginDefinition exists before
proceeding with plugin creation. When the definition is missing, the
ReadyCondition is set to false with an appropriate reason and the
reconciler requeues after 10 seconds.

  • Add PluginDefinitionNotExistsReason and ClusterPluginDefinitionNotExistsReason constants
  • Add pluginDefinitionExists helper method supporting both definition kinds
  • Preserve the not-exists ReadyCondition in computeReadyCondition
  • Clear stale not-exists condition once the definition becomes available

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI
  • 📦 Chore (Release)
  • ⏩ Revert

Related Tickets & Documents

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help
  • Separate ticket for tests # (issue/pr)

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

Added to documentation?

  • 📜 README.md
  • 🤝 Documentation pages updated
  • 🙅 no documentation needed
  • (if applicable) generated OpenAPI docs for CRD changes

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

…set reconciler

Ensures e.g. changes bringing new PluginDefinition via Catalog does not break a HelmRelease if a PluginPreset using it comes with the same change

Removes strict existince validation of referenced (Cluster-)PluginDefinition and values in PluginPreset webhook. Webhook only does structural validation of PluginOptionValues & ClusterOptionOverrides.

Add early validation in PluginPreset reconciliation to check whether the
referenced PluginDefinition or ClusterPluginDefinition exists before
proceeding with plugin creation. When the definition is missing, the
ReadyCondition is set to false with an appropriate reason and the
reconciler requeues after 10 seconds.

- Add PluginDefinitionNotExistsReason and ClusterPluginDefinitionNotExistsReason constants
- Add pluginDefinitionExists helper method supporting both definition kinds
- Preserve the not-exists ReadyCondition in computeReadyCondition
- Clear stale not-exists condition once the definition becomes available
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR shifts (Cluster-)PluginDefinition existence validation from the PluginPreset admission webhook to the PluginPreset controller reconciliation path to avoid admission-time failures when presets and definitions are introduced together (e.g., via Catalog).

Changes:

  • Relax PluginPreset webhook validation to structural checks for option values (value/valueFrom/expression) instead of requiring referenced PluginDefinitions to exist at admission time.
  • Add early reconciliation-time validation for referenced PluginDefinition/ClusterPluginDefinition existence, setting ReadyCondition=false with a specific reason and requeueing.
  • Add/update unit and controller tests to cover the new admission and reconciliation behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/webhook/v1alpha1/pluginpreset_webhook_test.go Updates admission tests to accept missing definitions; refactors option-value validation tests to match new structural-only webhook behavior.
internal/webhook/v1alpha1/pluginpreset_webhook.go Removes admission-time existence/type validation against PluginDefinitions; adds structural validation helper for option value sources.
internal/controller/plugin/pluginpreset_controller.go Adds reconciliation-time existence checks for referenced definitions; preserves/clears “definition missing” ReadyCondition reasons.
internal/controller/plugin/pluginpreset_controller_test.go Adds controller test verifying ReadyCondition behavior when a referenced ClusterPluginDefinition is initially missing then created.
api/v1alpha1/pluginpreset_types.go Introduces new ReadyCondition reason constants for missing PluginDefinition/ClusterPluginDefinition.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Entry("PluginOption ValueFrom is missing SecretReference Name", &greenhousev1alpha1.PluginValueFromSource{Secret: &greenhousev1alpha1.SecretKeyReference{Key: "key"}}, true),
Entry("PluginOption ValueFrom is missing SecretReference Key", &greenhousev1alpha1.PluginValueFromSource{Secret: &greenhousev1alpha1.SecretKeyReference{Name: "secret"}}, true),
Entry("PluginOption ValueFrom does not contain a SecretReference", nil, true),
Entry("PluginOption ValueFrom without a SecretReference is valid", &greenhousev1alpha1.PluginValueFromSource{}, false),
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

The test case treats valueFrom: {} as valid, but PluginValueFromSource has CRD XValidations requiring exactly one of secret or ref to be set. This should be an error (or the test should provide a valid ref/secret). Otherwise this test will diverge from (and fail under) API server validation.

Suggested change
Entry("PluginOption ValueFrom without a SecretReference is valid", &greenhousev1alpha1.PluginValueFromSource{}, false),
Entry("PluginOption ValueFrom without a SecretReference is invalid", &greenhousev1alpha1.PluginValueFromSource{}, true),

Copilot uses AI. Check for mistakes.
Entry("PluginOption ValueFrom is missing SecretReference Name", &greenhousev1alpha1.PluginValueFromSource{Secret: &greenhousev1alpha1.SecretKeyReference{Key: "key"}}, true),
Entry("PluginOption ValueFrom is missing SecretReference Key", &greenhousev1alpha1.PluginValueFromSource{Secret: &greenhousev1alpha1.SecretKeyReference{Name: "secret"}}, true),
Entry("PluginOption ValueFrom does not contain a SecretReference", nil, true),
Entry("PluginOption ValueFrom without a SecretReference is valid", &greenhousev1alpha1.PluginValueFromSource{}, false),
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

Same issue as above: PluginValueFromSource{} violates the CRD XValidation rules (must set exactly one of secret or ref). This entry should expect an error or be changed to include a valid ref/secret.

Suggested change
Entry("PluginOption ValueFrom without a SecretReference is valid", &greenhousev1alpha1.PluginValueFromSource{}, false),
Entry("PluginOption ValueFrom without a SecretReference is invalid", &greenhousev1alpha1.PluginValueFromSource{}, true),

Copilot uses AI. Check for mistakes.
Comment on lines +204 to +206
// Validate that ValueFrom secret references are well-formed if a secret is referenced.
if val.ValueFrom != nil && val.ValueFrom.Secret != nil {
if val.ValueFrom.Secret.Name == "" {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

validateOptionValuesStructure currently allows ValueFrom to be set with neither secret nor ref (it only validates secret when present). The API type PluginValueFromSource enforces “exactly one of secret or ref” via XValidation, so this helper should mirror that to keep webhook/unit-test validation consistent and to provide clearer field errors.

Copilot uses AI. Check for mistakes.
// Check if the referenced PluginDefinition exists. If not, set ReadyCondition to false and return early.
pluginDefExists, err := r.pluginDefinitionExists(ctx, pluginPreset)
if err != nil {
return ctrl.Result{}, lifecycle.Failed, err
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

If pluginDefinitionExists returns an error, EnsureCreated returns early without setting any condition that computeReadyCondition uses to mark the resource not-ready; the status update can end up reporting Ready=true despite a failed reconcile. Consider setting an explicit ReadyCondition/other condition for lookup failures (and preserving it in computeReadyCondition) or handling the error by setting Ready=false and requeueing without returning an error.

Suggested change
return ctrl.Result{}, lifecycle.Failed, err
pluginPreset.SetCondition(greenhousemetav1alpha1.FalseCondition(
greenhousemetav1alpha1.ReadyCondition,
"PluginDefinitionLookupFailed",
fmt.Sprintf("Failed to look up %s %q: %v",
pluginPreset.Spec.Plugin.PluginDefinitionRef.Kind,
pluginPreset.Spec.Plugin.PluginDefinitionRef.Name,
err,
),
))
return ctrl.Result{RequeueAfter: 10 * time.Second}, lifecycle.Success, nil

Copilot uses AI. Check for mistakes.
}
pluginPreset.SetCondition(greenhousemetav1alpha1.FalseCondition(greenhousemetav1alpha1.ReadyCondition, reason,
fmt.Sprintf("The referenced %s %q does not exist", pluginPreset.Spec.Plugin.PluginDefinitionRef.Kind, pluginPreset.Spec.Plugin.PluginDefinitionRef.Name)))
return ctrl.Result{RequeueAfter: 10 * time.Second}, lifecycle.Success, nil
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

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

Returning RequeueAfter: 10s on missing PluginDefinition will cause periodic reconciles for as long as the definition is absent. Since this controller already watches PluginDefinition/ClusterPluginDefinition and enqueues PluginPresets via the corresponding labels, consider relying on those watch events (or using a much longer/backoff requeue) to reduce steady-state load when a definition is missing long-term.

Suggested change
return ctrl.Result{RequeueAfter: 10 * time.Second}, lifecycle.Success, nil
// Do not requeue periodically; rely on PluginDefinition/ClusterPluginDefinition watch events to trigger reconciliation.
return ctrl.Result{}, lifecycle.Success, nil

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] - Move existence check of (Cluster-)PluginDefinition for PluginPreset to reconciliation

2 participants