Skip to content

DNM: add virt annotation remover webhook#2088

Open
weshayutin wants to merge 3 commits intoopenshift:oadp-devfrom
weshayutin:virt_annotation_webhook
Open

DNM: add virt annotation remover webhook#2088
weshayutin wants to merge 3 commits intoopenshift:oadp-devfrom
weshayutin:virt_annotation_webhook

Conversation

@weshayutin
Copy link
Contributor

Why the changes were made

Unclear atm how quickly the kubevirt org will be able to remove velero annotations:
https://issues.redhat.com/browse/CNV-75370

How to test the changes made

apiVersion: oadp.openshift.io/v1alpha1
kind: DataProtectionApplication
metadata:
  name: dpa-sample
  namespace: openshift-adp
spec:
  kubevirtAnnotationsRemover:
    enable: true
  backupLocations:
  - velero:
      config:

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Walkthrough

Adds a KubeVirt Annotations Remover feature: API types and deepcopy support, CRD/CSV and RBAC updates, manager env var, new reconciler managing Service/Deployment/MutatingWebhookConfiguration, readiness integration, and comprehensive unit tests.

Changes

Cohort / File(s) Summary
API Type & DeepCopy
api/v1alpha1/dataprotectionapplication_types.go, api/v1alpha1/zz_generated.deepcopy.go
Adds KubevirtAnnotationsRemover type (Enable *bool), new kubevirtAnnotationsRemover field on DataProtectionApplicationSpec, readiness constant ConditionKubevirtAnnotationsRemoverReady, image key kubevirtAnnotationsRemoverImageFqin, and generated deepcopy methods/handling.
Manifests: CSV & CRD
bundle/manifests/oadp-operator.clusterserviceversion.yaml, bundle/manifests/oadp.openshift.io_dataprotectionapplications.yaml
Adds RELATED_IMAGE and relatedImages entry for kubevirt-annotations-remover, new manager container reference, and CRD schema addition for kubevirtAnnotationsRemover including unsupported override key.
Manager Config & RBAC
config/manager/manager.yaml, config/rbac/role.yaml
Adds RELATED_IMAGE_KUBEVIRT_ANNOTATIONS_REMOVER env var to manager config and new ClusterRole rule permitting mutatingwebhookconfigurations (admissionregistration.k8s.io) with full verbs.
Controller Integration
internal/controller/dataprotectionapplication_controller.go
Inserts ReconcileKubevirtAnnotationsRemover into the reconciliation batch sequence.
KubeVirt Annotations Remover Controller
internal/controller/kubevirt_annotations_remover.go
New reconciler implementing enablement check, Service (with OpenShift CA annotation), Deployment (builder, image resolution via overrides/env/default, volumes, security), cluster-scoped MutatingWebhookConfiguration, cleanup logic, events, and owner references.
Readiness
internal/controller/readiness.go
Adds updateKubevirtAnnotationsRemoverReadinessCondition and integrates KubevirtAnnotationsRemover readiness into overall readiness evaluation.
Tests
internal/controller/kubevirt_annotations_remover_test.go
Adds comprehensive Ginkgo/Gomega test suite covering reconciliation paths, deployment build, image resolution, enablement checks, readiness behaviors, and event assertions.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

@openshift-ci
Copy link

openshift-ci bot commented Feb 11, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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

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

Copy link
Contributor

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

🤖 Fix all issues with AI agents
In `@config/rbac/role.yaml`:
- Around line 38-49: The MutatingWebhookConfiguration RBAC rule grants
unnecessary permissions; edit the role definition in config/rbac/role.yaml and
remove the "list" and "watch" verbs for apiGroups: admissionregistration.k8s.io
/ resources: mutatingwebhookconfigurations so the verbs are exactly "create",
"delete", "get", "patch", "update" (this aligns with how
internal/controller/kubevirt_annotations_remover.go uses Get (line ~75), Delete
(line ~84) and CreateOrUpdate (around line ~418) for the fixed-name webhook).
🧹 Nitpick comments (2)
internal/controller/kubevirt_annotations_remover_test.go (1)

532-543: Use strings.Contains instead of custom helpers.

The containsStr and containsSubstring functions duplicate functionality provided by Go's standard library strings.Contains.

♻️ Suggested simplification
+import "strings"

-func containsStr(s, substr string) bool {
-	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsSubstring(s, substr))
-}
-
-func containsSubstring(s, substr string) bool {
-	for i := 0; i <= len(s)-len(substr); i++ {
-		if s[i:i+len(substr)] == substr {
-			return true
-		}
-	}
-	return false
-}

Then replace containsStr(err.Error(), tt.errorContains) with strings.Contains(err.Error(), tt.errorContains).

internal/controller/kubevirt_annotations_remover.go (1)

72-72: Remove unused log parameters.

The log parameter is declared but unused in these functions. Either use the logger for debugging/tracing or remove the parameter.

♻️ Option 1: Remove unused parameters
-func (r *DataProtectionApplicationReconciler) cleanupKubevirtAnnotationsRemover(log logr.Logger) (bool, error) {
+func (r *DataProtectionApplicationReconciler) cleanupKubevirtAnnotationsRemover() (bool, error) {

-func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverService(log logr.Logger) error {
+func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverService() error {

-func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverDeployment(log logr.Logger) error {
+func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverDeployment() error {

-func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverWebhook(log logr.Logger) error {
+func (r *DataProtectionApplicationReconciler) reconcileKubevirtAnnotationsRemoverWebhook() error {

And update the callers in ReconcileKubevirtAnnotationsRemover accordingly.

Also applies to: 182-182, 239-239, 407-407

Comment on lines +38 to +49
- apiGroups:
- admissionregistration.k8s.io
resources:
- mutatingwebhookconfigurations
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the overall structure and locate the controller code
fd --type f -e go | head -20

Repository: openshift/oadp-operator

Length of output: 925


🏁 Script executed:

# Search for MutatingWebhookConfiguration usage in Go files
rg -n 'MutatingWebhookConfiguration' --type go -A 3 -B 3

Repository: openshift/oadp-operator

Length of output: 5404


🏁 Script executed:

# Search for List and Watch patterns related to MutatingWebhookConfiguration
rg -n '(List|Watch).*MutatingWebhookConfiguration|MutatingWebhookConfiguration.*(List|Watch)' --type go -A 2 -B 2

Repository: openshift/oadp-operator

Length of output: 49


🏁 Script executed:

# Check for client operations on MutatingWebhookConfiguration
rg -n 'mutatingwebhookconfigurations|webhook.*config' --type go -i -A 2 | head -50

Repository: openshift/oadp-operator

Length of output: 4585


Remove unnecessary list and watch verbs from MutatingWebhookConfiguration RBAC rule.

The controller in internal/controller/kubevirt_annotations_remover.go manages only a single webhook configuration by fixed name (see line 405-406). The actual operations used are: get (line 75), delete (line 84), and create/update/patch via CreateOrUpdate (line 418). The list and watch verbs are not used and should be removed to maintain least privilege.

Update the verbs to: create, delete, get, patch, update.

🧰 Tools
🪛 Trivy (0.69.1)

[error] 38-49: Manage webhookconfigurations

ClusterRole 'manager-role' should not have access to resources ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] for verbs ["create", "update", "patch", "delete", "deletecollection", "impersonate", "*"]

Rule: KSV-0114

Learn more

(IaC/Kubernetes)

🤖 Prompt for AI Agents
In `@config/rbac/role.yaml` around lines 38 - 49, The MutatingWebhookConfiguration
RBAC rule grants unnecessary permissions; edit the role definition in
config/rbac/role.yaml and remove the "list" and "watch" verbs for apiGroups:
admissionregistration.k8s.io / resources: mutatingwebhookconfigurations so the
verbs are exactly "create", "delete", "get", "patch", "update" (this aligns with
how internal/controller/kubevirt_annotations_remover.go uses Get (line ~75),
Delete (line ~84) and CreateOrUpdate (around line ~418) for the fixed-name
webhook).

@weshayutin
Copy link
Contributor Author

/retest

Copy link
Contributor

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/readiness.go`:
- Around line 301-310: The function
updateKubevirtAnnotationsRemoverReadinessCondition is left unterminated and its
logic was accidentally merged into updateKubevirtDatamoverReadinessCondition,
causing duplicate Name fields and duplicate isReady assignments and wrong
condition types; fix by restoring separation: close
updateKubevirtAnnotationsRemoverReadinessCondition with its own return path
(handle !r.checkKubevirtAnnotationsRemoverEnabled() by setting
apimeta.SetStatusCondition with
oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady and returning), then
remove all KubevirtAnnotationsRemover-specific code from
updateKubevirtDatamoverReadinessCondition, ensure that
updateKubevirtDatamoverReadinessCondition uses
r.checkKubevirtDatamoverEnabled(), sets apimeta.SetStatusCondition with
oadpv1alpha1.ConditionKubevirtDatamoverReady, eliminates duplicate Name fields
and duplicate isReady assignments, and return the correct (isReady, error) from
each function.

Comment on lines +301 to +310
// updateKubevirtAnnotationsRemoverReadinessCondition checks the kubevirt-velero-annotations-remover
// deployment readiness and updates the condition.
func (r *DataProtectionApplicationReconciler) updateKubevirtAnnotationsRemoverReadinessCondition() (bool, error) {
// If not enabled, mark as disabled
if !r.checkKubevirtAnnotationsRemoverEnabled() {
apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
Type: oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
Status: metav1.ConditionTrue,
Reason: oadpv1alpha1.ReasonComponentDisabled,
Message: "KubeVirt annotations remover webhook is disabled",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix broken readiness methods (compile error + mixed conditions).

Lines 301-310 and Lines 323-367 leave updateKubevirtAnnotationsRemoverReadinessCondition unterminated and inject KubevirtAnnotationsRemover logic into updateKubevirtDatamoverReadinessCondition, including a duplicate Name field and duplicate isReady assignment. This breaks the build and misreports readiness.

🔧 Proposed fix to separate the two readiness paths
 func (r *DataProtectionApplicationReconciler) updateKubevirtAnnotationsRemoverReadinessCondition() (bool, error) {
 	// If not enabled, mark as disabled
 	if !r.checkKubevirtAnnotationsRemoverEnabled() {
 		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
 			Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
 			Status:  metav1.ConditionTrue,
 			Reason:  oadpv1alpha1.ReasonComponentDisabled,
 			Message: "KubeVirt annotations remover webhook is disabled",
-        })
+		})
+		return true, nil
+	}
+
+	deployment := &appsv1.Deployment{}
+	err := r.Get(r.Context, types.NamespacedName{
+		Name:      kubevirtAnnotationsRemoverName,
+		Namespace: r.dpa.Namespace,
+	}, deployment)
+	if err != nil {
+		if k8serror.IsNotFound(err) {
+			apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
+				Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
+				Status:  metav1.ConditionFalse,
+				Reason:  oadpv1alpha1.ReasonComponentNotFound,
+				Message: "KubeVirt annotations remover deployment not found",
+			})
+			return false, nil
+		}
+		return false, err
+	}
+
+	replicas := int32(1)
+	if deployment.Spec.Replicas != nil {
+		replicas = *deployment.Spec.Replicas
+	}
+	isReady := deployment.Status.ReadyReplicas >= 1 &&
+		deployment.Status.ReadyReplicas == replicas
+
+	if isReady {
+		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
+			Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
+			Status:  metav1.ConditionTrue,
+			Reason:  oadpv1alpha1.ReasonDeploymentReady,
+			Message: fmt.Sprintf("KubeVirt annotations remover ready: %d/%d replicas", deployment.Status.ReadyReplicas, replicas),
+		})
+	} else {
+		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
+			Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
+			Status:  metav1.ConditionFalse,
+			Reason:  oadpv1alpha1.ReasonDeploymentNotReady,
+			Message: fmt.Sprintf("KubeVirt annotations remover not ready: %d/%d replicas ready", deployment.Status.ReadyReplicas, replicas),
+		})
 	}
-
-func (r *DataProtectionApplicationReconciler) updateKubevirtDatamoverReadinessCondition() (bool, error) {
+	return isReady, nil
+}
+
+func (r *DataProtectionApplicationReconciler) updateKubevirtDatamoverReadinessCondition() (bool, error) {
 	// If plugin not enabled, mark as disabled (which counts as ready)
 	if !r.checkKubevirtDatamoverEnabled() {
 		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
 			Type:    oadpv1alpha1.ConditionKubevirtDatamoverReady,
 			Status:  metav1.ConditionTrue,
@@
 	deployment := &appsv1.Deployment{}
 	err := r.Get(r.Context, types.NamespacedName{
-		Name:      kubevirtAnnotationsRemoverName,
 		Name:      kubevirtDatamoverObjectName,
 		Namespace: r.dpa.Namespace,
 	}, deployment)
 
 	if err != nil {
 		if k8serror.IsNotFound(err) {
 			apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
-				Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
-				Status:  metav1.ConditionFalse,
-				Reason:  oadpv1alpha1.ReasonComponentNotFound,
-				Message: "KubeVirt annotations remover deployment not found",
 				Type:    oadpv1alpha1.ConditionKubevirtDatamoverReady,
 				Status:  metav1.ConditionFalse,
 				Reason:  oadpv1alpha1.ReasonComponentNotFound,
 				Message: "KubeVirt DataMover controller deployment not found",
 			})
 			return false, nil
@@
-	isReady := deployment.Status.ReadyReplicas >= 1 &&
-		deployment.Status.ReadyReplicas == replicas
-
-	if isReady {
-		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
-			Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
-			Status:  metav1.ConditionTrue,
-			Reason:  oadpv1alpha1.ReasonDeploymentReady,
-			Message: fmt.Sprintf("KubeVirt annotations remover ready: %d/%d replicas", deployment.Status.ReadyReplicas, replicas),
-		})
-	} else {
-		apimeta.SetStatusCondition(&r.dpa.Status.Conditions, metav1.Condition{
-			Type:    oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady,
-			Status:  metav1.ConditionFalse,
-			Reason:  oadpv1alpha1.ReasonDeploymentNotReady,
-			Message: fmt.Sprintf("KubeVirt annotations remover not ready: %d/%d replicas ready", deployment.Status.ReadyReplicas, replicas),
-		})
-	}
-
-	isReady := deployment.Status.ReadyReplicas >= 1 && deployment.Status.ReadyReplicas == replicas
+	isReady := deployment.Status.ReadyReplicas >= 1 && deployment.Status.ReadyReplicas == replicas

Also applies to: 323-367

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

In `@internal/controller/readiness.go` around lines 301 - 310, The function
updateKubevirtAnnotationsRemoverReadinessCondition is left unterminated and its
logic was accidentally merged into updateKubevirtDatamoverReadinessCondition,
causing duplicate Name fields and duplicate isReady assignments and wrong
condition types; fix by restoring separation: close
updateKubevirtAnnotationsRemoverReadinessCondition with its own return path
(handle !r.checkKubevirtAnnotationsRemoverEnabled() by setting
apimeta.SetStatusCondition with
oadpv1alpha1.ConditionKubevirtAnnotationsRemoverReady and returning), then
remove all KubevirtAnnotationsRemover-specific code from
updateKubevirtDatamoverReadinessCondition, ensure that
updateKubevirtDatamoverReadinessCondition uses
r.checkKubevirtDatamoverEnabled(), sets apimeta.SetStatusCondition with
oadpv1alpha1.ConditionKubevirtDatamoverReady, eliminates duplicate Name fields
and duplicate isReady assignments, and return the correct (isReady, error) from
each function.

@openshift-ci
Copy link

openshift-ci bot commented Feb 17, 2026

@weshayutin: 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/unit-test 01c9797 link true /test unit-test
ci/prow/4.22-ci-index 01c9797 link true /test 4.22-ci-index
ci/prow/4.21-images 01c9797 link true /test 4.21-images
ci/prow/4.22-images 01c9797 link true /test 4.22-images
ci/prow/5.0-images 01c9797 link true /test 5.0-images
ci/prow/5.0-e2e-test-aws 01c9797 link false /test 5.0-e2e-test-aws
ci/prow/5.0-ci-index 01c9797 link true /test 5.0-ci-index
ci/prow/4.21-e2e-test-cli-aws 01c9797 link false /test 4.21-e2e-test-cli-aws
ci/prow/4.21-ci-index 01c9797 link true /test 4.21-ci-index
ci/prow/4.21-e2e-test-hcp-aws 01c9797 link true /test 4.21-e2e-test-hcp-aws
ci/prow/4.21-e2e-test-aws 01c9797 link true /test 4.21-e2e-test-aws
ci/prow/4.22-e2e-test-aws 01c9797 link false /test 4.22-e2e-test-aws
ci/prow/images 01c9797 link true /test images
ci/prow/4.21-e2e-test-kubevirt-aws 01c9797 link true /test 4.21-e2e-test-kubevirt-aws

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.

- create
- delete
- get
- list
Copy link
Member

Choose a reason for hiding this comment

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

why do we need list?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants