Skip to content

OCPCLOUD-3005,OCPCLOUD-3186,OCPCLOUD-3367: Implement validation webhook for custom resource admission#461

Open
JoelSpeed wants to merge 11 commits intoopenshift:mainfrom
openshift-cloud-team:compatibility-object-admission
Open

OCPCLOUD-3005,OCPCLOUD-3186,OCPCLOUD-3367: Implement validation webhook for custom resource admission#461
JoelSpeed wants to merge 11 commits intoopenshift:mainfrom
openshift-cloud-team:compatibility-object-admission

Conversation

@JoelSpeed
Copy link
Contributor

@JoelSpeed JoelSpeed commented Feb 13, 2026

This implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Summary by CodeRabbit

  • New Features

    • Added per-requirement object validation via admission webhooks for CompatibilityRequirement resources; webhook configurations are created and removed automatically and the validator runs alongside the operator.
  • Tests

    • Added extensive unit and end-to-end tests covering webhook integration, validation strategies, caching/invalidation, and create/update/delete behaviors.

@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 Feb 13, 2026
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Feb 13, 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-robot
Copy link

openshift-ci-robot commented Feb 13, 2026

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

Details

In response to this:

This implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Currently based on #459

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 added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Feb 13, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a webhook-based object validator for CompatibilityRequirements, wires it into manager startup, manages ValidatingWebhookConfiguration lifecycle in the reconciler, implements per-(requirement,version,generation) strategy caching/pruning and admission handling, and includes unit and end-to-end tests plus small test fixture updates.

Changes

Cohort / File(s) Summary
Webhook implementation
pkg/controllers/crdcompatibility/objectvalidation/webhook.go, pkg/controllers/crdcompatibility/objectvalidation/handle.go
New validator type, constructor (NewValidator), SetupWithManager registration, admission handler, ValidateCreate/ValidateUpdate/ValidateDelete, strategy creation from CompatibilityRequirement schema, per-(requirement,version,generation) caching and pruning, and admission response construction.
Webhook tests (e2e & suite)
pkg/controllers/crdcompatibility/objectvalidation/handle_test.go, pkg/controllers/crdcompatibility/objectvalidation/suite_test.go
Envtest end-to-end suite and helpers: start webhook server, install CRDs, exercise create/update/delete across exact/tighter/looser scenarios, validate admission decisions and warnings, and provide InitValidator/bootstrap helpers.
Validator unit tests
pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go
Unit tests for getValidationStrategy, caching, cache key composition, pruning/invalidation, and YAML parsing error cases using fake client and indexers.
Manager integration (binary)
cmd/crd-compatibility-checker/main.go
Wires object validation into manager startup via NewValidator().SetupWithManager(ctx, mgr) and exits on setup failure.
Reconciler webhook lifecycle
pkg/controllers/crdcompatibility/reconcile.go
Adds ensure/remove ValidatingWebhookConfiguration, builder for webhook config (service refs, rules, subresources), integrates apply/delete into reconcile flows, and helpers to decide webhook eligibility.
Reconcile tests
pkg/controllers/crdcompatibility/reconcile_test.go
Tests creating CompatibilityRequirement with ObjectSchemaValidation and asserts ValidatingWebhookConfiguration fields (name, annotations, service, path via objectvalidation.WebhookPrefix, rules, operations) and deletion behavior (duplicate contexts added).
Controller changes
pkg/controllers/crdcompatibility/controller.go
Reconciler struct adds kubeClient kubernetes.Interface and resourceCache resourceapply.ResourceCache; constructor initializes resource cache and SetupWithManager creates kube client.
Test utilities
pkg/test/crdbuilder.go
Removed ptr import and dropped XPreserveUnknownFields entries for generated CRD spec and status.
Small test tweak
pkg/controllers/crdcompatibility/util_test.go
Added explicit 10s timeout to an Eventually call in test helper.
Dependencies
go.mod
Added several indirect dependencies (etcd v3.6.4 modules, grpc-prometheus, protobuf, go-systemd/coreos semver, OpenTelemetry gRPC instrumentation).
sequenceDiagram
    participant APIServer as API Server
    participant Manager as Manager
    participant Validator as Validation Webhook
    participant K8sClient as Kubernetes API
    participant Strategy as Validation Strategy

    APIServer->>Manager: Admission request (CREATE/UPDATE/DELETE)
    Manager->>Validator: Forward request to Handle
    Validator->>Validator: Extract CompatibilityRequirement name from path/context
    Validator->>K8sClient: Get CompatibilityRequirement (schema YAML)
    K8sClient-->>Validator: Return CompatibilityRequirement
    Validator->>Strategy: Lookup or build per-(requirement,version,generation) strategy
    alt Cache miss
        Strategy->>Strategy: Parse CRD schema, build validators
        Strategy-->>Validator: Return new strategy (cached)
    else Cache hit
        Strategy-->>Validator: Return cached strategy
    end
    Validator->>Strategy: Validate object (and old object for UPDATE)
    alt Validation succeeds
        Strategy-->>Validator: Optional warnings
        Validator-->>APIServer: Admission Allowed (with warnings)
    else Validation fails
        Strategy-->>Validator: Validation errors
        Validator-->>APIServer: Admission Denied (status error)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped in to check each schema line,
Caching generations, pruning old design,
I nudge or deny, then scamper away,
Tests and webhooks in a tidy array,
Carrots, code, and validation fine.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 change: implementing a validation webhook for custom resource admission control in the cluster-capi-operator, aligning well with the substantial webhook infrastructure additions across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@JoelSpeed JoelSpeed changed the title OCPCLOUD-3005: Implement validation webhook for custom resource admission OCPCLOUD-3005,OCPCLOUD-3186,OCPCLOUD-3367: Implement validation webhook for custom resource admission Feb 13, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 13, 2026

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

This pull request references OCPCLOUD-3186 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 story to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3367 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 implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Currently based on #459

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.

@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch from 08d1969 to d564e4e Compare February 16, 2026 14:06
@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 20, 2026
@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch from d564e4e to 2daa19b Compare February 23, 2026 13:57
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 23, 2026
@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch 2 times, most recently from 07f96d2 to 958e566 Compare February 25, 2026 12:04
@JoelSpeed JoelSpeed marked this pull request as ready for review February 25, 2026 12:04
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 25, 2026

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

