From 2154d1a18b0b197b52dc94d08509602ca5fee010 Mon Sep 17 00:00:00 2001 From: "mykyta.oleksiienko" Date: Wed, 21 Feb 2024 12:47:08 +0200 Subject: [PATCH] issue-698-Save-default-user-ref-for-cassandra --- apis/clusters/v1beta1/cassandra_types.go | 5 +- apis/clusters/v1beta1/kafkaconnect_types.go | 23 ------- apis/clusters/v1beta1/zookeeper_types.go | 24 ------- .../clusters/v1beta1/zz_generated.deepcopy.go | 5 ++ .../clusters.instaclustr.com_cassandras.yaml | 11 +++ .../samples/clusters_v1beta1_cassandra.yaml | 2 +- controllers/clusters/cassandra_controller.go | 68 +++++++++++++++++++ controllers/clusters/helpers.go | 23 +++++++ .../clusters/kafkaconnect_controller.go | 2 +- .../clusters/kafkaconnect_controller_test.go | 2 +- controllers/clusters/zookeeper_controller.go | 2 +- .../clusters/zookeeper_controller_test.go | 2 +- 12 files changed, 115 insertions(+), 54 deletions(-) diff --git a/apis/clusters/v1beta1/cassandra_types.go b/apis/clusters/v1beta1/cassandra_types.go index 354595402..0b14dcbe6 100644 --- a/apis/clusters/v1beta1/cassandra_types.go +++ b/apis/clusters/v1beta1/cassandra_types.go @@ -65,8 +65,9 @@ type CassandraSpec struct { // CassandraStatus defines the observed state of Cassandra type CassandraStatus struct { - GenericStatus `json:",inline"` - DataCentres []*CassandraDataCentreStatus `json:"dataCentres,omitempty"` + GenericStatus `json:",inline"` + DataCentres []*CassandraDataCentreStatus `json:"dataCentres,omitempty"` + DefaultUserSecretRef *Reference `json:"defaultUserSecretRef,omitempty"` AvailableUsers References `json:"availableUsers,omitempty"` } diff --git a/apis/clusters/v1beta1/kafkaconnect_types.go b/apis/clusters/v1beta1/kafkaconnect_types.go index 5610ce421..a01f6e3e0 100644 --- a/apis/clusters/v1beta1/kafkaconnect_types.go +++ b/apis/clusters/v1beta1/kafkaconnect_types.go @@ -17,8 +17,6 @@ limitations under the License. package v1beta1 import ( - "fmt" - k8scorev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -543,27 +541,6 @@ func (tc *TargetCluster) ManagedClustersToInstAPI() (iClusters []*models.Managed return } -func (k *KafkaConnect) NewDefaultUserSecret(username, password string) *k8scorev1.Secret { - return &k8scorev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: models.SecretKind, - APIVersion: models.K8sAPIVersionV1, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(models.DefaultUserSecretNameTemplate, models.DefaultUserSecretPrefix, k.Name), - Namespace: k.Namespace, - Labels: map[string]string{ - models.ControlledByLabel: k.Name, - models.DefaultSecretLabel: "true", - }, - }, - StringData: map[string]string{ - models.Username: username, - models.Password: password, - }, - } -} - func (k *KafkaConnect) GetExposePorts() []k8scorev1.ServicePort { var exposePorts []k8scorev1.ServicePort if !k.Spec.PrivateNetwork { diff --git a/apis/clusters/v1beta1/zookeeper_types.go b/apis/clusters/v1beta1/zookeeper_types.go index 2dece32a9..d5fb93258 100644 --- a/apis/clusters/v1beta1/zookeeper_types.go +++ b/apis/clusters/v1beta1/zookeeper_types.go @@ -18,9 +18,6 @@ package v1beta1 import ( "encoding/json" - "fmt" - - "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -217,24 +214,3 @@ func (rs *ZookeeperSpec) areDCsEqual(b []*ZookeeperDataCentre) bool { return true } - -func (a *Zookeeper) NewDefaultUserSecret(username, password string) *v1.Secret { - return &v1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: models.SecretKind, - APIVersion: models.K8sAPIVersionV1, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf(models.DefaultUserSecretNameTemplate, models.DefaultUserSecretPrefix, a.Name), - Namespace: a.Namespace, - Labels: map[string]string{ - models.ControlledByLabel: a.Name, - models.DefaultSecretLabel: "true", - }, - }, - StringData: map[string]string{ - models.Username: username, - models.Password: password, - }, - } -} diff --git a/apis/clusters/v1beta1/zz_generated.deepcopy.go b/apis/clusters/v1beta1/zz_generated.deepcopy.go index bd948d15a..0fc301951 100644 --- a/apis/clusters/v1beta1/zz_generated.deepcopy.go +++ b/apis/clusters/v1beta1/zz_generated.deepcopy.go @@ -662,6 +662,11 @@ func (in *CassandraStatus) DeepCopyInto(out *CassandraStatus) { } } } + if in.DefaultUserSecretRef != nil { + in, out := &in.DefaultUserSecretRef, &out.DefaultUserSecretRef + *out = new(apiextensions.ObjectReference) + **out = **in + } if in.AvailableUsers != nil { in, out := &in.AvailableUsers, &out.AvailableUsers *out = make(References, len(*in)) diff --git a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml index 5ef899082..78f78d177 100644 --- a/config/crd/bases/clusters.instaclustr.com_cassandras.yaml +++ b/config/crd/bases/clusters.instaclustr.com_cassandras.yaml @@ -449,6 +449,17 @@ spec: - nodes type: object type: array + defaultUserSecretRef: + description: ObjectReference is namespaced reference to an object + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object id: type: string maintenanceEvents: diff --git a/config/samples/clusters_v1beta1_cassandra.yaml b/config/samples/clusters_v1beta1_cassandra.yaml index 244eda769..43adb8b72 100644 --- a/config/samples/clusters_v1beta1_cassandra.yaml +++ b/config/samples/clusters_v1beta1_cassandra.yaml @@ -4,7 +4,7 @@ metadata: name: cassandra-cluster spec: name: "example-cassandra" #(immutable) - version: "4.0.10" #(immutable) + version: "4.1.3" #(immutable) privateNetwork: false #(immutable) dataCentres: - name: "AWS_cassandra" #(mutable) diff --git a/controllers/clusters/cassandra_controller.go b/controllers/clusters/cassandra_controller.go index cef870773..80db278ae 100644 --- a/controllers/clusters/cassandra_controller.go +++ b/controllers/clusters/cassandra_controller.go @@ -316,6 +316,21 @@ func (r *CassandraReconciler) handleCreateCluster( } } + err := r.createDefaultSecret(ctx, c, l) + if err != nil { + l.Error(err, "Cannot create default secret for Cassandra", + "cluster name", c.Spec.Name, + "clusterID", c.Status.ID, + ) + r.EventRecorder.Eventf( + c, models.Warning, models.CreationFailed, + "Default user secret creation on the Instaclustr is failed. Reason: %v", + err, + ) + + return reconcile.Result{}, err + } + if c.Status.State != models.DeletedStatus { patch := c.NewPatch() c.Annotations[models.ResourceStateAnnotation] = models.CreatedEvent @@ -888,6 +903,59 @@ func (r *CassandraReconciler) newWatchBackupsJob(c *v1beta1.Cassandra) scheduler } } +func (r *CassandraReconciler) createDefaultSecret(ctx context.Context, c *v1beta1.Cassandra, l logr.Logger) error { + username, password, err := r.API.GetDefaultCredentialsV1(c.Status.ID) + if err != nil { + l.Error(err, "Cannot get default user creds for Cassandra cluster from the Instaclustr API", + "cluster ID", c.Status.ID, + ) + r.EventRecorder.Eventf(c, models.Warning, models.FetchFailed, + "Default user password fetch from the Instaclustr API is failed. Reason: %v", err, + ) + + return err + } + + patch := c.NewPatch() + secret := newDefaultUserSecret(username, password, c.Name, c.Namespace) + err = r.Create(ctx, secret) + if err != nil { + l.Error(err, "Cannot create secret with default user credentials", + "cluster ID", c.Status.ID, + ) + r.EventRecorder.Eventf(c, models.Warning, models.CreationFailed, + "Creating secret with default user credentials is failed. Reason: %v", err, + ) + + return err + } + + l.Info("Default secret was created", + "secret name", secret.Name, + "secret namespace", secret.Namespace, + ) + + c.Status.DefaultUserSecretRef = &v1beta1.Reference{ + Name: secret.Name, + Namespace: secret.Namespace, + } + + err = r.Status().Patch(ctx, c, patch) + if err != nil { + l.Error(err, "Cannot patch Cassandra resource", + "cluster name", c.Spec.Name, + "status", c.Status) + + r.EventRecorder.Eventf( + c, models.Warning, models.PatchFailed, + "Cluster resource patch is failed. Reason: %v", err) + + return err + } + + return nil +} + func (r *CassandraReconciler) newUsersCreationJob(c *v1beta1.Cassandra) scheduler.Job { l := log.Log.WithValues("component", "cassandraUsersCreationJob") diff --git a/controllers/clusters/helpers.go b/controllers/clusters/helpers.go index a6d88ac51..2380137c1 100644 --- a/controllers/clusters/helpers.go +++ b/controllers/clusters/helpers.go @@ -24,8 +24,10 @@ import ( "github.com/go-logr/logr" "github.com/hashicorp/go-version" + k8scorev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/utils/strings/slices" @@ -197,6 +199,27 @@ func createSpecDifferenceMessage[T any](k8sSpec, iSpec T) (string, error) { return fmt.Sprintf("%s Diffs: %s", models.ExternalChangesBaseMessage, b), nil } +func newDefaultUserSecret(username, password, name, namespace string) *k8scorev1.Secret { + return &k8scorev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: models.SecretKind, + APIVersion: models.K8sAPIVersionV1, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf(models.DefaultUserSecretNameTemplate, models.DefaultUserSecretPrefix, name), + Namespace: namespace, + Labels: map[string]string{ + models.ControlledByLabel: name, + models.DefaultSecretLabel: "true", + }, + }, + StringData: map[string]string{ + models.Username: username, + models.Password: password, + }, + } +} + var msgDeleteClusterWithTwoFactorDelete = "Please confirm cluster deletion via email or phone. " + "If you have canceled a cluster deletion and want to put the cluster on deletion again, " + "remove \"triggered\" from Instaclustr.com/clusterDeletion annotation." diff --git a/controllers/clusters/kafkaconnect_controller.go b/controllers/clusters/kafkaconnect_controller.go index 87985dd09..697a4c721 100644 --- a/controllers/clusters/kafkaconnect_controller.go +++ b/controllers/clusters/kafkaconnect_controller.go @@ -413,7 +413,7 @@ func (r *KafkaConnectReconciler) createDefaultSecret(ctx context.Context, kc *v1 } patch := kc.NewPatch() - secret := kc.NewDefaultUserSecret(username, password) + secret := newDefaultUserSecret(username, password, kc.Name, kc.Namespace) err = r.Create(ctx, secret) if err != nil { l.Error(err, "Cannot create secret with default user credentials", diff --git a/controllers/clusters/kafkaconnect_controller_test.go b/controllers/clusters/kafkaconnect_controller_test.go index a305c6b44..9ac0f6d06 100644 --- a/controllers/clusters/kafkaconnect_controller_test.go +++ b/controllers/clusters/kafkaconnect_controller_test.go @@ -68,7 +68,7 @@ var _ = Describe("Kafka Connect Controller", func() { <-done By("creating secret with the default user credentials") - secret := kafkaConnect.NewDefaultUserSecret("", "") + secret := newDefaultUserSecret("", "", kafkaConnect.Name, kafkaConnectManifest.Namespace) secretNamespacedName := types.NamespacedName{ Namespace: secret.Namespace, Name: secret.Name, diff --git a/controllers/clusters/zookeeper_controller.go b/controllers/clusters/zookeeper_controller.go index 258edc559..369b5f142 100644 --- a/controllers/clusters/zookeeper_controller.go +++ b/controllers/clusters/zookeeper_controller.go @@ -212,7 +212,7 @@ func (r *ZookeeperReconciler) createDefaultSecret(ctx context.Context, zk *v1bet } patch := zk.NewPatch() - secret := zk.NewDefaultUserSecret(username, password) + secret := newDefaultUserSecret(username, password, zk.Name, zk.Namespace) err = r.Create(ctx, secret) if err != nil { l.Error(err, "Cannot create secret with default user credentials", diff --git a/controllers/clusters/zookeeper_controller_test.go b/controllers/clusters/zookeeper_controller_test.go index aee943330..2a4a320c3 100644 --- a/controllers/clusters/zookeeper_controller_test.go +++ b/controllers/clusters/zookeeper_controller_test.go @@ -66,7 +66,7 @@ var _ = Describe("Zookeeper Controller", func() { <-done By("creating secret with the default user credentials") - secret := zookeeper.NewDefaultUserSecret("", "") + secret := newDefaultUserSecret("", "", zookeeper.Name, zookeeper.Namespace) secretNamespacedName := types.NamespacedName{ Namespace: secret.Namespace, Name: secret.Name,