diff --git a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go index 6909860582..7a10b02cae 100644 --- a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go +++ b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "fmt" + "reflect" "strings" "time" @@ -20,12 +21,17 @@ import ( "github.com/openshift/library-go/pkg/operator/resource/resourceread" "github.com/openshift/library-go/pkg/operator/v1helpers" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/client-go/kubernetes" coreclientv1 "k8s.io/client-go/kubernetes/typed/core/v1" corev1listers "k8s.io/client-go/listers/core/v1" ) -const workQueueKey = "key" +const ( + workQueueKey = "key" + kubeApiserverServerCA = "kube-apiserver-server-ca" + nodeSystemAdminClient = "node-system-admin-client" +) type NodeKubeconfigController struct { operatorClient v1helpers.StaticPodOperatorClient @@ -40,7 +46,7 @@ func NewNodeKubeconfigController( operatorClient v1helpers.StaticPodOperatorClient, kubeInformersForNamespaces v1helpers.KubeInformersForNamespaces, kubeClient kubernetes.Interface, - infrastuctureInformer configv1informers.InfrastructureInformer, + infrastructureInformer configv1informers.InfrastructureInformer, eventRecorder events.Recorder, ) factory.Controller { c := &NodeKubeconfigController{ @@ -48,16 +54,29 @@ func NewNodeKubeconfigController( kubeClient: kubeClient, configMapLister: kubeInformersForNamespaces.ConfigMapLister(), secretLister: kubeInformersForNamespaces.SecretLister(), - infrastructureLister: infrastuctureInformer.Lister(), - } - - return factory.New().WithInformers( + infrastructureLister: infrastructureInformer.Lister(), + } + + return factory.New().WithFilteredEventsInformers( + func(obj interface{}) bool { + if cm, ok := obj.(*corev1.ConfigMap); ok { + if cm.Namespace == operatorclient.TargetNamespace && cm.Name == kubeApiserverServerCA { + return true + } + return false + } + if secret, ok := obj.(*corev1.Secret); ok { + if secret.Namespace == operatorclient.OperatorNamespace && secret.Name == nodeSystemAdminClient { + return true + } + return false + } + return true + }, operatorClient.Informer(), - kubeInformersForNamespaces.InformersFor(operatorclient.OperatorNamespace).Core().V1().ConfigMaps().Informer(), kubeInformersForNamespaces.InformersFor(operatorclient.TargetNamespace).Core().V1().ConfigMaps().Informer(), kubeInformersForNamespaces.InformersFor(operatorclient.OperatorNamespace).Core().V1().Secrets().Informer(), - kubeInformersForNamespaces.InformersFor(operatorclient.TargetNamespace).Core().V1().Secrets().Informer(), - infrastuctureInformer.Informer(), + infrastructureInformer.Informer(), ).WithSync(c.sync).WithSyncDegradedOnError(c.operatorClient).ResyncEvery(5*time.Minute).ToController("NodeKubeconfigController", eventRecorder.WithComponentSuffix("node-kubeconfig-controller")) } @@ -99,27 +118,27 @@ func (c NodeKubeconfigController) sync(ctx context.Context, syncContext factory. func ensureNodeKubeconfigs(ctx context.Context, client coreclientv1.CoreV1Interface, secretLister corev1listers.SecretLister, configmapLister corev1listers.ConfigMapLister, infrastructureLister configv1listers.InfrastructureLister, recorder events.Recorder) error { requiredSecret := resourceread.ReadSecretV1OrDie(bindata.MustAsset("assets/kube-apiserver/node-kubeconfigs.yaml")) - systemAdminCredsSecret, err := secretLister.Secrets(operatorclient.OperatorNamespace).Get("node-system-admin-client") + systemAdminCredsSecret, err := secretLister.Secrets(operatorclient.OperatorNamespace).Get(nodeSystemAdminClient) if err != nil { return err } systemAdminClientCert := systemAdminCredsSecret.Data[corev1.TLSCertKey] if len(systemAdminClientCert) == 0 { - return fmt.Errorf("system:admin client certificate missing from secret %s/node-system-admin-client", operatorclient.OperatorNamespace) + return fmt.Errorf("system:admin client certificate missing from secret %s/%s", operatorclient.OperatorNamespace, nodeSystemAdminClient) } systemAdminClientKey := systemAdminCredsSecret.Data[corev1.TLSPrivateKeyKey] if len(systemAdminClientKey) == 0 { - return fmt.Errorf("system:admin client private key missing from secret %s/node-system-admin-client", operatorclient.OperatorNamespace) + return fmt.Errorf("system:admin client private key missing from secret %s/%s", operatorclient.OperatorNamespace, nodeSystemAdminClient) } - servingCABundleCM, err := configmapLister.ConfigMaps(operatorclient.TargetNamespace).Get("kube-apiserver-server-ca") + servingCABundleCM, err := configmapLister.ConfigMaps(operatorclient.TargetNamespace).Get(kubeApiserverServerCA) if err != nil { return err } servingCABundleData := servingCABundleCM.Data["ca-bundle.crt"] if len(servingCABundleData) == 0 { - return fmt.Errorf("serving CA bundle missing from configmap %s/kube-apiserver-server-ca", operatorclient.TargetNamespace) + return fmt.Errorf("serving CA bundle missing from configmap %s/%s", operatorclient.TargetNamespace, kubeApiserverServerCA) } infrastructure, err := infrastructureLister.Get("cluster") @@ -161,10 +180,15 @@ func ensureNodeKubeconfigs(ctx context.Context, client coreclientv1.CoreV1Interf requiredSecret.Annotations[certrotation.CertificateNotAfterAnnotation] = systemAdminCredsSecret.Annotations[certrotation.CertificateNotAfterAnnotation] } - _, _, err = resourceapply.ApplySecret(ctx, client, recorder, requiredSecret) - if err != nil { - return err + actualSecret, err := secretLister.Secrets(requiredSecret.Namespace).Get(requiredSecret.Name) + if !apierrors.IsNotFound(err) { + if err != nil { + return err + } + if reflect.DeepEqual(actualSecret.Data, requiredSecret.Data) && reflect.DeepEqual(actualSecret.Annotations, requiredSecret.Annotations) { + return nil + } } - - return nil + _, _, err = resourceapply.ApplySecret(ctx, client, recorder, requiredSecret) + return err } diff --git a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go index 341af79e90..4e74c8162a 100644 --- a/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go +++ b/pkg/operator/nodekubeconfigcontroller/nodekubeconfigcontroller_test.go @@ -365,6 +365,107 @@ func TestEnsureNodeKubeconfigs(t *testing.T) { }, }, }, + { + 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, + }, + }, + }, + }, } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) {