Skip to content

OCPCLOUD-3384, OCPCLOUD-3365: Implement admission action Warn and ensure validations not active when not configured#492

Open
JoelSpeed wants to merge 6 commits intoopenshift:mainfrom
openshift-cloud-team:compatibility-admit-warn
Open

OCPCLOUD-3384, OCPCLOUD-3365: Implement admission action Warn and ensure validations not active when not configured#492
JoelSpeed wants to merge 6 commits intoopenshift:mainfrom
openshift-cloud-team:compatibility-admit-warn

Conversation

@JoelSpeed
Copy link
Contributor

@JoelSpeed JoelSpeed commented Mar 9, 2026

This ensures that users configuring admission to warn do not have pruning or rejected writes for object admission

Also ensures that VWC/MWCs are removed when object admission is not configured

Summary by CodeRabbit

  • New Features

    • Warning mode for object schema validation: validations can emit warnings instead of denying requests.
  • Bug Fixes

    • Webhook ownership and lifecycle tightened to avoid overwriting or deleting webhooks not owned by the requirement.
    • Pruning/validation behavior adjusted to respect warning mode and ownership checks.
  • Chores

    • Bumped Go toolchain to 1.25.0 and updated Kubernetes, OpenShift, and etcd dependencies.
    • CI/Docker builder images updated.

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: e8abc187-fc03-4e52-8fd6-a1993c22a25b

📥 Commits

Reviewing files that changed from the base of the PR and between 4478e45 and fa03d1b.

📒 Files selected for processing (2)
  • .ci-operator.yaml
  • Dockerfile.rhel

Walkthrough

Upgrades Go and Kubernetes module versions; refactors webhook logic to pass CompatibilityRequirement objects into schema/strategy resolvers; adds admit-action handling (WARN/DENY) for validation and pruning, ownership checks for webhook configs, new test warning-mode coverage, and test utilities for capturing REST warnings.

Changes

