diff --git a/api/v1/databaseclaim_types.go b/api/v1/databaseclaim_types.go index ede621c4..e9a247b4 100644 --- a/api/v1/databaseclaim_types.go +++ b/api/v1/databaseclaim_types.go @@ -28,8 +28,9 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. var ( - DSNKey = "dsn.txt" - DSNURIKey = "uri_dsn.txt" + DSNKey = "dsn.txt" + DSNURIKey = "uri_dsn.txt" + ReplicaDSNURIKey = "ro_uri_dsn.txt" ) type DatabaseType string diff --git a/pkg/databaseclaim/databaseclaim.go b/pkg/databaseclaim/databaseclaim.go index 8808df00..30bff058 100644 --- a/pkg/databaseclaim/databaseclaim.go +++ b/pkg/databaseclaim/databaseclaim.go @@ -563,7 +563,7 @@ func (r *DatabaseClaimReconciler) executeDbClaimRequest(ctx context.Context, dbC newDBConnInfo := dbClaim.Status.NewDB.ConnectionInfo.DeepCopy() newDBConnInfo.Password = r.Input.TempSecret - if err := r.createOrUpdateSecret(ctx, dbClaim, newDBConnInfo); err != nil { + if err := r.createOrUpdateSecret(ctx, dbClaim, newDBConnInfo, basefun.GetCloud(r.Config.Viper)); err != nil { return r.manageError(ctx, dbClaim, err) } } @@ -645,7 +645,7 @@ func (r *DatabaseClaimReconciler) reconcileUseExistingDB(ctx context.Context, db newDBConnInfo := dbClaim.Status.NewDB.ConnectionInfo.DeepCopy() newDBConnInfo.Password = r.Input.TempSecret - if err := r.createOrUpdateSecret(ctx, dbClaim, newDBConnInfo); err != nil { + if err := r.createOrUpdateSecret(ctx, dbClaim, newDBConnInfo, basefun.GetCloud(r.Config.Viper)); err != nil { return err } } @@ -1405,7 +1405,7 @@ func (r *DatabaseClaimReconciler) rerouteTargetSecret(ctx context.Context, sourc if err != nil { return err } - err = r.createOrUpdateSecret(ctx, dbClaim, targetAppConn) + err = r.createOrUpdateSecret(ctx, dbClaim, targetAppConn, basefun.GetCloud(r.Config.Viper)) if err != nil { return err } diff --git a/pkg/databaseclaim/secrets.go b/pkg/databaseclaim/secrets.go index 0a9f3193..782ab58c 100644 --- a/pkg/databaseclaim/secrets.go +++ b/pkg/databaseclaim/secrets.go @@ -3,6 +3,7 @@ package databaseclaim import ( "context" "fmt" + "strings" _ "github.com/lib/pq" corev1 "k8s.io/api/core/v1" @@ -17,14 +18,13 @@ import ( "k8s.io/apimachinery/pkg/api/errors" ) -func (r *DatabaseClaimReconciler) createOrUpdateSecret(ctx context.Context, dbClaim *v1.DatabaseClaim, - connInfo *v1.DatabaseClaimConnectionInfo) error { +func (r *DatabaseClaimReconciler) createOrUpdateSecret(ctx context.Context, dbClaim *v1.DatabaseClaim, connInfo *v1.DatabaseClaimConnectionInfo, cloud string) error { logr := log.FromContext(ctx) gs := &corev1.Secret{} dbType := dbClaim.Spec.Type secretName := dbClaim.Spec.SecretName logr.Info("createOrUpdateSecret being executed. SecretName: " + secretName) - var dsn, dsnURI string + var dsn, dsnURI, replicaDsnURI string = "", "", "" switch dbType { case v1.Postgres: @@ -32,6 +32,10 @@ func (r *DatabaseClaimReconciler) createOrUpdateSecret(ctx context.Context, dbCl case v1.AuroraPostgres: dsn = dbclient.PostgresConnectionString(connInfo.Host, connInfo.Port, connInfo.Username, connInfo.Password, connInfo.DatabaseName, connInfo.SSLMode) dsnURI = dbclient.PostgresURI(connInfo.Host, connInfo.Port, connInfo.Username, connInfo.Password, connInfo.DatabaseName, connInfo.SSLMode) + + if cloud == "aws" { + replicaDsnURI = strings.Replace(dsnURI, ".cluster-", ".cluster-ro-", -1) + } default: return fmt.Errorf("unknown DB type") } @@ -42,16 +46,16 @@ func (r *DatabaseClaimReconciler) createOrUpdateSecret(ctx context.Context, dbCl }, gs) if err != nil && errors.IsNotFound(err) { - if err := r.createSecret(ctx, dbClaim, dsn, dsnURI, connInfo); err != nil { + if err := r.createSecret(ctx, dbClaim, dsn, dsnURI, replicaDsnURI, connInfo); err != nil { return err } return nil } - return r.updateSecret(ctx, dsn, dsnURI, connInfo, gs) + return r.updateSecret(ctx, dsn, dsnURI, replicaDsnURI, connInfo, gs) } -func (r *DatabaseClaimReconciler) createSecret(ctx context.Context, dbClaim *v1.DatabaseClaim, dsn, dbURI string, connInfo *v1.DatabaseClaimConnectionInfo) error { +func (r *DatabaseClaimReconciler) createSecret(ctx context.Context, dbClaim *v1.DatabaseClaim, dsn, dbURI, replicaDbURI string, connInfo *v1.DatabaseClaimConnectionInfo) error { logr := log.FromContext(ctx) secretName := dbClaim.Spec.SecretName @@ -73,14 +77,15 @@ func (r *DatabaseClaimReconciler) createSecret(ctx context.Context, dbClaim *v1. }, }, Data: map[string][]byte{ - v1.DSNKey: []byte(dsn), - v1.DSNURIKey: []byte(dbURI), - "hostname": []byte(connInfo.Host), - "port": []byte(connInfo.Port), - "database": []byte(connInfo.DatabaseName), - "username": []byte(connInfo.Username), - "password": []byte(connInfo.Password), - "sslmode": []byte(connInfo.SSLMode), + v1.DSNKey: []byte(dsn), + v1.DSNURIKey: []byte(dbURI), + v1.ReplicaDSNURIKey: []byte(replicaDbURI), + "hostname": []byte(connInfo.Host), + "port": []byte(connInfo.Port), + "database": []byte(connInfo.DatabaseName), + "username": []byte(connInfo.Username), + "password": []byte(connInfo.Password), + "sslmode": []byte(connInfo.SSLMode), }, } logr.Info("creating connection info SECRET: "+secret.Name, "secret", secret.Name, "namespace", secret.Namespace) @@ -88,12 +93,13 @@ func (r *DatabaseClaimReconciler) createSecret(ctx context.Context, dbClaim *v1. return r.Client.Create(ctx, secret) } -func (r *DatabaseClaimReconciler) updateSecret(ctx context.Context, dsn, dbURI string, connInfo *v1.DatabaseClaimConnectionInfo, exSecret *corev1.Secret) error { +func (r *DatabaseClaimReconciler) updateSecret(ctx context.Context, dsn, dbURI, replicaDsnURI string, connInfo *v1.DatabaseClaimConnectionInfo, exSecret *corev1.Secret) error { logr := log.FromContext(ctx) exSecret.Data[v1.DSNKey] = []byte(dsn) exSecret.Data[v1.DSNURIKey] = []byte(dbURI) + exSecret.Data[v1.ReplicaDSNURIKey] = []byte(replicaDsnURI) exSecret.Data["hostname"] = []byte(connInfo.Host) exSecret.Data["port"] = []byte(connInfo.Port) exSecret.Data["database"] = []byte(connInfo.DatabaseName) diff --git a/pkg/databaseclaim/secrets_test.go b/pkg/databaseclaim/secrets_test.go new file mode 100644 index 00000000..776b9c74 --- /dev/null +++ b/pkg/databaseclaim/secrets_test.go @@ -0,0 +1,105 @@ +package databaseclaim + +import ( + "context" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + v1 "github.com/infobloxopen/db-controller/api/v1" + role "github.com/infobloxopen/db-controller/pkg/roleclaim" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestCreateSecret(t *testing.T) { + + RegisterFailHandler(Fail) + + mockClient := role.MockClient{} + + dbClaimReconciler := DatabaseClaimReconciler{ + Client: &mockClient, + } + + ctx := context.Background() + claimConnInfo := v1.DatabaseClaimConnectionInfo{ + Host: "host", + Port: "123", + DatabaseName: "dbName", + Username: "user", + Password: "pass", + SSLMode: "ssl", + } + + dbClaim := v1.DatabaseClaim{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "dbClaim", + Namespace: "testNamespace", + }, + Spec: v1.DatabaseClaimSpec{ + SecretName: "create-master-secret", + }, + Status: v1.DatabaseClaimStatus{}, + } + + err := dbClaimReconciler.createSecret(ctx, &dbClaim, "dsn", "dsnUri", "ro_dsnUri", &claimConnInfo) + + secret := mockClient.CreatedObject.(*corev1.Secret) + + Expect(secret.Data[v1.DSNKey]).To(Equal([]byte("dsn"))) + Expect(secret.Data[v1.DSNURIKey]).To(Equal([]byte("dsnUri"))) + Expect(secret.Data[v1.ReplicaDSNURIKey]).To(Equal([]byte("ro_dsnUri"))) + Expect(secret.Data["hostname"]).To(Equal([]byte("host"))) + Expect(secret.Data["port"]).To(Equal([]byte("123"))) + Expect(secret.Data["database"]).To(Equal([]byte("dbName"))) + Expect(secret.Data["username"]).To(Equal([]byte("user"))) + Expect(secret.Data["password"]).To(Equal([]byte("pass"))) + Expect(secret.Data["sslmode"]).To(Equal([]byte("ssl"))) + + if err != nil { + t.Errorf("Error updating secret: %s", err) + } +} + +func TestUpdateSecret(t *testing.T) { + + RegisterFailHandler(Fail) + + mockClient := role.MockClient{} + + dbClaimReconciler := DatabaseClaimReconciler{ + Client: &mockClient, + } + + ctx := context.Background() + claimConnInfo := v1.DatabaseClaimConnectionInfo{ + Host: "host", + Port: "123", + DatabaseName: "dbName", + Username: "user", + Password: "pass", + SSLMode: "ssl", + } + secret := corev1.Secret{ + Data: make(map[string][]byte), + } + + err := dbClaimReconciler.updateSecret(ctx, "dsn", "dsnUri", "ro_dsnUri", &claimConnInfo, &secret) + + Expect(secret.Data[v1.DSNKey]).To(Equal([]byte("dsn"))) + Expect(secret.Data[v1.DSNURIKey]).To(Equal([]byte("dsnUri"))) + Expect(secret.Data[v1.ReplicaDSNURIKey]).To(Equal([]byte("ro_dsnUri"))) + Expect(secret.Data["hostname"]).To(Equal([]byte("host"))) + Expect(secret.Data["port"]).To(Equal([]byte("123"))) + Expect(secret.Data["database"]).To(Equal([]byte("dbName"))) + Expect(secret.Data["username"]).To(Equal([]byte("user"))) + Expect(secret.Data["password"]).To(Equal([]byte("pass"))) + Expect(secret.Data["sslmode"]).To(Equal([]byte("ssl"))) + + if err != nil { + t.Errorf("Error updating secret: %s", err) + } +} diff --git a/pkg/roleclaim/mockclient_test.go b/pkg/roleclaim/mockclient.go similarity index 92% rename from pkg/roleclaim/mockclient_test.go rename to pkg/roleclaim/mockclient.go index 2f4d66d9..b0947a12 100644 --- a/pkg/roleclaim/mockclient_test.go +++ b/pkg/roleclaim/mockclient.go @@ -15,23 +15,24 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -type mockClient struct { +type MockClient struct { client.Client // port is derived from the DSN - dsn string + dsn string + CreatedObject client.Object } var responseUpdate interface{} -func (m *mockClient) GetResponseUpdate() interface{} { +func (m *MockClient) GetResponseUpdate() interface{} { return responseUpdate } -func (m *mockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { return nil } -func (m *mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { parsedDSN, err := url.Parse(m.dsn) if err != nil { @@ -147,24 +148,21 @@ func (m *mockClient) Get(ctx context.Context, key client.ObjectKey, obj client.O return errors.NewNotFound(schema.GroupResource{Group: "core", Resource: "secret"}, key.Name) } -func (m *mockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { _ = ctx if (obj.GetNamespace() == "testNamespace" || obj.GetNamespace() == "schema-user-test") && (obj.GetName() == "create-master-secret" || obj.GetName() == "sample-master-secret") { sec, ok := obj.(*corev1.Secret) + m.CreatedObject = sec if !ok { return fmt.Errorf("can't assert type") } - sec.Data = map[string][]byte{ - "password": []byte("masterpassword"), - "username": []byte("mainUser"), - } return nil } return fmt.Errorf("can't create object") } -func (m *mockClient) Status() client.StatusWriter { +func (m *MockClient) Status() client.StatusWriter { return &MockStatusWriter{} } diff --git a/pkg/roleclaim/roleclaim_test.go b/pkg/roleclaim/roleclaim_test.go index 2db682c3..487635b2 100644 --- a/pkg/roleclaim/roleclaim_test.go +++ b/pkg/roleclaim/roleclaim_test.go @@ -58,7 +58,7 @@ func TestDBRoleClaimController_CreateSchemasAndRoles(t *testing.T) { { "Get UserSchema claim 1", reconciler{ - Client: &mockClient{dsn: dsn}, + Client: &MockClient{dsn: dsn}, Config: &RoleConfig{ Viper: viperObj, Class: "default", @@ -90,7 +90,7 @@ func TestDBRoleClaimController_CreateSchemasAndRoles(t *testing.T) { dbClient, err := basefun.GetClientForExistingDB(existingDBConnInfo, &controllerruntime.Log) Expect(err).ShouldNot(HaveOccurred()) - var responseUpdate = r.Client.(*mockClient).GetResponseUpdate() + var responseUpdate = r.Client.(*MockClient).GetResponseUpdate() Expect(responseUpdate).Should(Not(BeNil())) var schemaUserClaimStatus = responseUpdate.(*persistancev1.DbRoleClaim).Status Expect(schemaUserClaimStatus).Should(Not(BeNil())) @@ -166,7 +166,7 @@ func TestDBRoleClaimController_ExistingSchemaRoleAndUser(t *testing.T) { wantErr bool }{ reconciler{ - Client: &mockClient{dsn: dsn}, + Client: &MockClient{dsn: dsn}, Config: &RoleConfig{ Viper: viperObj, Class: "default", @@ -201,7 +201,7 @@ func TestDBRoleClaimController_ExistingSchemaRoleAndUser(t *testing.T) { Expect(result.Requeue).Should(BeFalse()) - var responseUpdate = r.Client.(*mockClient).GetResponseUpdate() + var responseUpdate = r.Client.(*MockClient).GetResponseUpdate() Expect(responseUpdate).Should(Not(BeNil())) var schemaUserClaimStatus = responseUpdate.(*persistancev1.DbRoleClaim).Status Expect(schemaUserClaimStatus).Should(Not(BeNil())) @@ -270,7 +270,7 @@ func TestDBRoleClaimController_RevokeRolesAndAssignNew(t *testing.T) { wantErr bool }{ reconciler{ - Client: &mockClient{dsn: dsn}, + Client: &MockClient{dsn: dsn}, Config: &RoleConfig{ Viper: viperObj, Class: "default", @@ -322,7 +322,7 @@ func TestDBRoleClaimController_RevokeRolesAndAssignNew(t *testing.T) { Expect(result.Requeue).Should(BeFalse()) - var responseUpdate = r.Client.(*mockClient).GetResponseUpdate() + var responseUpdate = r.Client.(*MockClient).GetResponseUpdate() Expect(responseUpdate).Should(Not(BeNil())) var schemaUserClaimStatus = responseUpdate.(*persistancev1.DbRoleClaim).Status Expect(schemaUserClaimStatus).Should(Not(BeNil()))