diff --git a/pkg/hub/controllers/cluster/reconciler.go b/pkg/hub/controllers/cluster/reconciler.go index 7c4d25026..a51e5ce8b 100644 --- a/pkg/hub/controllers/cluster/reconciler.go +++ b/pkg/hub/controllers/cluster/reconciler.go @@ -507,27 +507,24 @@ func (r *Reconciler) updateDashboardCreds(ctx context.Context, cr *hubv1alpha1.C log := logger.FromContext(ctx) log.Info("Updating kubernetes dashboard creds") - sa := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: KubeSliceDashboardSA, - Namespace: controllers.ControlPlaneNamespace, - }, - } - if err := r.MeshClient.Get(ctx, types.NamespacedName{Name: sa.Name, Namespace: controllers.ControlPlaneNamespace}, sa); err != nil { - log.Error(err, "Error getting service account") + secretList := &corev1.SecretList{} + if err := r.MeshClient.List(ctx, secretList, client.InNamespace(controllers.ControlPlaneNamespace)); err != nil { + log.Error(err, "Error listing secrets for dashboard credentials") return err } - if len(sa.Secrets) == 0 { - err := fmt.Errorf("ServiceAccount has no secret") - log.Error(err, "Error getting service account secret") - return err + var secret *corev1.Secret + for i := range secretList.Items { + s := &secretList.Items[i] + if s.Type == corev1.SecretTypeServiceAccountToken && + s.Annotations[corev1.ServiceAccountNameKey] == KubeSliceDashboardSA { + secret = s + break + } } - - secret := &corev1.Secret{} - err := r.MeshClient.Get(ctx, types.NamespacedName{Name: sa.Secrets[0].Name, Namespace: controllers.ControlPlaneNamespace}, secret) - if err != nil { - log.Error(err, "Error getting service account's secret") + if secret == nil { + err := fmt.Errorf("no token secret found for ServiceAccount %s", KubeSliceDashboardSA) + log.Error(err, "Error getting dashboard token secret") return err } @@ -556,7 +553,7 @@ func (r *Reconciler) updateDashboardCreds(ctx context.Context, cr *hubv1alpha1.C Data: secretData, } log.Info("creating secret on hub", "hubSecret", hubSecret.Name) - err = r.Create(ctx, &hubSecret) + err := r.Create(ctx, &hubSecret) if apierrors.IsAlreadyExists(err) { err = r.Update(ctx, &hubSecret) } diff --git a/pkg/hub/controllers/cluster/reconciler_unit_test.go b/pkg/hub/controllers/cluster/reconciler_unit_test.go index 595341eb2..faffcffd7 100644 --- a/pkg/hub/controllers/cluster/reconciler_unit_test.go +++ b/pkg/hub/controllers/cluster/reconciler_unit_test.go @@ -3,6 +3,7 @@ package cluster import ( "context" "errors" + "os" "testing" "time" @@ -408,6 +409,86 @@ func TestUpdateNetworkStatus(t *testing.T) { } } +func Test_updateDashboardCreds(t *testing.T) { + tests := []struct { + name string + ctx context.Context + loadMocks MockClientCall + err error + }{ + { + "no token secret found for dashboard SA — returns error", + context.WithValue(context.Background(), types.NamespacedName{}, testClusterObj), + func(clientMock *utilmock.MockClient, ctx context.Context) { + clientMock.On("List", + mock.IsType(ctx), + mock.IsType(&corev1.SecretList{}), + mock.Anything, + ).Return(nil) // empty list — no matching secret + }, + errors.New("no token secret found for ServiceAccount kubeslice-kubernetes-dashboard"), + }, + { + "token secret found via annotation — credentials updated successfully", + context.WithValue(context.Background(), types.NamespacedName{}, testClusterObj), + func(clientMock *utilmock.MockClient, ctx context.Context) { + clientMock.On("List", + mock.IsType(ctx), + mock.IsType(&corev1.SecretList{}), + mock.Anything, + ).Return(nil).Run(func(args mock.Arguments) { + list := args.Get(1).(*corev1.SecretList) + list.Items = []corev1.Secret{{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kubeslice-kubernetes-dashboard-token", + Namespace: ControlPlaneNamespace, + Annotations: map[string]string{ + corev1.ServiceAccountNameKey: KubeSliceDashboardSA, + }, + }, + Type: corev1.SecretTypeServiceAccountToken, + Data: map[string][]byte{ + "token": []byte("test-token"), + "ca.crt": []byte("test-ca"), + }, + }} + }) + clientMock.On("Create", + mock.IsType(ctx), + mock.IsType(&corev1.Secret{}), + mock.IsType([]k8sclient.CreateOption(nil)), + ).Return(nil) + clientMock.On("Update", + mock.IsType(ctx), + mock.IsType(&hubv1alpha1.Cluster{}), + mock.IsType([]k8sclient.UpdateOption(nil)), + ).Return(nil) + }, + nil, + }, + } + + os.Setenv("CLUSTER_NAME", "test-cluster") + os.Setenv("HUB_PROJECT_NAMESPACE", "kubeslice-avesha") + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctx := test.ctx + clientMock := utilmock.NewClient() + mf, _ := metrics.NewMetricsFactory(prometheus.NewRegistry(), metrics.MetricsFactoryOptions{}) + r := NewReconciler(clientMock, clientMock, nil, mf) + test.loadMocks(clientMock, ctx) + err := r.updateDashboardCreds(ctx, &hubv1alpha1.Cluster{}) + if test.err != nil && err != nil { + if test.err.Error() != err.Error() { + t.Error("Expected error:", test.err, " but got ", err) + } + } else if test.err != err { + t.Error("Expected error:", test.err, " but got ", err) + } + }) + } +} + func Test_isNsmInstalled(t *testing.T) { tests := []struct { name string