Cohort / File(s) Summary
Dependency updates
e2e/go.mod, go.mod, hack/tools/go.mod, manifests-gen/go.mod
Bumped Go 1.24→1.25 and upgraded Kubernetes/OpenShift-related modules (k8s.io/* v0.34.1→v0.35.2, etcd v3.6.4→v3.6.5, openshift/api pseudo-version updates).
Object pruning webhook
pkg/controllers/crdcompatibility/objectpruning/webhook.go, pkg/controllers/crdcompatibility/objectpruning/validator_unit_test.go, pkg/controllers/crdcompatibility/objectpruning/handle_test.go
Changed getStructuralSchema to accept a CompatibilityRequirement object; handleObjectPruning fetches the CR and early-returns for CRDAdmitActionWarn; added OwnerReference to mutating webhook config; tests updated to exercise warn-mode and async retrievals.
Object validation webhook
pkg/controllers/crdcompatibility/objectvalidation/webhook.go, pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go, pkg/controllers/crdcompatibility/objectvalidation/handle_test.go, pkg/controllers/crdcompatibility/objectvalidation/suite_test.go
Changed getValidationStrategy to accept CompatibilityRequirement object; ValidateCreate/ValidateUpdate fetch CR, apply action-based logic (WARN → emit warnings, DENY → reject); added errorToString helper and OwnerReference on validating webhook config; extensive tests added for warning-mode; metrics server configured in tests.
Reconciliation & ownership
pkg/controllers/crdcompatibility/reconcile.go, pkg/controllers/crdcompatibility/reconcile_test.go
Added ownership checks for webhook configs with sentinel error errWebhookConfigNotControlledByCompatibilityRequirement; changed enablement logic to treat non-empty config as enabled and to remove when disabled; guard removals to avoid deleting non-owned webhooks; tests extended to cover webhook presence/absence based on ObjectSchemaValidation.
Test utilities & builders
pkg/test/crdbuilder.go, pkg/test/warninghandler.go
Added ObjectSchemaValidation to test CompatibilityRequirement builder (default DENY) and introduced Test WarningHandler to capture REST warning headers (NewTestWarningHandler, Messages()).
CI / Images
.ci-operator.yaml, Dockerfile.rhel
Updated build root / builder base image tags to use golang-1.25 variants.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant WebhookServer
    participant Controller as Reconciler
    participant API as Kubernetes API
    participant CR as CompatibilityRequirement
    participant Validator

    Client->>WebhookServer: Admission request (create/update/prune)
    WebhookServer->>API: Get CR (CompatibilityRequirement)
    API-->>WebhookServer: Return CR
    WebhookServer->>Validator: getValidationStrategy(cr, version) / getStructuralSchema(cr, version)
    Validator-->>WebhookServer: strategy + field errors
    alt Action == DENY
        WebhookServer->>Client: Reject (apiInvalid)
    else Action == WARN
        WebhookServer->>Client: Allow + Warnings
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Stable And Deterministic Test Names ❌ Error Test step descriptions in pkg/controllers/crdcompatibility/reconcile_test.go contain dynamic values (requirement.Name, testCRDClean.Name, obj.GetName()) via string concatenation and fmt.Sprintf, violating stable test naming requirements. Replace dynamic values in By() statements with static descriptive text and move object name assertions into test body logic instead of test descriptions.
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning New Ginkgo tests bundle multiple behaviors into single entries, violating single-responsibility, and lack descriptive failure messages on assertions. Refactor scenarios into separate It blocks, add contextual messages to Expect calls, and specify explicit timeouts on all Eventually/Consistently calls.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: implementing Warn admission action and ensuring validations are inactive when not configured, directly aligned with the substantial refactoring and feature additions throughout the codebase.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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

@openshift-ci-robot
Copy link

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 9, 2026
@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 9, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Mar 9, 2026

@JoelSpeed: This pull request references OCPCLOUD-3384 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3365 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

Details

In response to this:

Currently stacked on #464

This ensures that users configuring admission to warn do not have pruning or rejected writes for object admission

Also ensures that VWC/MWCs are removed when object admission is not configured

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 9, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 9, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign radekmanak for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@JoelSpeed JoelSpeed force-pushed the compatibility-admit-warn branch from 18f2100 to 4478e45 Compare March 13, 2026 11:20
@JoelSpeed JoelSpeed marked this pull request as ready for review March 13, 2026 11:21
@openshift-ci-robot
Copy link

openshift-ci-robot commented Mar 13, 2026

@JoelSpeed: This pull request references OCPCLOUD-3384 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3365 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

Details

In response to this:

This ensures that users configuring admission to warn do not have pruning or rejected writes for object admission

Also ensures that VWC/MWCs are removed when object admission is not configured

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci openshift-ci bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Mar 13, 2026
@openshift-ci openshift-ci bot requested review from mdbooth and nrb March 13, 2026 11:23
@openshift-ci-robot
Copy link

openshift-ci-robot commented Mar 13, 2026

@JoelSpeed: This pull request references OCPCLOUD-3384 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3365 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

Details

In response to this:

This ensures that users configuring admission to warn do not have pruning or rejected writes for object admission

Also ensures that VWC/MWCs are removed when object admission is not configured

Summary by CodeRabbit

  • New Features

  • Added warning mode for object schema validation, allowing validation enforcement via warnings instead of denial.

  • Bug Fixes

  • Webhook configurations now properly owned by CompatibilityRequirements to prevent accidental overwrites.

  • Enhanced object pruning and validation to respect webhook ownership constraints.

  • Chores

  • Updated Go toolchain to 1.25.0.

  • Upgraded Kubernetes API dependencies to v0.35.2.

  • Updated OpenShift API and etcd dependencies.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Copy link

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/controllers/crdcompatibility/reconcile.go (1)

232-249: ⚠️ Potential issue | 🔴 Critical

Prevent deletion of non-owned ValidatingWebhookConfiguration objects.

At Line 247, deletion is unconditional once the object is found. Combined with Line 202-205 (disable path), this can remove a same-named webhook not controlled by this CompatibilityRequirement.

🔒 Proposed fix
 func (r *reconcileState) removeObjectValidationWebhook(ctx context.Context, obj *apiextensionsv1alpha1.CompatibilityRequirement) error {
 	webhookConfig := &admissionregistrationv1.ValidatingWebhookConfiguration{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: obj.Name,
 		},
 	}
@@
 	if err := r.client.Get(ctx, types.NamespacedName{Name: webhookConfig.Name}, webhookConfig); err != nil {
 		if apierrors.IsNotFound(err) {
 			return nil
 		}

 		return fmt.Errorf("failed to get ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
 	}
+
+	// If we don't own the webhook config, we should not be deleting it.
+	if !metav1.IsControlledBy(webhookConfig, obj) {
+		return nil
+	}

 	if err := r.client.Delete(ctx, webhookConfig); err != nil {
 		return fmt.Errorf("failed to delete ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/reconcile.go` around lines 232 - 249, The
deletion is unconditional in removeObjectValidationWebhook which can remove a
same-named ValidatingWebhookConfiguration not owned by this
CompatibilityRequirement; before calling r.client.Delete check ownership (use
metav1.IsControlledBy or controllerutil.ContainsReference/IsOwned helper) to
ensure webhookConfig is owned/controlled by the obj (CompatibilityRequirement)
and only delete if true—if not owned, skip deletion and return nil (or a clear
no-op) to avoid deleting resources owned by others.
🧹 Nitpick comments (2)
go.mod (1)

19-29: Align require versions with replace pins to reduce dependency drift.

The replace directives pin Kubernetes modules to v0.35.2/v1.35.2, but several require entries lag behind:

  • Line 55: k8s.io/apiextensions-apiserver v0.34.1 → should be v0.35.2
  • Line 61: k8s.io/kubernetes v1.34.1 → should be v1.35.2
  • Line 351: k8s.io/controller-manager v0.32.1 (indirect) → should be v0.35.2
  • Line 354: k8s.io/kubelet v0.34.1 (indirect) → should be v0.35.2

This drift may cause future go mod tidy churn and makes the intended dependency versions less clear.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@go.mod` around lines 19 - 29, Update the go.mod require entries so they match
the pinned versions in the replace directives: change
k8s.io/apiextensions-apiserver to v0.35.2, k8s.io/kubernetes to v1.35.2, and
ensure indirect requires for k8s.io/controller-manager and k8s.io/kubelet are
v0.35.2; run go get <module>@<version> or edit the require block to the listed
versions and then run go mod tidy to verify there is no drift between require
and replace pins.
pkg/controllers/crdcompatibility/reconcile_test.go (1)

381-382: Use the typed NotFound matcher here.

Matching "not found" couples the test to error text and can pass for the wrong failure. test.BeK8SNotFound() is already used in the deletion specs above and keeps these assertions tied to the actual API status.

♻️ Suggested cleanup
-			Expect(kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)()).To(MatchError(ContainSubstring("not found")))
-			Expect(kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)()).To(MatchError(ContainSubstring("not found")))
+			Expect(kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)()).To(test.BeK8SNotFound())
+			Expect(kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)()).To(test.BeK8SNotFound())-			Eventually(kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)).WithContext(ctx).Should(MatchError(ContainSubstring("not found")))
-			Eventually(kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)).WithContext(ctx).Should(MatchError(ContainSubstring("not found")))
+			Eventually(kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)).WithContext(ctx).Should(test.BeK8SNotFound())
+			Eventually(kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)).WithContext(ctx).Should(test.BeK8SNotFound())

Also applies to: 399-400

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/reconcile_test.go` around lines 381 - 382,
Replace the fragile string-based error assertions with the typed NotFound
matcher: change the two expectations using MatchError(ContainSubstring("not
found")) for kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)() and
kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)() to use test.BeK8SNotFound()
instead; also make the same replacement for the identical assertions noted
elsewhere in the file (the other occurrence around the deletion specs) so the
tests assert Kubernetes NotFound status rather than matching error text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/controllers/crdcompatibility/objectpruning/webhook.go`:
- Around line 119-125: The handler currently only returns early when
ObjectSchemaValidation.Action == CRDAdmitActionWarn, but when
ObjectSchemaValidation is nil the zero-value falls through and causes pruning;
update the early-exit logic in the webhook handler (around
compatibilityRequirement.Spec.ObjectSchemaValidation.Action and the call to
v.getStructuralSchema) to treat unset/cleared validation as disabled by
returning nil unless the action is explicitly
apiextensionsv1alpha1.CRDAdmitActionDeny — i.e., check if
Spec.ObjectSchemaValidation is nil or its Action != CRDAdmitActionDeny and
return nil to keep the admission inert during reconciliation windows.

In `@pkg/controllers/crdcompatibility/objectvalidation/handle_test.go`:
- Around line 853-860: The BeforeEach mutates the package-global cl and relies
on the implicit global scheme; instead create an explicit scheme and avoid
changing global state: build a fresh scheme (e.g. runtime.NewScheme()) and
explicitly register the needed API types (the same apiextensionsv1alpha1 types
registered in pkg/test/envtest.go) and pass that scheme into client.New via a
copied rest.Config (warningConfig), then create a local client variable (e.g.
testCl) from client.New rather than assigning to the package-global cl; if other
helpers require the global, save the oldCl before creating testCl and restore
oldCl in AfterEach (and ensure any client cleanup runs even on panic) so tests
remain isolated.

In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 314-317: The isObjectValidationWebhookEnabled function currently
treats a non-nil MatchConditions slice as enabled even if it's empty; change the
check from osl.MatchConditions != nil to len(osv.MatchConditions) > 0 so an
explicitly empty list is considered not configured, i.e., update the return
expression in isObjectValidationWebhookEnabled to use len(osv.MatchConditions) >
0 (keeping the existing checks for osv.Action and the namespace/object selector
helpers).

---

Outside diff comments:
In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 232-249: The deletion is unconditional in
removeObjectValidationWebhook which can remove a same-named
ValidatingWebhookConfiguration not owned by this CompatibilityRequirement;
before calling r.client.Delete check ownership (use metav1.IsControlledBy or
controllerutil.ContainsReference/IsOwned helper) to ensure webhookConfig is
owned/controlled by the obj (CompatibilityRequirement) and only delete if
true—if not owned, skip deletion and return nil (or a clear no-op) to avoid
deleting resources owned by others.

---

Nitpick comments:
In `@go.mod`:
- Around line 19-29: Update the go.mod require entries so they match the pinned
versions in the replace directives: change k8s.io/apiextensions-apiserver to
v0.35.2, k8s.io/kubernetes to v1.35.2, and ensure indirect requires for
k8s.io/controller-manager and k8s.io/kubelet are v0.35.2; run go get
<module>@<version> or edit the require block to the listed versions and then run
go mod tidy to verify there is no drift between require and replace pins.

In `@pkg/controllers/crdcompatibility/reconcile_test.go`:
- Around line 381-382: Replace the fragile string-based error assertions with
the typed NotFound matcher: change the two expectations using
MatchError(ContainSubstring("not found")) for
kWithCtx(ctx).Get(noObjectSchemaValidatingWebhook)() and
kWithCtx(ctx).Get(noObjectSchemaMutatingWebhook)() to use test.BeK8SNotFound()
instead; also make the same replacement for the identical assertions noted
elsewhere in the file (the other occurrence around the deletion specs) so the
tests assert Kubernetes NotFound status rather than matching error text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 01a51f56-b065-4956-b2f5-83eb8e268ea7

📥 Commits

Reviewing files that changed from the base of the PR and between 98025f0 and 4478e45.

⛔ Files ignored due to path filters (285)
  • e2e/go.sum is excluded by !**/*.sum
  • go.sum is excluded by !**/*.sum
  • go.work is excluded by !**/*.work
  • manifests-gen/go.sum is excluded by !**/*.sum
  • vendor/github.com/gogo/protobuf/sortkeys/sortkeys.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/.ci-operator.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/AGENTS.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/Dockerfile.ocp is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apiextensions/v1alpha1/types_compatibilityrequirement.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apps/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/apps/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/authorization/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/authorization/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/build/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/build/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/cloudnetwork/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/cloudnetwork/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_config-operator_01_networks.crd.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1/zz_generated.crd-manifests/0000_10_openshift-controller-manager_01_builds.crd.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha1/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha1/types_pki.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.featuregated-crd-manifests.yaml is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/config/v1alpha1/zz_generated.swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/features.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/features/features.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/image/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/image/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/network/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/network/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/networkoperator/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/networkoperator/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/oauth/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/oauth/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/openapi/generated_openapi/zz_generated.openapi.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/github.com/openshift/api/operator/v1/zz_generated.crd-manifests/0000_50_ingress_00_ingresscontrollers.crd.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/operator/v1/zz_generated.crd-manifests/0000_70_dns_00_dnses.crd.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/project/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/project/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/quota/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/route/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/route/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/samples/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/samples/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/security/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/template/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/template/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/user/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/openshift/api/user/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/.travis.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/chain.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/constantdelay.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/cron.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/logger.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/option.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/parser.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/robfig/cron/v3/spec.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/version/version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/retry_interceptor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/admission/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admission/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/admissionregistration/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/admissionregistration/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1alpha1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1alpha1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1alpha1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/admissionregistration/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/admissionregistration/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apidiscovery/v2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apidiscovery/v2beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apidiscovery/v2beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apiserverinternal/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apiserverinternal/v1alpha1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apiserverinternal/v1alpha1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apiserverinternal/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apps/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apps/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/apps/v1beta2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/apps/v1beta2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/authentication/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/authentication/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1alpha1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1alpha1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/authentication/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authentication/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/authorization/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/authorization/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/authorization/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/autoscaling/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/autoscaling/v2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/autoscaling/v2beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/autoscaling/v2beta2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/autoscaling/v2beta2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/batch/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/batch/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/batch/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1alpha1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1alpha1/zz_generated.prerelease-lifecycle.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/certificates/v1beta1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/certificates/v1beta1/zz_generated.prerelease-lifecycle.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/coordination/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/coordination/v1alpha2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1alpha2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1alpha2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1alpha2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/coordination/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/coordination/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/core/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/toleration.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/core/v1/zz_generated.deepcopy.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/core/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/discovery/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/discovery/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/discovery/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/events/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/events/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/events/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/extensions/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/generated.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/types_swagger_doc_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/extensions/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/extensions/v1beta1/zz_generated.validations.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/flowcontrol/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/flowcontrol/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/flowcontrol/v1beta2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta2/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta2/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta2/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/flowcontrol/v1beta3/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta3/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta3/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/flowcontrol/v1beta3/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/imagepolicy/v1alpha1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/imagepolicy/v1alpha1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/networking/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/networking/v1beta1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1beta1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1beta1/generated.protomessage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/networking/v1beta1/zz_generated.model_name.go is excluded by !**/vendor/**, !vendor/**, !**/zz_generated*
  • vendor/k8s.io/api/node/v1/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/api/node/v1/generated.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
📒 Files selected for processing (15)
  • e2e/go.mod
  • go.mod
  • hack/tools/go.mod
  • manifests-gen/go.mod
  • pkg/controllers/crdcompatibility/objectpruning/handle_test.go
  • pkg/controllers/crdcompatibility/objectpruning/validator_unit_test.go
  • pkg/controllers/crdcompatibility/objectpruning/webhook.go
  • pkg/controllers/crdcompatibility/objectvalidation/handle_test.go
  • pkg/controllers/crdcompatibility/objectvalidation/suite_test.go
  • pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go
  • pkg/controllers/crdcompatibility/objectvalidation/webhook.go
  • pkg/controllers/crdcompatibility/reconcile.go
  • pkg/controllers/crdcompatibility/reconcile_test.go
  • pkg/test/crdbuilder.go
  • pkg/test/warninghandler.go

Comment on lines +119 to 125
if compatibilityRequirement.Spec.ObjectSchemaValidation.Action == apiextensionsv1alpha1.CRDAdmitActionWarn {
// When set to warn, we do not expect to mutate the object, so we return early without pruning.
return nil
}

schema, err := v.getStructuralSchema(compatibilityRequirement, obj.GroupVersionKind().Version)
if err != nil {
Copy link

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Treat unset object-schema validation as disabled here too.

This only skips pruning for Warn. When ObjectSchemaValidation is cleared, the old MutatingWebhookConfiguration can still hit this handler until reconciliation removes it, and the zero-value action currently falls through to pruning. Returning early for any action other than Deny keeps admission inert during that window.

🛠️ Suggested fix
-	if compatibilityRequirement.Spec.ObjectSchemaValidation.Action == apiextensionsv1alpha1.CRDAdmitActionWarn {
-		// When set to warn, we do not expect to mutate the object, so we return early without pruning.
-		return nil
-	}
+	if compatibilityRequirement.Spec.ObjectSchemaValidation.Action != apiextensionsv1alpha1.CRDAdmitActionDeny {
+		// Warn and unset should not mutate the object.
+		return nil
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if compatibilityRequirement.Spec.ObjectSchemaValidation.Action == apiextensionsv1alpha1.CRDAdmitActionWarn {
// When set to warn, we do not expect to mutate the object, so we return early without pruning.
return nil
}
schema, err := v.getStructuralSchema(compatibilityRequirement, obj.GroupVersionKind().Version)
if err != nil {
if compatibilityRequirement.Spec.ObjectSchemaValidation.Action != apiextensionsv1alpha1.CRDAdmitActionDeny {
// Warn and unset should not mutate the object.
return nil
}
schema, err := v.getStructuralSchema(compatibilityRequirement, obj.GroupVersionKind().Version)
if err != nil {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/objectpruning/webhook.go` around lines 119 -
125, The handler currently only returns early when ObjectSchemaValidation.Action
== CRDAdmitActionWarn, but when ObjectSchemaValidation is nil the zero-value
falls through and causes pruning; update the early-exit logic in the webhook
handler (around compatibilityRequirement.Spec.ObjectSchemaValidation.Action and
the call to v.getStructuralSchema) to treat unset/cleared validation as disabled
by returning nil unless the action is explicitly
apiextensionsv1alpha1.CRDAdmitActionDeny — i.e., check if
Spec.ObjectSchemaValidation is nil or its Action != CRDAdmitActionDeny and
return nil to keep the admission inert during reconciliation windows.

Copy link
Contributor

Choose a reason for hiding this comment

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

The bot is correct here. This is a race to the api user unless we ensure that the MWC is gone before we update observedGeneration. Probably not a very consequential race, tbh, but a race.

Copy link

Choose a reason for hiding this comment

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

@mdbooth, agreed — the suggested fix covers that window cleanly. Changing the guard to != CRDAdmitActionDeny means the handler is inert for any action that isn't an explicit deny (including the zero-value/unset case), so pruning won't happen during the period between the CR update and the MWC being removed by the reconciler:

-	if compatibilityRequirement.Spec.ObjectSchemaValidation.Action == apiextensionsv1alpha1.CRDAdmitActionWarn {
-		// When set to warn, we do not expect to mutate the object, so we return early without pruning.
-		return nil
-	}
+	if compatibilityRequirement.Spec.ObjectSchemaValidation.Action != apiextensionsv1alpha1.CRDAdmitActionDeny {
+		// Only prune when explicitly set to Deny; Warn and unset should not mutate the object.
+		return nil
+	}

Comment on lines +853 to +860
BeforeEach(func() {
// Create a new client that collects warnings in the test warning handler.
var err error
warningHandler = test.NewTestWarningHandler()
warningConfig := *cfg
warningConfig.WarningHandlerWithContext = warningHandler
cl, err = client.New(&warningConfig, client.Options{})
Expect(err).ToNot(HaveOccurred())
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Locate warning-mode client creation:"
rg -n --type=go 'client.New\(&warningConfig,\s*client.Options\{\}\)' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go -C2

echo
echo "2) Find StartEnvTest/client initialization and scheme setup:"
rg -n --type=go 'func StartEnvTest|NewScheme|AddToScheme|CompatibilityRequirement' pkg/test -C3

echo
echo "3) Check whether custom API types are registered globally vs per-test scheme:"
rg -n --type=go 'scheme\.Scheme|AddToScheme|Install\(' -C2

Repository: openshift/cluster-capi-operator

Length of output: 50388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) Find cfg initialization in handle_test.go ==="
rg -n --type=go '\bcfg\b' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go | head -30

echo
echo "=== 2) Look for BeforeEach/setup in the test file around the warning handler setup ==="
rg -n --type=go 'var.*cfg|func.*BeforeEach' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go -B2 -A5 | head -50

echo
echo "=== 3) Check how StartEnvTest is used in this package ==="
rg -n --type=go 'StartEnvTest' pkg/controllers/crdcompatibility/objectvalidation/ -B2 -A2

echo
echo "=== 4) Look for other client.New calls in the test file ==="
rg -n --type=go 'client\.New' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go -B3 -A3

echo
echo "=== 5) Check if apiextensionsv1alpha1 is imported in handle_test.go ==="
head -80 pkg/controllers/crdcompatibility/objectvalidation/handle_test.go | grep -E '^import|apiextensionsv1alpha1'

Repository: openshift/cluster-capi-operator

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Reading handle_test.go to understand test structure ==="
wc -l pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== First 100 lines: imports and setup ==="
head -100 pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Lines around 853-870: the code in question with context ==="
sed -n '830,880p' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Search for all BeforeEach in this file ==="
rg -n 'BeforeEach' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

Repository: openshift/cluster-capi-operator

Length of output: 7035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Package-level variable declarations ==="
rg -n '^var \(' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go -A 20

echo
echo "=== Outermost BeforeEach (line 81) - full setup ==="
sed -n '81,150p' pkg/controllers/capi-controller/handle_test.go 2>/dev/null || sed -n '81,150p' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Find where cfg is initialized ==="
rg -n 'cfg.*=' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go | head -20

echo
echo "=== Check testEnv and StarEnvTest usage ==="
rg -n 'testEnv|StartEnvTest' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

Repository: openshift/cluster-capi-operator

Length of output: 118


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read lines 1-80 to find package-level var declarations ==="
sed -n '1,80p' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Find var declarations anywhere in file ==="
rg -n '^\s*var\s+' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Look for testEnv initialization ==="
rg -n 'testEnv\s*=' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go -B2 -A5

Repository: openshift/cluster-capi-operator

Length of output: 3784


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for 'cfg' and 'cl' declarations/assignments ==="
rg -n '\bcfg\b' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go | head -20

echo
echo "=== Search for 'cl' variable ==="
rg -n '\bcl\b' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go | head -20

echo
echo "=== Look at lines 849-875 to see full variable block ==="
sed -n '849,875p' pkg/controllers/crdcompatibility/objectvalidation/handle_test.go

echo
echo "=== Check if cfg/cl are declared in outer test file or parent suite setup ==="
find pkg/controllers/crdcompatibility/objectvalidation -name '*.go' -exec grep -l '\bcfg\b.*\*rest.Config\|\bcl\b.*client' {} \;

echo
echo "=== Look for test setup/init file ==="
ls -la pkg/controllers/crdcompatibility/objectvalidation/
</parameter>

Repository: openshift/cluster-capi-operator

Length of output: 3366


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read suite_test.go to find cfg and cl declarations ==="
cat pkg/controllers/crdcompatibility/objectvalidation/suite_test.go

Repository: openshift/cluster-capi-operator

Length of output: 5133


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check test.StartEnvTest to understand scheme registration ==="
sed -n '40,100p' pkg/test/envtest.go

echo
echo "=== Verify that apiextensionsv1alpha1 is registered in scheme ==="
grep -n 'apiextensionsv1alpha1' pkg/test/envtest.go

echo
echo "=== Check what scheme package is imported and where ==="
head -30 pkg/test/envtest.go | grep -E '^import|scheme'

Repository: openshift/cluster-capi-operator

Length of output: 3275


Use explicit scheme and avoid mutating global client state in BeforeEach.

At line 859, client.New(&warningConfig, client.Options{}) relies on the default scheme.Scheme which includes apiextensionsv1alpha1 types (registered in pkg/test/envtest.go). While this works, it's implicit and brittle. Additionally, reassigning the package-global cl creates test isolation issues: subsequent tests within this Describe block depend on the warning-configured client, and cleanup is not guaranteed if a test panics.

💡 Suggested fix
 BeforeEach(func() {
 	// Create a new client that collects warnings in the test warning handler.
 	var err error
+	baseClient := cl
 	warningHandler = test.NewTestWarningHandler()
 	warningConfig := *cfg
 	warningConfig.WarningHandlerWithContext = warningHandler
-	cl, err = client.New(&warningConfig, client.Options{})
+	cl, err = client.New(&warningConfig, client.Options{
+		Scheme: baseClient.Scheme(),
+	})
 	Expect(err).ToNot(HaveOccurred())
+
+	DeferCleanup(func() {
+		cl = baseClient
+	})
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BeforeEach(func() {
// Create a new client that collects warnings in the test warning handler.
var err error
warningHandler = test.NewTestWarningHandler()
warningConfig := *cfg
warningConfig.WarningHandlerWithContext = warningHandler
cl, err = client.New(&warningConfig, client.Options{})
Expect(err).ToNot(HaveOccurred())
BeforeEach(func() {
// Create a new client that collects warnings in the test warning handler.
var err error
baseClient := cl
warningHandler = test.NewTestWarningHandler()
warningConfig := *cfg
warningConfig.WarningHandlerWithContext = warningHandler
cl, err = client.New(&warningConfig, client.Options{
Scheme: baseClient.Scheme(),
})
Expect(err).ToNot(HaveOccurred())
DeferCleanup(func() {
cl = baseClient
})
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/objectvalidation/handle_test.go` around
lines 853 - 860, The BeforeEach mutates the package-global cl and relies on the
implicit global scheme; instead create an explicit scheme and avoid changing
global state: build a fresh scheme (e.g. runtime.NewScheme()) and explicitly
register the needed API types (the same apiextensionsv1alpha1 types registered
in pkg/test/envtest.go) and pass that scheme into client.New via a copied
rest.Config (warningConfig), then create a local client variable (e.g. testCl)
from client.New rather than assigning to the package-global cl; if other helpers
require the global, save the oldCl before creating testCl and restore oldCl in
AfterEach (and ensure any client cleanup runs even on panic) so tests remain
isolated.

Comment on lines 314 to 317
func isObjectValidationWebhookEnabled(obj *apiextensionsv1alpha1.CompatibilityRequirement) bool {
osv := obj.Spec.ObjectSchemaValidation
return osv.Action == "" && osv.MatchConditions == nil && labelSelectorIsEmpty(osv.NamespaceSelector) && labelSelectorIsEmpty(osv.ObjectSelector)
return osv.Action != "" || osv.MatchConditions != nil || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
}
Copy link

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Treat empty MatchConditions as not configured.

Line 316 uses osv.MatchConditions != nil, so an explicit empty list still enables webhooks. That contradicts the “not active when not configured” objective.

🎯 Proposed fix
 func isObjectValidationWebhookEnabled(obj *apiextensionsv1alpha1.CompatibilityRequirement) bool {
 	osv := obj.Spec.ObjectSchemaValidation
-	return osv.Action != "" || osv.MatchConditions != nil || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
+	return osv.Action != "" || len(osv.MatchConditions) > 0 || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func isObjectValidationWebhookEnabled(obj *apiextensionsv1alpha1.CompatibilityRequirement) bool {
osv := obj.Spec.ObjectSchemaValidation
return osv.Action == "" && osv.MatchConditions == nil && labelSelectorIsEmpty(osv.NamespaceSelector) && labelSelectorIsEmpty(osv.ObjectSelector)
return osv.Action != "" || osv.MatchConditions != nil || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
}
func isObjectValidationWebhookEnabled(obj *apiextensionsv1alpha1.CompatibilityRequirement) bool {
osv := obj.Spec.ObjectSchemaValidation
return osv.Action != "" || len(osv.MatchConditions) > 0 || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/reconcile.go` around lines 314 - 317, The
isObjectValidationWebhookEnabled function currently treats a non-nil
MatchConditions slice as enabled even if it's empty; change the check from
osl.MatchConditions != nil to len(osv.MatchConditions) > 0 so an explicitly
empty list is considered not configured, i.e., update the return expression in
isObjectValidationWebhookEnabled to use len(osv.MatchConditions) > 0 (keeping
the existing checks for osv.Action and the namespace/object selector helpers).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the bot is correct here. I did wonder if we would distinguish between empty slices and no value, but I can't think of a reasonable behaviour for the empty value. Even if our validation disallows an empty value, I think it's nice to save somebody the effort of having to check by doing the more defensive test.

Copy link

Choose a reason for hiding this comment

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

@mdbooth, agreed — len(osv.MatchConditions) > 0 is the more defensive and correct check. The committable suggestion above already has the exact one-line fix ready to apply whenever the author picks it up.

@openshift-ci-robot
Copy link

openshift-ci-robot commented Mar 13, 2026

@JoelSpeed: This pull request references OCPCLOUD-3384 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3365 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "4.22.0" version, but no target version was set.

Details

In response to this:

This ensures that users configuring admission to warn do not have pruning or rejected writes for object admission

Also ensures that VWC/MWCs are removed when object admission is not configured

Summary by CodeRabbit

  • New Features

  • Warning mode for object schema validation: validations can emit warnings instead of denying requests.

  • Bug Fixes

  • Webhook ownership and lifecycle tightened to avoid overwriting or deleting webhooks not owned by the requirement.

  • Pruning/validation behavior adjusted to respect warning mode and ownership checks.

  • Chores

  • Bumped Go toolchain to 1.25.0 and updated Kubernetes, OpenShift, and etcd dependencies.

  • CI/Docker builder images updated.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 13, 2026

@JoelSpeed: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/unit fa03d1b link true /test unit

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Comment on lines 314 to 317
func isObjectValidationWebhookEnabled(obj *apiextensionsv1alpha1.CompatibilityRequirement) bool {
osv := obj.Spec.ObjectSchemaValidation
return osv.Action == "" && osv.MatchConditions == nil && labelSelectorIsEmpty(osv.NamespaceSelector) && labelSelectorIsEmpty(osv.ObjectSelector)
return osv.Action != "" || osv.MatchConditions != nil || !labelSelectorIsEmpty(osv.NamespaceSelector) || !labelSelectorIsEmpty(osv.ObjectSelector)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the bot is correct here. I did wonder if we would distinguish between empty slices and no value, but I can't think of a reasonable behaviour for the empty value. Even if our validation disallows an empty value, I think it's nice to save somebody the effort of having to check by doing the more defensive test.

if err := r.client.Get(ctx, types.NamespacedName{Name: webhookConfig.Name}, existingWebhookConfig); err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to get ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
} else if err == nil && !metav1.IsControlledBy(existingWebhookConfig, obj) {
return fmt.Errorf("%w: %s", errWebhookConfigNotControlledByCompatibilityRequirement, webhookConfig.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Terminal error?

Comment on lines +119 to 125
if compatibilityRequirement.Spec.ObjectSchemaValidation.Action == apiextensionsv1alpha1.CRDAdmitActionWarn {
// When set to warn, we do not expect to mutate the object, so we return early without pruning.
return nil
}

schema, err := v.getStructuralSchema(compatibilityRequirement, obj.GroupVersionKind().Version)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

The bot is correct here. This is a race to the api user unless we ensure that the MWC is gone before we update observedGeneration. Probably not a very consequential race, tbh, but a race.

retrievedObj.SetName(inputObject.GetName())
retrievedObj.SetNamespace(inputObject.GetNamespace())

Eventually(kWithCtx(ctx).Get(retrievedObj)).WithContext(ctx).Should(Succeed())
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we were doing timeouts too, now?

scenario.CompatibilityRequirement.Spec.ObjectSchemaValidation.Action = apiextensionsv1alpha1.CRDAdmitActionWarn
})).WithContext(ctx).Should(Succeed())

By("Updating the object again, should not be pruned")
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like you need to wait here until the CR has a success condition with observedGeneration >= the return from your Update?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants