Explorar el Código

fix: watch kubernetes provider config changes

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner hace 2 meses
padre
commit
fd7e71410f

+ 0 - 16
e2e/suites/provider/cases/kubernetes/provider_v2.go

@@ -192,7 +192,6 @@ var _ = Describe("[kubernetes] v2 namespaced provider", Label("kubernetes", "v2"
 		})
 
 		updateKubernetesProviderServiceAccount(f, f.Namespace.Name, providerConfigName(f.Namespace.Name, ""), "missing-service-account")
-		triggerProviderConnectionReconcile(f, f.Namespace.Name, f.Namespace.Name, "broken-auth")
 
 		brokenProvider := frameworkv2.WaitForProviderConnectionNotReady(f, f.Namespace.Name, f.Namespace.Name, defaultV2WaitTimeout)
 		Expect(brokenProvider.Status.Capabilities).To(BeEmpty())
@@ -226,7 +225,6 @@ var _ = Describe("[kubernetes] v2 namespaced provider", Label("kubernetes", "v2"
 		expectSecretToBeAbsent(f, f.Namespace.Name, "provider-v2-recovery-target")
 
 		updateKubernetesProviderServiceAccount(f, f.Namespace.Name, providerConfigName(f.Namespace.Name, ""), frameworkv2.DefaultSAName)
-		triggerProviderConnectionReconcile(f, f.Namespace.Name, f.Namespace.Name, "repaired-auth")
 
 		repairedProvider := frameworkv2.WaitForProviderConnectionReady(f, f.Namespace.Name, f.Namespace.Name, defaultV2WaitTimeout)
 		Expect(repairedProvider.Status.Capabilities).To(Equal(esv1.ProviderReadWrite))
@@ -268,20 +266,6 @@ func updateKubernetesProviderServiceAccount(f *framework.Framework, namespace, n
 	Expect(f.CRClient.Update(context.Background(), &providerConfig)).To(Succeed())
 }
 
-func triggerProviderConnectionReconcile(f *framework.Framework, namespace, name, value string) {
-	var provider esv1.Provider
-	Expect(f.CRClient.Get(context.Background(), types.NamespacedName{
-		Name:      name,
-		Namespace: namespace,
-	}, &provider)).To(Succeed())
-
-	if provider.Annotations == nil {
-		provider.Annotations = make(map[string]string)
-	}
-	provider.Annotations["e2e.external-secrets.io/reconcile"] = value
-	Expect(f.CRClient.Update(context.Background(), &provider)).To(Succeed())
-}
-
 func waitForExternalSecretReadyStatus(f *framework.Framework, namespace, name string, expectedStatus corev1.ConditionStatus) {
 	Eventually(func(g Gomega) {
 		var externalSecret esv1.ExternalSecret

+ 39 - 0
pkg/controllers/clusterprovider/controller.go

@@ -28,8 +28,11 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	ctrlreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
 	pb "github.com/external-secrets/external-secrets/proto/provider"
 	"github.com/external-secrets/external-secrets/providers/v2/common/grpc"
 )
@@ -210,11 +213,47 @@ func (r *Reconciler) setCondition(store *esv1.ClusterProvider, newCondition esv1
 	UpdateStatusCondition(store, newCondition)
 }
 
+func findClusterProvidersForKubernetesProvider(ctx context.Context, kubeClient client.Client, obj client.Object) []ctrlreconcile.Request {
+	kubernetesProvider, ok := obj.(*k8sv2alpha1.Kubernetes)
+	if !ok {
+		return nil
+	}
+
+	var providers esv1.ClusterProviderList
+	if err := kubeClient.List(ctx, &providers); err != nil {
+		return nil
+	}
+
+	requests := make([]ctrlreconcile.Request, 0, len(providers.Items))
+	for i := range providers.Items {
+		provider := providers.Items[i]
+		ref := provider.Spec.Config.ProviderRef
+		if ref.APIVersion != k8sv2alpha1.GroupVersion.String() ||
+			ref.Kind != k8sv2alpha1.Kind ||
+			ref.Name != kubernetesProvider.Name ||
+			ref.Namespace != kubernetesProvider.Namespace {
+			continue
+		}
+
+		requests = append(requests, ctrlreconcile.Request{
+			NamespacedName: client.ObjectKeyFromObject(&provider),
+		})
+	}
+
+	return requests
+}
+
 // SetupWithManager sets up the controller with the Manager.
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		WithOptions(opts).
 		For(&esv1.ClusterProvider{}).
+		Watches(
+			&k8sv2alpha1.Kubernetes{},
+			handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
+				return findClusterProvidersForKubernetesProvider(ctx, r.Client, obj)
+			}),
+		).
 		Owns(&corev1.Secret{}). // Watch secrets that might be used for auth
 		Complete(r)
 }

+ 83 - 11
pkg/controllers/clusterprovider/controller_test.go

@@ -29,20 +29,21 @@ import (
 
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/status"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/credentials"
-	"google.golang.org/grpc/status"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
 	pb "github.com/external-secrets/external-secrets/proto/provider"
 )
 
@@ -394,6 +395,77 @@ func TestSetNotReadyConditionUpdatesReasonAndMessageWithoutChangingTransitionTim
 	}
 }
 
+func TestFindClusterProvidersForKubernetesProviderEnqueuesMatchingProviders(t *testing.T) {
+	scheme := runtime.NewScheme()
+	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+	utilruntime.Must(esv1.AddToScheme(scheme))
+
+	kubeClient := fakeclient.NewClientBuilder().
+		WithScheme(scheme).
+		WithObjects(
+			&esv1.ClusterProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "match",
+				},
+				Spec: esv1.ClusterProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       k8sv2alpha1.Kind,
+							Name:       "backend",
+							Namespace:  "config-ns",
+						},
+					},
+				},
+			},
+			&esv1.ClusterProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "wrong-name",
+				},
+				Spec: esv1.ClusterProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       k8sv2alpha1.Kind,
+							Name:       "other",
+							Namespace:  "config-ns",
+						},
+					},
+				},
+			},
+			&esv1.ClusterProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "wrong-namespace",
+				},
+				Spec: esv1.ClusterProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       k8sv2alpha1.Kind,
+							Name:       "backend",
+							Namespace:  "other-ns",
+						},
+					},
+				},
+			},
+		).
+		Build()
+
+	requests := findClusterProvidersForKubernetesProvider(context.Background(), kubeClient, &k8sv2alpha1.Kubernetes{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "backend",
+			Namespace: "config-ns",
+		},
+	})
+
+	if len(requests) != 1 {
+		t.Fatalf("expected one reconcile request, got %#v", requests)
+	}
+	if requests[0].NamespacedName != (client.ObjectKey{Name: "match"}) {
+		t.Fatalf("unexpected reconcile request: %#v", requests[0])
+	}
+}
+
 func newClusterProviderGRPCServer(t *testing.T) (*recordingClusterProviderGRPCServer, string, map[string][]byte) {
 	t.Helper()
 
@@ -457,8 +529,8 @@ func newClusterProviderTLSArtifacts(t *testing.T, host string) (serverCertPEM, s
 	}
 
 	caTemplate := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject: pkix.Name{CommonName: "cluster-provider-controller-test-ca"},
+		SerialNumber:          big.NewInt(1),
+		Subject:               pkix.Name{CommonName: "cluster-provider-controller-test-ca"},
 		NotBefore:             time.Now().Add(-time.Hour),
 		NotAfter:              time.Now().Add(24 * time.Hour),
 		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
@@ -491,11 +563,11 @@ func newClusterProviderSignedTLSCert(t *testing.T, caCert *x509.Certificate, caK
 
 	template := &x509.Certificate{
 		SerialNumber: big.NewInt(serial),
-		Subject: pkix.Name{CommonName: host},
-		NotBefore:   time.Now().Add(-time.Hour),
-		NotAfter:    time.Now().Add(24 * time.Hour),
-		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
-		ExtKeyUsage: usages,
+		Subject:      pkix.Name{CommonName: host},
+		NotBefore:    time.Now().Add(-time.Hour),
+		NotAfter:     time.Now().Add(24 * time.Hour),
+		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:  usages,
 	}
 
 	if ip := net.ParseIP(host); ip != nil {

+ 39 - 0
pkg/controllers/provider/controller.go

@@ -28,8 +28,11 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	ctrlreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
 	pb "github.com/external-secrets/external-secrets/proto/provider"
 	"github.com/external-secrets/external-secrets/providers/v2/common/grpc"
 )
@@ -210,11 +213,47 @@ func (r *Reconciler) setCondition(store *esv1.Provider, newCondition esv1.Provid
 	UpdateStatusCondition(store, newCondition)
 }
 
+func findProvidersForKubernetesProvider(ctx context.Context, kubeClient client.Client, obj client.Object) []ctrlreconcile.Request {
+	kubernetesProvider, ok := obj.(*k8sv2alpha1.Kubernetes)
+	if !ok {
+		return nil
+	}
+
+	var providers esv1.ProviderList
+	if err := kubeClient.List(ctx, &providers); err != nil {
+		return nil
+	}
+
+	requests := make([]ctrlreconcile.Request, 0, len(providers.Items))
+	for i := range providers.Items {
+		provider := providers.Items[i]
+		ref := provider.Spec.Config.ProviderRef
+		if ref.APIVersion != k8sv2alpha1.GroupVersion.String() ||
+			ref.Kind != k8sv2alpha1.Kind ||
+			ref.Name != kubernetesProvider.Name ||
+			ref.Namespace != kubernetesProvider.Namespace {
+			continue
+		}
+
+		requests = append(requests, ctrlreconcile.Request{
+			NamespacedName: client.ObjectKeyFromObject(&provider),
+		})
+	}
+
+	return requests
+}
+
 // SetupWithManager sets up the controller with the Manager.
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		WithOptions(opts).
 		For(&esv1.Provider{}).
+		Watches(
+			&k8sv2alpha1.Kubernetes{},
+			handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []ctrlreconcile.Request {
+				return findProvidersForKubernetesProvider(ctx, r.Client, obj)
+			}),
+		).
 		Owns(&corev1.Secret{}). // Watch secrets that might be used for auth
 		Complete(r)
 }

+ 86 - 11
pkg/controllers/provider/controller_test.go

@@ -29,20 +29,21 @@ import (
 
 	"github.com/go-logr/logr"
 	"github.com/prometheus/client_golang/prometheus"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/status"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/credentials"
-	"google.golang.org/grpc/status"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	k8sv2alpha1 "github.com/external-secrets/external-secrets/apis/provider/kubernetes/v2alpha1"
 	ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
 	pb "github.com/external-secrets/external-secrets/proto/provider"
 )
@@ -375,6 +376,80 @@ func TestSetNotReadyConditionUpdatesReasonAndMessageWithoutChangingTransitionTim
 	}
 }
 
+func TestFindProvidersForKubernetesProviderEnqueuesMatchingProviders(t *testing.T) {
+	scheme := runtime.NewScheme()
+	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+	utilruntime.Must(esv1.AddToScheme(scheme))
+
+	kubeClient := fakeclient.NewClientBuilder().
+		WithScheme(scheme).
+		WithObjects(
+			&esv1.Provider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "match",
+					Namespace: "tenant-a",
+				},
+				Spec: esv1.ProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       k8sv2alpha1.Kind,
+							Name:       "backend",
+							Namespace:  "config-ns",
+						},
+					},
+				},
+			},
+			&esv1.Provider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "wrong-name",
+					Namespace: "tenant-a",
+				},
+				Spec: esv1.ProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       k8sv2alpha1.Kind,
+							Name:       "other",
+							Namespace:  "config-ns",
+						},
+					},
+				},
+			},
+			&esv1.Provider{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "wrong-kind",
+					Namespace: "tenant-a",
+				},
+				Spec: esv1.ProviderSpec{
+					Config: esv1.ProviderConfig{
+						ProviderRef: esv1.ProviderReference{
+							APIVersion: k8sv2alpha1.GroupVersion.String(),
+							Kind:       "AWS",
+							Name:       "backend",
+							Namespace:  "config-ns",
+						},
+					},
+				},
+			},
+		).
+		Build()
+
+	requests := findProvidersForKubernetesProvider(context.Background(), kubeClient, &k8sv2alpha1.Kubernetes{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "backend",
+			Namespace: "config-ns",
+		},
+	})
+
+	if len(requests) != 1 {
+		t.Fatalf("expected one reconcile request, got %#v", requests)
+	}
+	if requests[0].NamespacedName != (client.ObjectKey{Name: "match", Namespace: "tenant-a"}) {
+		t.Fatalf("unexpected reconcile request: %#v", requests[0])
+	}
+}
+
 func newProviderGRPCServer(t *testing.T) (*recordingProviderGRPCServer, string, map[string][]byte) {
 	t.Helper()
 
@@ -438,8 +513,8 @@ func newProviderTLSArtifacts(t *testing.T, host string) (serverCertPEM, serverKe
 	}
 
 	caTemplate := &x509.Certificate{
-		SerialNumber: big.NewInt(1),
-		Subject: pkix.Name{CommonName: "provider-controller-test-ca"},
+		SerialNumber:          big.NewInt(1),
+		Subject:               pkix.Name{CommonName: "provider-controller-test-ca"},
 		NotBefore:             time.Now().Add(-time.Hour),
 		NotAfter:              time.Now().Add(24 * time.Hour),
 		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
@@ -472,11 +547,11 @@ func newProviderSignedTLSCert(t *testing.T, caCert *x509.Certificate, caKey *rsa
 
 	template := &x509.Certificate{
 		SerialNumber: big.NewInt(serial),
-		Subject: pkix.Name{CommonName: host},
-		NotBefore:   time.Now().Add(-time.Hour),
-		NotAfter:    time.Now().Add(24 * time.Hour),
-		KeyUsage:    x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
-		ExtKeyUsage: usages,
+		Subject:      pkix.Name{CommonName: host},
+		NotBefore:    time.Now().Add(-time.Hour),
+		NotAfter:     time.Now().Add(24 * time.Hour),
+		KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
+		ExtKeyUsage:  usages,
 	}
 
 	if ip := net.ParseIP(host); ip != nil {