This pull request references OCPCLOUD-3186 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 story to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3367 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 implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

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 Feb 25, 2026
@openshift-ci openshift-ci bot requested review from nrb and racheljpg February 25, 2026 12:04
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 25, 2026

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

This pull request references OCPCLOUD-3186 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 story to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3367 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 implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Summary by CodeRabbit

  • New Features

  • Added object validation webhook support for CRD compatibility requirements, enabling automatic validation of objects against defined CRD schemas during creation and updates. Webhooks are automatically provisioned and managed.

  • Tests

  • Added comprehensive test coverage for object validation webhook integration, validation strategies, and caching behavior.

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

🧹 Nitpick comments (1)
pkg/controllers/crdcompatibility/reconcile_test.go (1)

258-280: Assert the selector and match condition fields you configure in setup.

Lines 228-245 configure NamespaceSelector, ObjectSelector, and MatchConditions, but the assertion block does not verify them. This leaves a key behavior untested.

🤖 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 258 - 280,
The test currently asserts many webhook fields but omits the NamespaceSelector,
ObjectSelector, and MatchConditions configured in setup; update the assertion on
webhookConfig (the Eventually(kWithCtx(ctx).Object(webhookConfig)) block) to
include HaveField checks for "NamespaceSelector", "ObjectSelector", and
"MatchConditions" inside the Webhooks ConsistOf SatisfyAll so they verify the
values you set in setup (compare against the selector/condition objects you
created there — reference the same variables used in setup when asserting, and
add them alongside the existing HaveField checks for Name/ClientConfig/Rules).
🤖 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/objectvalidation/handle_test.go`:
- Around line 482-489: The test uses compatibilityRequirement.Name (creating
testPath) without initializing compatibilityRequirement, causing a hidden
cross-context dependency; fix by declaring and initializing a local test
object/name in this spec (e.g., set a local variable like reqName := "test-name"
or construct a local compatibilityRequirement with Name set) and use that local
value when building testPath and asserting against
compatibilityRequrementFromContext; update the lines that reference
compatibilityRequirement.Name to use the newly initialized local variable or
object so the test is self-contained and does not rely on outer contexts.

In `@pkg/controllers/crdcompatibility/objectvalidation/handle.go`:
- Around line 39-48: The function compatibilityRequrementFromContext currently
panics if the context key compatibilityRequirementContextKey{} is missing or the
value is not a string; change it to return (string, error) instead of panicking
(e.g., return "", fmt.Errorf("missing or invalid CompatibilityRequirement in
context: %T", v)) and update all callers of compatibilityRequrementFromContext
to handle the error and convert it into a proper admission failure response (no
panics) in the webhook request handling path.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go`:
- Around line 103-135: The current ValidateCreate and ValidateUpdate always
convert validation errors to apierrors.NewInvalid (deny); update both functions
to honor the requirement's action by checking the compatibility
requirement/action returned by getValidationStrategy (or the strategy object,
e.g., strategy.Action or requirement.Action) and: if the action is "Warn" return
the collected warnings and nil even when errs is non-empty, and if the action is
"Deny" continue returning apierrors.NewInvalid as now; keep using
strategy.Validate/ValidateUpdate and strategy.WarningsOnCreate/WarningsOnUpdate,
but branch on the action before converting errs into an admission error.

In `@pkg/controllers/crdcompatibility/reconcile_test.go`:
- Around line 226-249: Replace the dependency on the parent-level testCRDClean
by creating and using a locally-scoped CRD inside this BeforeAll: call the
appropriate helper to construct a fresh CRD (instead of referencing
testCRDClean) and pass that to test.GenerateTestCompatibilityRequirement so
requirement is initialized from a local variable; update references in this
BeforeAll to use that local CRD (e.g., localCRD / newCRD) before calling
createTestObject and leave all other fields (ObjectSchemaValidation, selectors,
MatchConditions) unchanged.

In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 230-286: The validatingWebhookConfigurationFor function builds a
ValidatingWebhookConfiguration but omits copying object-scoped selectors and
match conditions; update validatingWebhookConfigurationFor to set the
ValidatingWebhookConfiguration.Webhooks[*].NamespaceSelector,
Webhooks[*].ObjectSelector, and Webhooks[*].MatchConditions from the
CompatibilityRequirement.Spec.ObjectSchemaValidation (or the appropriate field
on obj that holds NamespaceSelector, ObjectSelector, and MatchConditions).
Locate where vwc.Webhooks[0] is constructed and after creating the webhook
assign vwc.Webhooks[0].NamespaceSelector =
obj.Spec.ObjectSchemaValidation.NamespaceSelector,
vwc.Webhooks[0].ObjectSelector = obj.Spec.ObjectSchemaValidation.ObjectSelector,
and vwc.Webhooks[0].MatchConditions =
obj.Spec.ObjectSchemaValidation.MatchConditions (ensuring types match and
handling nil safely) so namespace/object-scoped admission behavior is preserved.
- Around line 192-193: The code calls validatingWebhookConfigurationFor(obj,
r.compatibilityCRD) without ensuring r.compatibilityCRD is non-nil, which can
cause a panic; before building webhookConfig, add a nil-check for
r.compatibilityCRD (e.g., if r.compatibilityCRD == nil) and handle the condition
by returning an appropriate error or reconciling requeue, then only call
validatingWebhookConfigurationFor and proceed with the existing r.client.Get
using webhookConfig. Ensure you update the surrounding control flow so
webhookConfig is only constructed when r.compatibilityCRD is present.
- Around line 187-202: The reconcile in ensureObjectValidationWebhook is wrong:
stop returning early when isObjectValidationWebhookEnabled(obj) is false and
instead ensure previously-created resources are removed (call r.client.Get with
an empty apiextensionsv1alpha1.ValidatingWebhookConfiguration and
r.client.Delete if found); when reconciling the desired config use
validatingWebhookConfigurationFor(obj, r.compatibilityCRD) only as the desired
object and do not pass that same instance into r.client.Get (use a fresh empty
instance for Get/IsNotFound checks), and on create/update call r.client.Create
or r.client.Update with the desired webhookConfig as built.

---

Nitpick comments:
In `@pkg/controllers/crdcompatibility/reconcile_test.go`:
- Around line 258-280: The test currently asserts many webhook fields but omits
the NamespaceSelector, ObjectSelector, and MatchConditions configured in setup;
update the assertion on webhookConfig (the
Eventually(kWithCtx(ctx).Object(webhookConfig)) block) to include HaveField
checks for "NamespaceSelector", "ObjectSelector", and "MatchConditions" inside
the Webhooks ConsistOf SatisfyAll so they verify the values you set in setup
(compare against the selector/condition objects you created there — reference
the same variables used in setup when asserting, and add them alongside the
existing HaveField checks for Name/ClientConfig/Rules).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 2b8e8b6 and 958e566.

⛔ Files ignored due to path filters (290)
  • go.sum is excluded by !**/*.sum
  • go.work.sum is excluded by !**/*.sum
  • vendor/github.com/coreos/go-semver/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-semver/NOTICE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-semver/semver/semver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-semver/semver/sort.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/NOTICE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/journal/journal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/coreos/go-systemd/v22/journal/journal_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/Makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/gogo.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/gogo.pb.golden is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/gogo.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/gogoproto/helper.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/Makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/descriptor_gostring.gen.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/gogo/protobuf/protoc-gen-gogo/descriptor/helper.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/AUTHORS is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/CONTRIBUTORS is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/buffer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/defaults.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/deprecated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/discard.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/extensions.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/properties.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/proto.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/registry.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/text_decode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/text_encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/wire.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/protobuf/proto/wrappers.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/.travis.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/CHANGELOG.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/client_reporter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/metric_options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/server_reporter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/go-grpc-prometheus/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/BUILD.bazel is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/annotations.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/annotations.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/annotations_protoopaque.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/buf.gen.yaml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/openapiv2.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/openapiv2.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options/openapiv2_protoopaque.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/authpb/auth.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/authpb/auth.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/etcdserver.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/etcdserver.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/raft_internal.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/raft_internal.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/raft_internal_stringer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/rpc.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/etcdserverpb/rpc.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/membershippb/membership.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/membershippb/membership.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/mvccpb/kv.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/mvccpb/kv.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/v3rpc/rpctypes/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/v3rpc/rpctypes/error.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/v3rpc/rpctypes/md.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/v3rpc/rpctypes/metadatafields.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/api/v3/versionpb/version.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/api/v3/versionpb/version.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/dir_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/dir_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/filereader.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/fileutil.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_flock.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_linux.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_plan9.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_solaris.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/lock_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/preallocate.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/preallocate_darwin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/preallocate_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/preallocate_unsupported.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/purge.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/read_dir.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/sync.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/sync_darwin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/fileutil/sync_linux.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/logutil/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/logutil/log_format.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/logutil/log_level.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/logutil/zap.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/logutil/zap_journal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/systemd/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/systemd/journal.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/tlsutil/cipher_suites.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/tlsutil/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/tlsutil/tlsutil.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/tlsutil/versions.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/keepalive_listener.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/keepalive_listener_openbsd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/keepalive_listener_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/limit_listen.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/listener.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/listener_opts.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/listener_tls.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/sockopt.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/sockopt_solaris.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/sockopt_unix.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/sockopt_wasm.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/sockopt_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/timeout_conn.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/timeout_dialer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/timeout_listener.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/timeout_transport.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/tls.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/transport.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/transport/unix_listener.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/id.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/set.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/slice.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/urls.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/types/urlsmap.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/pkg/v3/verify/verify.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/OWNERS is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/auth.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/cluster.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/compact_op.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/compare.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/credentials/credentials.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/ctx.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/internal/endpoint/endpoint.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/internal/resolver/resolver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/kubernetes/client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/kubernetes/interface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/kv.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/lease.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/logger.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/maintenance.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/op.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/retry.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/retry_interceptor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/sort.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/txn.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.etcd.io/etcd/client/v3/watch.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/interceptorinfo.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/internal/parse.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/metadata_supplier.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/stats_handler.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/MIGRATION.md is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/attribute_group.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/exception.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/metric.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.opentelemetry.io/otel/semconv/v1.30.0/schema.go is excluded by !**/vendor/**, !vendor/**
  • vendor/go.uber.org/zap/zapgrpc/zapgrpc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/cryptobyte/asn1.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/cryptobyte/builder.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/crypto/cryptobyte/string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/golang.org/x/net/context/context.go is excluded by !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/client.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/field_behavior.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/field_info.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/resource.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/annotations/routing.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/genproto/googleapis/api/launch_stage.pb.go is excluded by !**/*.pb.go, !**/vendor/**, !vendor/**
  • vendor/google.golang.org/grpc/resolver/manual/manual.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install/install.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/apiserver.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/converter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/nop_converter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_discovery_controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/helpers.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype/validation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/interface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/customresourcedefinition.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1/interface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/customresourcedefinition.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1beta1/interface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/factory.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/generic.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1/customresourcedefinition.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1/expansion_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/customresourcedefinition.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1/expansion_generated.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/apiapproval/apiapproval_controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/establish/establishing_controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/finalizer/crd_finalizer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/merge.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapi/controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapi/metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapiv3/controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapiv3/metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/openapiv3/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/controller/status/naming_controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/crdserverscheme/unstructured.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/generated/openapi/doc.go is excluded by !**/generated/**, !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/generated/openapi/zz_generated.openapi.go is excluded by !**/generated/**, !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor/tableconvertor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource/validator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/api/meta/table/table.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion/validation/validation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/util/duration/duration.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/util/waitgroup/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/metrics/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/metrics/metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors/statuserror.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/apis/apidiscovery/v2/conversion.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/apis/apidiscovery/v2/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/apis/apidiscovery/v2/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap/default.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/OWNERS is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/deprecation/deprecation.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/OWNERS is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/addresses.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/etag.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/fake.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/handler.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/metrics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/k8s.io/apiserver/pkg/endpoints/discovery/aggregated/negotiation.go is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (10)
  • cmd/crd-compatibility-checker/main.go
  • go.mod
  • pkg/controllers/crdcompatibility/objectvalidation/handle.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

@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch from 958e566 to 8252156 Compare February 25, 2026 17:07
@openshift-merge-robot openshift-merge-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 25, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 25, 2026

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

This pull request references OCPCLOUD-3186 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 story to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3367 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 implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Summary by CodeRabbit

  • New Features

  • Added object validation webhook support for CompatibilityRequirement resources: dynamic per-requirement validation is run on create/update, and webhook configurations are managed automatically.

  • Tests

  • Added extensive unit and end-to-end tests covering webhook integration, validation strategies, caching/invalidation, and behavior across create/update/delete scenarios.

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: 2

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)

170-185: ⚠️ Potential issue | 🟠 Major

Finalizer removal before webhook cleanup risks orphaning the webhook config.

errors.Join executes both clearFinalizer and removeObjectValidationWebhook regardless of individual outcomes. If clearFinalizer succeeds but removeObjectValidationWebhook fails, the finalizer is already gone and the object will be garbage-collected — the controller won't get another reconcile to retry webhook cleanup.

Consider removing the webhook before clearing the finalizer:

Proposed fix
 func (r *reconcileState) reconcileDelete(ctx context.Context, obj *apiextensionsv1alpha1.CompatibilityRequirement) (ctrl.Result, error) {
 	logger := logf.FromContext(ctx)
 	logger.Info("Reconciling CompatibilityRequirement deletion")

-	err := errors.Join(
-		clearFinalizer(ctx, r.client, obj),
-		r.removeObjectValidationWebhook(ctx, obj),
-	)
+	if err := r.removeObjectValidationWebhook(ctx, obj); err != nil {
+		return ctrl.Result{}, err
+	}
+
+	if err := clearFinalizer(ctx, r.client, obj); err != nil {
+		return ctrl.Result{}, err
+	}

-	if err != nil {
-		return ctrl.Result{}, err
-	}
 	return ctrl.Result{}, nil
 }
🤖 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 170 - 185, The
deletion currently uses errors.Join to run clearFinalizer and
removeObjectValidationWebhook in parallel which can remove the finalizer even if
webhook cleanup fails; change reconcileDelete to call
removeObjectValidationWebhook(ctx, obj) first and if it returns an error return
that error (so the finalizer remains), and only after successful webhook removal
call clearFinalizer(ctx, r.client, obj) and return any error from that; stop
using errors.Join so the finalizer is only cleared after
removeObjectValidationWebhook succeeds.
♻️ Duplicate comments (6)
pkg/controllers/crdcompatibility/reconcile.go (3)

156-161: ⚠️ Potential issue | 🔴 Critical

errors.Join chains sequential dependencies — later functions run even if earlier ones failed.

parseCompatibilityCRD, fetchCurrentCRD, checkCompatibilityRequirement, and ensureObjectValidationWebhook are called via errors.Join, but they have data dependencies (r.compatibilityCRD set by parseCompatibilityCRD is used by ensureObjectValidationWebhook). If parseCompatibilityCRD fails, r.compatibilityCRD stays nil and ensureObjectValidationWebhook will panic in validatingWebhookConfigurationFor.

The nil-guard suggested in the past review comment on r.compatibilityCRD would help, but the root issue is using errors.Join for sequential-dependent steps.

🤖 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 156 - 161,
Replace the use of errors.Join (which runs all calls unconditionally) with a
sequential error-check pattern so dependent steps run only if prior steps
succeed: call r.parseCompatibilityCRD(obj) first and return on error, then call
r.fetchCurrentCRD(ctx, logger) and return on error, then
r.checkCompatibilityRequirement() and return on error, then
r.ensureObjectValidationWebhook(ctx, obj) and return on error; also keep or add
a nil-guard around r.compatibilityCRD (used by
validatingWebhookConfigurationFor) to avoid panics if it ever becomes nil.

243-282: ⚠️ Potential issue | 🟠 Major

Webhook config does not propagate NamespaceSelector, ObjectSelector, or MatchConditions from the spec.

validatingWebhookConfigurationFor builds the webhook but never sets these fields from obj.Spec.ObjectSchemaValidation. Users configuring namespace/object-scoped admission or match conditions will find them silently ignored.

Proposed fix (add to the webhook definition)
 		Webhooks: []admissionregistrationv1.ValidatingWebhook{
 			{
+				NamespaceSelector: &obj.Spec.ObjectSchemaValidation.NamespaceSelector,
+				ObjectSelector:    &obj.Spec.ObjectSchemaValidation.ObjectSelector,
+				MatchConditions:   obj.Spec.ObjectSchemaValidation.MatchConditions,
 				AdmissionReviewVersions: []string{"v1"},
🤖 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 243 - 282, The
ValidatingWebhookConfiguration built in validatingWebhookConfigurationFor is
missing propagation of namespace/object selectors and match conditions from the
CR's spec; update the ValidatingWebhook (inside
validatingWebhookConfigurationFor) to set NamespaceSelector, ObjectSelector and
MatchConditions using values from obj.Spec.ObjectSchemaValidation (copying those
fields into the admissionregistrationv1.ValidatingWebhook fields), ensuring
proper type conversion if needed so NamespaceSelector and ObjectSelector become
*metav1.LabelSelector and MatchConditions map to
admissionregistrationv1.MatchCondition slice on the webhook; keep the field
names (NamespaceSelector, ObjectSelector, MatchConditions) consistent with
admissionregistrationv1 API and read from obj.Spec.ObjectSchemaValidation to
preserve user intent.

187-210: ⚠️ Potential issue | 🔴 Critical

Get overwrites the desired webhook config, making Update a no-op.

webhookConfig is built with the desired state on line 192, but r.client.Get on line 193 overwrites it with the server's current state. The subsequent Update on line 205 then writes back the server state unchanged. Also, when isObjectValidationWebhookEnabled returns true (i.e., no webhook config needed), any previously-created webhook is left behind.

Proposed fix
 func (r *reconcileState) ensureObjectValidationWebhook(ctx context.Context, obj *apiextensionsv1alpha1.CompatibilityRequirement) error {
-	if isObjectValidationWebhookEnabled(obj) {
-		return nil
+	if !isObjectValidationWebhookNeeded(obj) {
+		return r.removeObjectValidationWebhook(ctx, obj)
 	}
+	if r.compatibilityCRD == nil {
+		return fmt.Errorf("cannot reconcile object validation webhook: compatibility CRD is nil")
+	}

-	webhookConfig := validatingWebhookConfigurationFor(obj, r.compatibilityCRD)
-	if err := r.client.Get(ctx, types.NamespacedName{Name: webhookConfig.Name}, webhookConfig); err != nil {
+	desired := validatingWebhookConfigurationFor(obj, r.compatibilityCRD)
+	current := &admissionregistrationv1.ValidatingWebhookConfiguration{}
+	if err := r.client.Get(ctx, types.NamespacedName{Name: desired.Name}, current); err != nil {
 		if apierrors.IsNotFound(err) {
-			if err := r.client.Create(ctx, webhookConfig); err != nil {
-				return fmt.Errorf("failed to create ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
+			if err := r.client.Create(ctx, desired); err != nil {
+				return fmt.Errorf("failed to create ValidatingWebhookConfiguration %s: %w", desired.Name, err)
 			}
 			return nil
 		}
-		return fmt.Errorf("failed to get ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
+		return fmt.Errorf("failed to get ValidatingWebhookConfiguration %s: %w", desired.Name, err)
 	}

-	if err := r.client.Update(ctx, webhookConfig); err != nil {
-		return fmt.Errorf("failed to update ValidatingWebhookConfiguration %s: %w", webhookConfig.Name, err)
+	current.Annotations = desired.Annotations
+	current.Webhooks = desired.Webhooks
+	if err := r.client.Update(ctx, current); err != nil {
+		return fmt.Errorf("failed to update ValidatingWebhookConfiguration %s: %w", desired.Name, err)
 	}

 	return nil
 }
🤖 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 187 - 210, The
ensureObjectValidationWebhook function currently builds a desired webhookConfig
then calls r.client.Get into that same object (overwriting the desired state)
and later Update becomes a no-op; also when
isObjectValidationWebhookEnabled(obj) returns true the controller skips creating
but never deletes an existing webhook. Fix by (1) if
isObjectValidationWebhookEnabled(obj) is true, attempt to fetch the existing
ValidatingWebhookConfiguration into a separate variable (e.g., existing :=
&admissionv1.ValidatingWebhookConfiguration{}) and Delete it if found; (2) when
creating/updating, keep your desired webhook (webhookConfig) separate from the
fetched one: call r.client.Get(ctx, NamespacedName{Name: webhookConfig.Name},
fetched) and if NotFound call r.client.Create(ctx, webhookConfig); otherwise
copy fetched.ResourceVersion into webhookConfig (or merge only the
ResourceVersion) before calling r.client.Update(ctx, webhookConfig) so Update
applies the desired content rather than re-saving the fetched object.
pkg/controllers/crdcompatibility/reconcile_test.go (1)

222-255: ⚠️ Potential issue | 🔴 Critical

BeforeAll runs before the parent BeforeEach, so testCRDClean is nil here.

In Ginkgo v2, a nested Ordered context's BeforeAll (line 226) executes before the parent's BeforeEach (line 52), so testCRDClean referenced on line 227 has not been initialized yet. This will cause a nil pointer dereference when GenerateTestCompatibilityRequirement tries to marshal it.

Proposed fix: use a locally-scoped CRD
 		BeforeAll(func(ctx context.Context) {
-			requirement = test.GenerateTestCompatibilityRequirement(testCRDClean)
+			localCRD := test.GenerateTestCRD()
+			requirement = test.GenerateTestCompatibilityRequirement(localCRD)

Then also update any assertions that reference testCRDClean (lines 270-273) to use localCRD.

🤖 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 222 - 255,
BeforeAll in this nested Ordered context runs before the parent's BeforeEach, so
testCRDClean is nil when calling GenerateTestCompatibilityRequirement; fix by
declaring and initializing a locally-scoped CRD (e.g., localCRD) inside this
Context and pass that to GenerateTestCompatibilityRequirement instead of
testCRDClean; also update any later assertions or comparisons that reference
testCRDClean (the checks against the created CRD/requirement) to use localCRD so
the test uses the locally-initialized CRD rather than the uninitialized package
variable; keep the rest of the setup (requirement.Spec.ObjectSchemaValidation,
createTestObject, webhookConfig) unchanged.
pkg/controllers/crdcompatibility/objectvalidation/handle_test.go (1)

477-491: ⚠️ Potential issue | 🟠 Major

Cross-context dependency: compatibilityRequirement.Name relies on prior test execution.

compatibilityRequirement (line 482) is only initialized in the "when schemas match exactly" BeforeEach (line 129). This Describe block depends on that prior context having run and set the variable. With ContinueOnFailure, if the earlier context fails, this test would use stale or nil state.

Proposed fix: use a local test name
-			testPath := fmt.Sprintf("%s%s", WebhookPrefix, compatibilityRequirement.Name)
+			requirementName := "test-requirement"
+			testPath := fmt.Sprintf("%s%s", WebhookPrefix, requirementName)
 			req := &http.Request{}
 			req.URL = &url.URL{Path: testPath}

 			ctxWithName := compatibilityRequrementIntoContext(ctx, req)
 			extractedName := compatibilityRequrementFromContext(ctxWithName)

-			Expect(extractedName).To(Equal(compatibilityRequirement.Name))
+			Expect(extractedName).To(Equal(requirementName))
🤖 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 477 - 491, This test depends on the outer variable
compatibilityRequirement.Name set in a different BeforeEach; instead create and
use a local name to avoid cross-context dependency: declare a local const or var
(e.g., testName := "local-cr-name") inside the It block, build testPath with
that local name, pass req through compatibilityRequrementIntoContext and assert
compatibilityRequrementFromContext returns testName (instead of referencing
compatibilityRequirement.Name); update references to the local name in the
testPath and Expect call while keeping compatibilityRequrementIntoContext and
compatibilityRequrementFromContext as the functions under test.
pkg/controllers/crdcompatibility/objectvalidation/webhook.go (1)

102-134: ⚠️ Potential issue | 🟠 Major

Validation action (warn vs deny) is not honored — violations always deny admission.

ValidateCreate and ValidateUpdate always convert validation errors to apierrors.NewInvalid, which denies the request. The ObjectSchemaValidation.Action field (which can be Warn or Deny) is never consulted. Objects that should only trigger warnings under Warn mode will be incorrectly rejected.

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

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go` around lines
102 - 134, ValidateCreate and ValidateUpdate always convert validation errors to
apierrors.NewInvalid and thus deny requests; change them to consult the
validation action (e.g., ObjectSchemaValidation.Action) from the strategy
returned by getValidationStrategy and only produce an apierrors.NewInvalid when
the action is Deny. Concretely: after calling strategy.Validate(...) /
strategy.ValidateUpdate(...), if errs is non-empty check the strategy's action
field (or the ObjectSchemaValidation.Action exposed by the strategy) and if
action == Warn return the warnings and nil, otherwise (action == Deny) return
warnings and apierrors.NewInvalid(obj.GroupVersionKind().GroupKind(),
obj.GetName(), errs).
🧹 Nitpick comments (4)
pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go (1)

83-95: Equal checks deep equality, not pointer identity — doesn't prove caching.

The comment says "Should be the exact same object (cached)" but Equal (line 94) uses reflect.DeepEqual. A freshly constructed but identical strategy would also pass. Use BeIdenticalTo to verify the cached instance is reused:

-			Expect(strategy1).To(Equal(strategy2))
+			Expect(strategy1).To(BeIdenticalTo(strategy2))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go`
around lines 83 - 95, The test "should use cached strategy on subsequent calls"
currently asserts pointer equality using Expect(strategy1).To(Equal(strategy2)),
which uses deep equality; change the assertion to check identity instead by
using Expect(strategy1).To(BeIdenticalTo(strategy2)) (or the equivalent
pointer-identity matcher) so getValidationStrategy's caching is actually
verified for the same instance; locate the assertions around variables
strategy1/strategy2 in the It block and update the matcher accordingly.
pkg/controllers/crdcompatibility/objectvalidation/webhook.go (2)

141-164: Missing double-checked locking — cache miss can cause redundant strategy creation under concurrency.

After getValidationStrategyFromCache returns a miss (line 147), another goroutine may have already populated the cache by the time the write lock is acquired (line 152). The code should re-check the cache after acquiring the write lock to avoid redundant createVersionedStrategy calls:

Proposed fix
 	v.validationStrategyCacheLock.Lock()
 	defer v.validationStrategyCacheLock.Unlock()

+	// Re-check cache after acquiring write lock (double-checked locking)
+	cacheKey := getValidationStrategyCacheKey(compatibilityRequirement, version)
+	if cached, ok := v.validationStrategyCache[cacheKey]; ok {
+		return cached, nil
+	}
+
 	strategy, err := v.createVersionedStrategy(ctx, compatibilityRequirementName, version)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go` around lines
141 - 164, getValidationStrategy has a race where after
getValidationStrategyFromCache returns a miss another goroutine may populate the
cache before you acquire validationStrategyCacheLock, causing redundant
createVersionedStrategy calls; fix by applying double-checked locking: after
acquiring validationStrategyCacheLock in getValidationStrategy call
getValidationStrategyFromCache again and return the found strategy if present,
otherwise proceed to call createVersionedStrategy, then call
storeValidationStrategyInCache and pruneOldValidationStrategies as before (refer
to functions getValidationStrategyFromCache, createVersionedStrategy,
storeValidationStrategyInCache, pruneOldValidationStrategies and the
validationStrategyCacheLock).

206-211: Redundant Get of CompatibilityRequirement — already fetched by caller.

getValidationStrategy (line 143) fetches the CompatibilityRequirement, then createVersionedStrategy (line 208-211) fetches it again. Consider passing the already-fetched object to avoid the extra API call:

Proposed fix
-func (v *validator) createVersionedStrategy(ctx context.Context, compatibilityRequirementName string, version string) (rest.RESTCreateUpdateStrategy, error) {
-	// Get the CompatibilityRequirement
-	compatibilityRequirement := &apiextensionsv1alpha1.CompatibilityRequirement{}
-	if err := v.client.Get(ctx, client.ObjectKey{Name: compatibilityRequirementName}, compatibilityRequirement); err != nil {
-		return nil, fmt.Errorf("failed to get CompatibilityRequirement %q: %w", compatibilityRequirementName, err)
-	}
+func (v *validator) createVersionedStrategy(ctx context.Context, compatibilityRequirement *apiextensionsv1alpha1.CompatibilityRequirement, version string) (rest.RESTCreateUpdateStrategy, error) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go` around lines
206 - 211, The code redundantly fetches the CompatibilityRequirement inside
createVersionedStrategy even though getValidationStrategy already retrieved it;
update createVersionedStrategy to accept a pre-fetched
*apiextensionsv1alpha1.CompatibilityRequirement parameter (instead of name) and
remove the client.Get call, then adjust callers (e.g., getValidationStrategy) to
pass the existing compatibilityRequirement when invoking
createVersionedStrategy; ensure function signature and all call sites are
updated consistently to avoid the extra API call.
pkg/controllers/crdcompatibility/reconcile.go (1)

234-237: Misleading function name: isObjectValidationWebhookEnabled returns true when nothing is configured.

The function returns true when Action == "", MatchConditions == nil, and both selectors are empty — meaning the webhook is not needed. This is the opposite of what "enabled" suggests. Consider renaming to isObjectValidationWebhookDisabled or inverting to isObjectValidationWebhookNeeded.

🤖 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 234 - 237, The
function isObjectValidationWebhookEnabled currently returns true when no object
schema validation is configured (osv.Action == "" && osv.MatchConditions == nil
&& labelSelectorIsEmpty(osv.NamespaceSelector) &&
labelSelectorIsEmpty(osv.ObjectSelector)), which is the opposite of its name;
either rename it to isObjectValidationWebhookDisabled or invert its boolean
logic and rename to isObjectValidationWebhookNeeded, update the return
expression accordingly (negate the current condition or remove the negation if
renaming to Disabled), and update all references to this function throughout the
codebase to use the new name or inverted semantics (ensure any callers relying
on the old truthiness are updated to maintain behavior).
🤖 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/objectvalidation/handle_test.go`:
- Around line 315-333: Update the misleading inline comment near the test case
that reads "This should succeed with looser validation": change it to state that
the creation is expected to fail because the base CRD's own OpenAPI schema
(baseCRD.Spec) still marks requiredField as required, so cl.Create(ctx,
objMissingFormerlyRequired) should return an invalid error; locate the comment
around the test It("should not allow objects without required fields through API
server") and the objMissingFormerlyRequired variable to make this clarification
referencing the CRD schema rather than the webhook.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go`:
- Around line 279-283: The error message in the scale conversion block is
incorrect: when converting subresources.Scale to the internal type (using
Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale)
update the fmt.Errorf call to reference the "scale subresource" instead of
"status subresource" (the relevant symbols are subresources.Scale, scaleSpec,
and
Convert_v1_CustomResourceSubresourceScale_To_apiextensions_CustomResourceSubresourceScale
in the webhook.go code).

---

Outside diff comments:
In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 170-185: The deletion currently uses errors.Join to run
clearFinalizer and removeObjectValidationWebhook in parallel which can remove
the finalizer even if webhook cleanup fails; change reconcileDelete to call
removeObjectValidationWebhook(ctx, obj) first and if it returns an error return
that error (so the finalizer remains), and only after successful webhook removal
call clearFinalizer(ctx, r.client, obj) and return any error from that; stop
using errors.Join so the finalizer is only cleared after
removeObjectValidationWebhook succeeds.

---

Duplicate comments:
In `@pkg/controllers/crdcompatibility/objectvalidation/handle_test.go`:
- Around line 477-491: This test depends on the outer variable
compatibilityRequirement.Name set in a different BeforeEach; instead create and
use a local name to avoid cross-context dependency: declare a local const or var
(e.g., testName := "local-cr-name") inside the It block, build testPath with
that local name, pass req through compatibilityRequrementIntoContext and assert
compatibilityRequrementFromContext returns testName (instead of referencing
compatibilityRequirement.Name); update references to the local name in the
testPath and Expect call while keeping compatibilityRequrementIntoContext and
compatibilityRequrementFromContext as the functions under test.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go`:
- Around line 102-134: ValidateCreate and ValidateUpdate always convert
validation errors to apierrors.NewInvalid and thus deny requests; change them to
consult the validation action (e.g., ObjectSchemaValidation.Action) from the
strategy returned by getValidationStrategy and only produce an
apierrors.NewInvalid when the action is Deny. Concretely: after calling
strategy.Validate(...) / strategy.ValidateUpdate(...), if errs is non-empty
check the strategy's action field (or the ObjectSchemaValidation.Action exposed
by the strategy) and if action == Warn return the warnings and nil, otherwise
(action == Deny) return warnings and
apierrors.NewInvalid(obj.GroupVersionKind().GroupKind(), obj.GetName(), errs).

In `@pkg/controllers/crdcompatibility/reconcile_test.go`:
- Around line 222-255: BeforeAll in this nested Ordered context runs before the
parent's BeforeEach, so testCRDClean is nil when calling
GenerateTestCompatibilityRequirement; fix by declaring and initializing a
locally-scoped CRD (e.g., localCRD) inside this Context and pass that to
GenerateTestCompatibilityRequirement instead of testCRDClean; also update any
later assertions or comparisons that reference testCRDClean (the checks against
the created CRD/requirement) to use localCRD so the test uses the
locally-initialized CRD rather than the uninitialized package variable; keep the
rest of the setup (requirement.Spec.ObjectSchemaValidation, createTestObject,
webhookConfig) unchanged.

In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 156-161: Replace the use of errors.Join (which runs all calls
unconditionally) with a sequential error-check pattern so dependent steps run
only if prior steps succeed: call r.parseCompatibilityCRD(obj) first and return
on error, then call r.fetchCurrentCRD(ctx, logger) and return on error, then
r.checkCompatibilityRequirement() and return on error, then
r.ensureObjectValidationWebhook(ctx, obj) and return on error; also keep or add
a nil-guard around r.compatibilityCRD (used by
validatingWebhookConfigurationFor) to avoid panics if it ever becomes nil.
- Around line 243-282: The ValidatingWebhookConfiguration built in
validatingWebhookConfigurationFor is missing propagation of namespace/object
selectors and match conditions from the CR's spec; update the ValidatingWebhook
(inside validatingWebhookConfigurationFor) to set NamespaceSelector,
ObjectSelector and MatchConditions using values from
obj.Spec.ObjectSchemaValidation (copying those fields into the
admissionregistrationv1.ValidatingWebhook fields), ensuring proper type
conversion if needed so NamespaceSelector and ObjectSelector become
*metav1.LabelSelector and MatchConditions map to
admissionregistrationv1.MatchCondition slice on the webhook; keep the field
names (NamespaceSelector, ObjectSelector, MatchConditions) consistent with
admissionregistrationv1 API and read from obj.Spec.ObjectSchemaValidation to
preserve user intent.
- Around line 187-210: The ensureObjectValidationWebhook function currently
builds a desired webhookConfig then calls r.client.Get into that same object
(overwriting the desired state) and later Update becomes a no-op; also when
isObjectValidationWebhookEnabled(obj) returns true the controller skips creating
but never deletes an existing webhook. Fix by (1) if
isObjectValidationWebhookEnabled(obj) is true, attempt to fetch the existing
ValidatingWebhookConfiguration into a separate variable (e.g., existing :=
&admissionv1.ValidatingWebhookConfiguration{}) and Delete it if found; (2) when
creating/updating, keep your desired webhook (webhookConfig) separate from the
fetched one: call r.client.Get(ctx, NamespacedName{Name: webhookConfig.Name},
fetched) and if NotFound call r.client.Create(ctx, webhookConfig); otherwise
copy fetched.ResourceVersion into webhookConfig (or merge only the
ResourceVersion) before calling r.client.Update(ctx, webhookConfig) so Update
applies the desired content rather than re-saving the fetched object.

---

Nitpick comments:
In `@pkg/controllers/crdcompatibility/objectvalidation/validator_unit_test.go`:
- Around line 83-95: The test "should use cached strategy on subsequent calls"
currently asserts pointer equality using Expect(strategy1).To(Equal(strategy2)),
which uses deep equality; change the assertion to check identity instead by
using Expect(strategy1).To(BeIdenticalTo(strategy2)) (or the equivalent
pointer-identity matcher) so getValidationStrategy's caching is actually
verified for the same instance; locate the assertions around variables
strategy1/strategy2 in the It block and update the matcher accordingly.

In `@pkg/controllers/crdcompatibility/objectvalidation/webhook.go`:
- Around line 141-164: getValidationStrategy has a race where after
getValidationStrategyFromCache returns a miss another goroutine may populate the
cache before you acquire validationStrategyCacheLock, causing redundant
createVersionedStrategy calls; fix by applying double-checked locking: after
acquiring validationStrategyCacheLock in getValidationStrategy call
getValidationStrategyFromCache again and return the found strategy if present,
otherwise proceed to call createVersionedStrategy, then call
storeValidationStrategyInCache and pruneOldValidationStrategies as before (refer
to functions getValidationStrategyFromCache, createVersionedStrategy,
storeValidationStrategyInCache, pruneOldValidationStrategies and the
validationStrategyCacheLock).
- Around line 206-211: The code redundantly fetches the CompatibilityRequirement
inside createVersionedStrategy even though getValidationStrategy already
retrieved it; update createVersionedStrategy to accept a pre-fetched
*apiextensionsv1alpha1.CompatibilityRequirement parameter (instead of name) and
remove the client.Get call, then adjust callers (e.g., getValidationStrategy) to
pass the existing compatibilityRequirement when invoking
createVersionedStrategy; ensure function signature and all call sites are
updated consistently to avoid the extra API call.

In `@pkg/controllers/crdcompatibility/reconcile.go`:
- Around line 234-237: The function isObjectValidationWebhookEnabled currently
returns true when no object schema validation is configured (osv.Action == "" &&
osv.MatchConditions == nil && labelSelectorIsEmpty(osv.NamespaceSelector) &&
labelSelectorIsEmpty(osv.ObjectSelector)), which is the opposite of its name;
either rename it to isObjectValidationWebhookDisabled or invert its boolean
logic and rename to isObjectValidationWebhookNeeded, update the return
expression accordingly (negate the current condition or remove the negation if
renaming to Disabled), and update all references to this function throughout the
codebase to use the new name or inverted semantics (ensure any callers relying
on the old truthiness are updated to maintain behavior).

@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch from 8252156 to 8969e3e Compare February 26, 2026 10:28
@openshift-merge-robot openshift-merge-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Feb 26, 2026
@openshift-ci-robot
Copy link

openshift-ci-robot commented Feb 26, 2026

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

This pull request references OCPCLOUD-3186 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 story to target the "4.22.0" version, but no target version was set.

This pull request references OCPCLOUD-3367 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 implements the object admission validation webhook and ValidatingWebhookConfiguration reconciliation. This will allow folks who want to implement object schema validations to restrict the resources in a particular namespace based on the compatibility requirements CRD

Summary by CodeRabbit

  • New Features

  • Added object validation webhook support for CompatibilityRequirement resources; per-requirement validation runs on create/update and webhook configurations are created/removed automatically.

  • Tests

  • Added extensive unit and end-to-end tests covering webhook integration, validation strategies, caching/invalidation, and create/update/delete behaviors.

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.

@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch 2 times, most recently from f5afc62 to 2c43d65 Compare March 5, 2026 10:56
@theobarberbany
Copy link
Contributor

/test unit

cl client.Client
)

var defaultNodeTimeout = NodeTimeout(10 * time.Second)
Copy link
Contributor

@theobarberbany theobarberbany Mar 5, 2026

Choose a reason for hiding this comment

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

Given slow / laggy CI I might bump this to 30s/1m or split on things we expect to maybe slow vs immediately return

@theobarberbany
Copy link
Contributor

broadly LGTM on changes, a few nits but would be happy to merge..

No idea what's going on with units though :(

@JoelSpeed JoelSpeed force-pushed the compatibility-object-admission branch from 2c43d65 to 9a38bff Compare March 6, 2026 12:12
Copy link
Contributor

@mdbooth mdbooth left a comment

Choose a reason for hiding this comment

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

/lgtm

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Mar 6, 2026
@openshift-ci-robot
Copy link

Scheduling tests matching the pipeline_run_if_changed or not excluded by pipeline_skip_if_only_changed parameters:
/test e2e-aws-capi-techpreview
/test e2e-aws-ovn
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-aws-ovn-techpreview
/test e2e-aws-ovn-techpreview-upgrade
/test e2e-azure-capi-techpreview
/test e2e-azure-ovn-techpreview
/test e2e-azure-ovn-techpreview-upgrade
/test e2e-gcp-capi-techpreview
/test e2e-gcp-ovn-techpreview
/test e2e-metal3-capi-techpreview
/test e2e-openstack-capi-techpreview
/test e2e-openstack-ovn-techpreview
/test e2e-vsphere-capi-techpreview
/test regression-clusterinfra-aws-ipi-techpreview-capi

@openshift-ci
Copy link
Contributor

openshift-ci bot commented Mar 6, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mdbooth

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

The pull request process is described 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

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 6, 2026
@JoelSpeed
Copy link
Contributor Author

/verified by @sunzhaohua2

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Mar 6, 2026
@openshift-ci-robot
Copy link

@JoelSpeed: This PR has been marked as verified by @sunzhaohua2.

Details

In response to this:

/verified by @sunzhaohua2

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 6, 2026

@JoelSpeed: The following tests 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/regression-clusterinfra-aws-ipi-techpreview-capi 9a38bff link false /test regression-clusterinfra-aws-ipi-techpreview-capi
ci/prow/e2e-openstack-ovn-techpreview 9a38bff link true /test e2e-openstack-ovn-techpreview
ci/prow/e2e-gcp-ovn-techpreview 9a38bff link true /test e2e-gcp-ovn-techpreview
ci/prow/e2e-aws-ovn-techpreview 9a38bff link true /test e2e-aws-ovn-techpreview

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.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants