Skip to content

Conversation

@vrutkovs
Copy link
Contributor

@vrutkovs vrutkovs commented Aug 6, 2025

In order to make controller sync less often this commit reworks it:

  • remove unused informers
  • filter events - run sync when either source or target configmaps change
  • use listers instead of direct client gets
  • avoid applies unless configmap contents change

@openshift-ci openshift-ci bot requested review from deads2k and dgrisonnet August 6, 2025 08:05
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Aug 6, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: vrutkovs

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

The pull request process is described here

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 Aug 6, 2025
@vrutkovs vrutkovs force-pushed the nodekubeconfig-lister branch from 749cf43 to 686a4ea Compare August 6, 2025 08:30
@vrutkovs
Copy link
Contributor Author

vrutkovs commented Aug 6, 2025

/test e2e-short-cert-rotation

@vrutkovs vrutkovs force-pushed the nodekubeconfig-lister branch 3 times, most recently from 338d30a to a82359c Compare August 6, 2025 09:49
@vrutkovs
Copy link
Contributor Author

vrutkovs commented Aug 6, 2025

/test e2e-short-cert-rotation

@vrutkovs vrutkovs force-pushed the nodekubeconfig-lister branch from a82359c to d2115d0 Compare August 13, 2025 04:38
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Aug 13, 2025

@vrutkovs: 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/okd-scos-e2e-aws-ovn d2115d0 link false /test okd-scos-e2e-aws-ovn
ci/prow/e2e-aws-ovn d2115d0 link true /test e2e-aws-ovn
ci/prow/e2e-aws-operator-disruptive-single-node d2115d0 link false /test e2e-aws-operator-disruptive-single-node
ci/prow/e2e-gcp-operator-single-node d2115d0 link false /test e2e-gcp-operator-single-node

Full PR test history. Your PR dashboard.

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.

@openshift-bot
Copy link
Contributor

Issues go stale after 90d of inactivity.

Mark the issue as fresh by commenting /remove-lifecycle stale.
Stale issues rot after an additional 30d of inactivity and eventually close.
Exclude this issue from closing by commenting /lifecycle frozen.

If this issue is safe to close now please do so with /close.

/lifecycle stale

@openshift-ci openshift-ci bot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Nov 11, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

This pull request refactors the NodeKubeconfigController to use filtered informers with selective event handling for specific ConfigMaps and Secrets. It introduces resource name constants, adds a pre-apply validation check comparing existing secrets against desired state using deep equality, and includes a new test case for annotation-only updates. Additionally, a parameter name typo is corrected.

Changes

Cohort / File(s) Summary
Main controller refactoring
pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go
Added runtime and apierrors imports; introduced constants for CA config and admin client resource names; refactored constructor to use filtered-informer approach with predicate logic targeting specific ConfigMaps (kube-apiserver-server-ca) and Secrets (node-system-admin-client); replaced hard-coded names with constants; added pre-apply validation check using reflect.DeepEqual to skip unnecessary secret updates; corrected parameter name from infrastuctureInformer to infrastructureInformer.
Test additions
pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go
Added new test case "annotations only update" to TestEnsureNodeKubeconfigs validating that annotation-only changes trigger secret recreation with updated annotations and preserved kubeconfig data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Filtered informer predicate logic: Verify that the selective event handling correctly targets only the intended ConfigMaps and Secrets while allowing other events to pass through
  • Pre-apply comparison logic: Review the reflect.DeepEqual comparison and ensure apierrors.IsNotFound properly distinguishes between creation and update flows
  • Public API change: Confirm the parameter name correction from infrastuctureInformer to infrastructureInformer is applied consistently across all call sites
  • New test coverage: Validate that the "annotations only update" test scenario accurately represents the intended behavior and properly exercises the updated controller logic
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

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

♻️ Duplicate comments (1)
pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go (1)

183-193: Critical: Optimization is broken—StringData not converted to Data before comparison.

The code populates only requiredSecret.StringData (lines 157-169) but compares requiredSecret.Data against actualSecret.Data (line 188). Since requiredSecret.Data is never initialized, it remains nil while actualSecret.Data (fetched from the API lister) contains the actual decoded secret data. This causes the comparison to always fail, bypassing the optimization and triggering unnecessary applies every reconciliation.

Convert StringData to Data before the comparison:

 	requiredSecret.Annotations[certrotation.CertificateNotAfterAnnotation] = systemAdminCredsSecret.Annotations[certrotation.CertificateNotAfterAnnotation]
 	}
 
+	// Convert StringData to Data for comparison
+	if requiredSecret.Data == nil {
+		requiredSecret.Data = make(map[string][]byte)
+	}
+	for k, v := range requiredSecret.StringData {
+		requiredSecret.Data[k] = []byte(v)
+	}
+	requiredSecret.StringData = nil
+
 	actualSecret, err := secretLister.Secrets(requiredSecret.Namespace).Get(requiredSecret.Name)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 05014ca and d2115d0.

📒 Files selected for processing (2)
  • pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go (5 hunks)
  • pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

-Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity.

Files:

  • pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go
  • pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go
🔇 Additional comments (5)
pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go (5)

7-7: LGTM: Import additions support new functionality.

The reflect import enables the deep equality check for the pre-apply optimization, and apierrors is used to distinguish NotFound errors from other errors.

Also applies to: 24-24


30-34: LGTM: Constants improve maintainability.

Extracting resource names into constants eliminates magic strings and makes the code more maintainable.


49-49: LGTM: Parameter name typo corrected.

Fixed typo: infrastuctureInformerinfrastructureInformer.


60-75: Verify the filter passes through non-ConfigMap/Secret events as intended.

The filter logic returns true for all events except specific ConfigMap and Secret resources. This means:

  • ConfigMaps: only kube-apiserver-server-ca in target namespace triggers sync
  • Secrets: only node-system-admin-client in operator namespace triggers sync
  • All other informer events (Infrastructure, OperatorClient) pass through

Ensure this is the intended behavior, as line 74 allows all non-ConfigMap/Secret events to trigger reconciliation.


121-121: LGTM: Improved error messages with constants and namespace/name format.

Error messages now use constants and follow the namespace/name format for better clarity.

Also applies to: 128-128, 132-132, 135-135, 141-141

Comment on lines +368 to +468
{
name: "annotations only update",
existingObjects: []runtime.Object{
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-kube-apiserver",
Name: "kube-apiserver-server-ca",
},
Data: map[string]string{
"ca-bundle.crt": "kube-apiserver-server-ca certificate",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-kube-apiserver-operator",
Name: "node-system-admin-client",
Annotations: map[string]string{
certrotation.CertificateNotBeforeAnnotation: certNotBefore,
certrotation.CertificateNotAfterAnnotation: certNotAfter,
},
},
Data: map[string][]byte{
"tls.crt": []byte(publicKey),
"tls.key": []byte(privateKey),
},
},
&corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-kube-apiserver",
Name: "node-kubeconfigs",
Annotations: map[string]string{
annotations.OpenShiftComponent: "kube-apiserver",
certrotation.CertificateNotBeforeAnnotation: "some-old-not-before",
certrotation.CertificateNotAfterAnnotation: "some-old-not-after",
},
},
Data: map[string][]byte{
"localhost.kubeconfig": generateKubeConfig("localhost", "https://localhost:6443"),
"localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"),
"lb-ext.kubeconfig": generateKubeConfig("lb-ext", lbExtServer),
"lb-int.kubeconfig": generateKubeConfig("lb-int", lbIntServer),
},
},
},
infrastructure: &configv1.Infrastructure{
ObjectMeta: metav1.ObjectMeta{
Namespace: "",
Name: "cluster",
},
Status: configv1.InfrastructureStatus{
APIServerURL: lbExtServer,
APIServerInternalURL: lbIntServer,
},
},
expectedErr: nil,
expectedActions: []clienttesting.Action{
clienttesting.DeleteActionImpl{
ActionImpl: clienttesting.ActionImpl{
Namespace: "openshift-kube-apiserver",
Verb: "delete",
Resource: corev1.SchemeGroupVersion.WithResource("secrets"),
},
Name: "node-kubeconfigs",
},
clienttesting.CreateActionImpl{
ActionImpl: clienttesting.ActionImpl{
Namespace: "openshift-kube-apiserver",
Verb: "create",
Resource: corev1.SchemeGroupVersion.WithResource("secrets"),
},
Object: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-kube-apiserver",
Name: "node-kubeconfigs",
Labels: map[string]string{},
OwnerReferences: []metav1.OwnerReference{},
Annotations: map[string]string{
annotations.OpenShiftComponent: "kube-apiserver",
certrotation.CertificateNotBeforeAnnotation: certNotBefore,
certrotation.CertificateNotAfterAnnotation: certNotAfter,
},
},
Data: map[string][]byte{
"localhost.kubeconfig": generateKubeConfig("localhost", "https://localhost:6443"),
"localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"),
"lb-ext.kubeconfig": generateKubeConfig("lb-ext", lbExtServer),
"lb-int.kubeconfig": generateKubeConfig("lb-int", lbIntServer),
},
Type: corev1.SecretTypeOpaque,
},
},
},
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Good test coverage for annotation updates, but missing no-op test case.

The new "annotations only update" test validates that annotation changes trigger a delete+recreate, which is correct.

However, to properly validate the pre-apply optimization introduced in this PR (lines 183-191 in nodekubeconfigcontroller.go), you should add a test case where the existing secret matches the desired secret exactly (same Data AND Annotations). In this case, the expected actions should be empty (no delete, no create, no update).

Add this test case after the current one:

{
	name: "no-op when secret matches exactly",
	existingObjects: []runtime.Object{
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "openshift-kube-apiserver",
				Name:      "kube-apiserver-server-ca",
			},
			Data: map[string]string{
				"ca-bundle.crt": "kube-apiserver-server-ca certificate",
			},
		},
		&corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "openshift-kube-apiserver-operator",
				Name:      "node-system-admin-client",
				Annotations: map[string]string{
					certrotation.CertificateNotBeforeAnnotation: certNotBefore,
					certrotation.CertificateNotAfterAnnotation:  certNotAfter,
				},
			},
			Data: map[string][]byte{
				"tls.crt": []byte(publicKey),
				"tls.key": []byte(privateKey),
			},
		},
		&corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "openshift-kube-apiserver",
				Name:      "node-kubeconfigs",
				Annotations: map[string]string{
					annotations.OpenShiftComponent:              "kube-apiserver",
					certrotation.CertificateNotBeforeAnnotation: certNotBefore,
					certrotation.CertificateNotAfterAnnotation:  certNotAfter,
				},
			},
			Data: map[string][]byte{
				"localhost.kubeconfig":          generateKubeConfig("localhost", "https://localhost:6443"),
				"localhost-recovery.kubeconfig": generateKubeConfig("localhost-recovery", "https://localhost:6443"),
				"lb-ext.kubeconfig":             generateKubeConfig("lb-ext", lbExtServer),
				"lb-int.kubeconfig":             generateKubeConfig("lb-int", lbIntServer),
			},
		},
	},
	infrastructure: &configv1.Infrastructure{
		ObjectMeta: metav1.ObjectMeta{
			Name: "cluster",
		},
		Status: configv1.InfrastructureStatus{
			APIServerURL:         lbExtServer,
			APIServerInternalURL: lbIntServer,
		},
	},
	expectedErr:     nil,
	expectedActions: []clienttesting.Action{}, // No actions expected
},
🤖 Prompt for AI Agents
In pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go around
lines 368 to 468, add a new table-driven test case immediately after the
"annotations only update" case named "no-op when secret matches exactly" that
supplies existingObjects containing the same ConfigMap, the same
node-system-admin-client Secret, and a node-kubeconfigs Secret whose Data and
Annotations exactly match the desired output (use certNotBefore/certNotAfter for
annotations and the same generateKubeConfig outputs and lbExt/lbInt values),
with the same infrastructure object as the other tests; set expectedErr to nil
and expectedActions to an empty slice so the test asserts no
delete/create/update occurs.

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. lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants