diff --git a/Makefile b/Makefile index 1ce5648..26edc13 100644 --- a/Makefile +++ b/Makefile @@ -310,7 +310,7 @@ setup/dev-services: .PHONY: setup/services setup/services: down/services @echo "Setup services" - docker run -d --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v $(CURDIR)/.run/postgres:/var/lib/postgresql/data postgres:16 + docker run -d --rm --name postgres -p 5432:5432 -p 5433:5432 -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v $(CURDIR)/.run/postgres:/var/lib/postgresql/data postgres:16 .PHONY: code/lint code/lint: setup/dep/install diff --git a/api/postgresql/v1alpha1/postgresqlengineconfiguration_types.go b/api/postgresql/v1alpha1/postgresqlengineconfiguration_types.go index 1d44b05..0efa9c6 100644 --- a/api/postgresql/v1alpha1/postgresqlengineconfiguration_types.go +++ b/api/postgresql/v1alpha1/postgresqlengineconfiguration_types.go @@ -59,6 +59,32 @@ type PostgresqlEngineConfigurationSpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:MinLength=1 SecretName string `json:"secretName"` + // User connections used for secret generation + // That will be used to generate secret with primary server as url or + // to use the pg bouncer one. + // Note: Operator won't check those values. + // +optional + UserConnections *UserConnections `json:"userConnections"` +} + +type UserConnections struct { + // Primary connection is referring to the primary node connection. + // +optional + PrimaryConnection *GenericUserConnection `json:"primaryConnection,omitempty"` + // Bouncer connection is referring to a pg bouncer node. + // +optional + BouncerConnection *GenericUserConnection `json:"bouncerConnection,omitempty"` +} + +type GenericUserConnection struct { + // Hostname + // +required + // +kubebuilder:validation:Required + Host string `json:"host"` + // URI args like sslmode, ... + URIArgs string `json:"uriArgs"` + // Port + Port int `json:"port,omitempty"` } type EngineStatusPhase string diff --git a/api/postgresql/v1alpha1/postgresqluserrole_types.go b/api/postgresql/v1alpha1/postgresqluserrole_types.go index c231726..0d3af2e 100644 --- a/api/postgresql/v1alpha1/postgresqluserrole_types.go +++ b/api/postgresql/v1alpha1/postgresqluserrole_types.go @@ -24,7 +24,18 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type ConnectionTypesSpecEnum string + +const PrimaryConnectionType ConnectionTypesSpecEnum = "PRIMARY" +const BouncerConnectionType ConnectionTypesSpecEnum = "BOUNCER" + type PostgresqlUserRolePrivilege struct { + // User Connection type. + // This is referring to the user connection type needed for this user. + // +optional + // +kubebuilder:default=PRIMARY + // +kubebuilder:validation:Enum=PRIMARY;BOUNCER + ConnectionType ConnectionTypesSpecEnum `json:"connectionType,omitempty"` // User privileges // +required // +kubebuilder:validation:Required diff --git a/api/postgresql/v1alpha1/zz_generated.deepcopy.go b/api/postgresql/v1alpha1/zz_generated.deepcopy.go index db99df0..07b293b 100644 --- a/api/postgresql/v1alpha1/zz_generated.deepcopy.go +++ b/api/postgresql/v1alpha1/zz_generated.deepcopy.go @@ -45,6 +45,21 @@ func (in *DatabaseModulesList) DeepCopy() *DatabaseModulesList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenericUserConnection) DeepCopyInto(out *GenericUserConnection) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericUserConnection. +func (in *GenericUserConnection) DeepCopy() *GenericUserConnection { + if in == nil { + return nil + } + out := new(GenericUserConnection) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresqlDatabase) DeepCopyInto(out *PostgresqlDatabase) { *out = *in @@ -157,7 +172,7 @@ func (in *PostgresqlEngineConfiguration) DeepCopyInto(out *PostgresqlEngineConfi *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -214,6 +229,11 @@ func (in *PostgresqlEngineConfigurationList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PostgresqlEngineConfigurationSpec) DeepCopyInto(out *PostgresqlEngineConfigurationSpec) { *out = *in + if in.UserConnections != nil { + in, out := &in.UserConnections, &out.UserConnections + *out = new(UserConnections) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PostgresqlEngineConfigurationSpec. @@ -474,3 +494,28 @@ func (in *StatusPostgresRoles) DeepCopy() *StatusPostgresRoles { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserConnections) DeepCopyInto(out *UserConnections) { + *out = *in + if in.PrimaryConnection != nil { + in, out := &in.PrimaryConnection, &out.PrimaryConnection + *out = new(GenericUserConnection) + **out = **in + } + if in.BouncerConnection != nil { + in, out := &in.BouncerConnection, &out.BouncerConnection + *out = new(GenericUserConnection) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConnections. +func (in *UserConnections) DeepCopy() *UserConnections { + if in == nil { + return nil + } + out := new(UserConnections) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/postgresql.easymile.com_postgresqlengineconfigurations.yaml b/config/crd/bases/postgresql.easymile.com_postgresqlengineconfigurations.yaml index 09787af..effe3a8 100644 --- a/config/crd/bases/postgresql.easymile.com_postgresqlengineconfigurations.yaml +++ b/config/crd/bases/postgresql.easymile.com_postgresqlengineconfigurations.yaml @@ -83,6 +83,47 @@ spec: uriArgs: description: URI args like sslmode, ... type: string + userConnections: + description: |- + User connections used for secret generation + That will be used to generate secret with primary server as url or + to use the pg bouncer one. + Note: Operator won't check those values. + properties: + bouncerConnection: + description: Bouncer connection is referring to a pg bouncer node. + properties: + host: + description: Hostname + type: string + port: + description: Port + type: integer + uriArgs: + description: URI args like sslmode, ... + type: string + required: + - host + - uriArgs + type: object + primaryConnection: + description: Primary connection is referring to the primary node + connection. + properties: + host: + description: Hostname + type: string + port: + description: Port + type: integer + uriArgs: + description: URI args like sslmode, ... + type: string + required: + - host + - uriArgs + type: object + type: object waitLinkedResourcesDeletion: description: Wait for linked resource to be deleted type: boolean diff --git a/config/crd/bases/postgresql.easymile.com_postgresqluserroles.yaml b/config/crd/bases/postgresql.easymile.com_postgresqluserroles.yaml index 996acd2..b547360 100644 --- a/config/crd/bases/postgresql.easymile.com_postgresqluserroles.yaml +++ b/config/crd/bases/postgresql.easymile.com_postgresqluserroles.yaml @@ -72,6 +72,15 @@ spec: description: Privileges items: properties: + connectionType: + default: PRIMARY + description: |- + User Connection type. + This is referring to the user connection type needed for this user. + enum: + - PRIMARY + - BOUNCER + type: string database: description: Postgresql Database properties: diff --git a/internal/controller/postgresql/postgresqlengineconfiguration_controller.go b/internal/controller/postgresql/postgresqlengineconfiguration_controller.go index d0cacd0..790aef8 100644 --- a/internal/controller/postgresql/postgresqlengineconfiguration_controller.go +++ b/internal/controller/postgresql/postgresqlengineconfiguration_controller.go @@ -38,6 +38,7 @@ import ( const ( PGECRequeueDelayErrorNumberSeconds = 5 + DefaultPGPort = 5432 ) // PostgresqlEngineConfigurationReconciler reconciles a PostgresqlEngineConfiguration object. @@ -272,7 +273,7 @@ func (r *PostgresqlEngineConfigurationReconciler) updateInstance( func (*PostgresqlEngineConfigurationReconciler) addDefaultValues(instance *postgresqlv1alpha1.PostgresqlEngineConfiguration) { // Check port if instance.Spec.Port == 0 { - instance.Spec.Port = 5432 + instance.Spec.Port = DefaultPGPort } // Check default database if instance.Spec.DefaultDatabase == "" { @@ -283,6 +284,36 @@ func (*PostgresqlEngineConfigurationReconciler) addDefaultValues(instance *postg if instance.Spec.CheckInterval == "" { instance.Spec.CheckInterval = "30s" } + + // Check if user connections aren't set to init it + if instance.Spec.UserConnections == nil { + instance.Spec.UserConnections = &postgresqlv1alpha1.UserConnections{} + } + + // Check if primary user connections aren't set to init it + if instance.Spec.UserConnections.PrimaryConnection == nil { + instance.Spec.UserConnections.PrimaryConnection = &postgresqlv1alpha1.GenericUserConnection{ + Host: instance.Spec.Host, + URIArgs: instance.Spec.URIArgs, + Port: instance.Spec.Port, + } + } + + // Check if primary user connections are set and fully valued + if instance.Spec.UserConnections.PrimaryConnection != nil { + // Check port + if instance.Spec.UserConnections.PrimaryConnection.Port == 0 { + instance.Spec.UserConnections.PrimaryConnection.Port = DefaultPGPort + } + } + + // Check if bouncer user connections are set and fully valued + if instance.Spec.UserConnections.BouncerConnection != nil { + // Check port + if instance.Spec.UserConnections.BouncerConnection.Port == 0 { + instance.Spec.UserConnections.BouncerConnection.Port = DefaultPGPort + } + } } func (r *PostgresqlEngineConfigurationReconciler) manageError( diff --git a/internal/controller/postgresql/postgresqlengineconfiguration_controller_test.go b/internal/controller/postgresql/postgresqlengineconfiguration_controller_test.go index edab8d0..5e69907 100644 --- a/internal/controller/postgresql/postgresqlengineconfiguration_controller_test.go +++ b/internal/controller/postgresql/postgresqlengineconfiguration_controller_test.go @@ -267,7 +267,7 @@ var _ = Describe("PostgresqlEngineConfiguration tests", func() { Expect(updatedPgec.Status.Message).To(BeEquivalentTo(`secret ` + pgecSecretName + ` must contain "user" and "password" values`)) }) - It("should be ok to set default values", func() { + It("should be ok to set default values (minimal pgec)", func() { // Create secret sec := setupPGECSecret() @@ -337,6 +337,95 @@ var _ = Describe("PostgresqlEngineConfiguration tests", func() { Expect(updatedPgec.Spec.CheckInterval).To(BeEquivalentTo("30s")) Expect(updatedPgec.Spec.Port).To(BeEquivalentTo(5432)) Expect(updatedPgec.Spec.DefaultDatabase).To(BeEquivalentTo("postgres")) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.Host).To(BeEquivalentTo("localhost")) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.Port).To(BeEquivalentTo(5432)) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.URIArgs).To(BeEquivalentTo("sslmode=disable")) + Expect(updatedPgec.Spec.UserConnections.BouncerConnection).To(BeNil()) + }) + + It("should be ok to create it with only bouncer user connections", func() { + // Create secret + sec := setupPGECSecret() + + // Get secret to be sure + Eventually( + func() error { + return k8sClient.Get(ctx, types.NamespacedName{ + Name: sec.Name, + Namespace: sec.Namespace, + }, sec) + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Create pgec + prov := &postgresqlv1alpha1.PostgresqlEngineConfiguration{ + ObjectMeta: v1.ObjectMeta{ + Name: pgecName, + Namespace: pgecNamespace, + }, + Spec: postgresqlv1alpha1.PostgresqlEngineConfigurationSpec{ + Provider: "", + Host: "localhost", + Port: 5432, + URIArgs: "sslmode=disable", + DefaultDatabase: "postgres", + CheckInterval: "30s", + SecretName: pgecSecretName, + UserConnections: &postgresqlv1alpha1.UserConnections{ + BouncerConnection: &postgresqlv1alpha1.GenericUserConnection{ + Host: "localhost", + Port: 5432, + URIArgs: "sslmode=disable", + }, + }, + }, + } + + // Create pgec + Expect(k8sClient.Create(ctx, prov)).Should(Succeed()) + + updatedPgec := &postgresqlv1alpha1.PostgresqlEngineConfiguration{} + // Get updated pgec + Eventually( + func() error { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: pgecName, + Namespace: pgecNamespace, + }, updatedPgec) + // Check error + if err != nil { + return err + } + + // Check if status hasn't been updated + if updatedPgec.Status.Phase == postgresqlv1alpha1.EngineNoPhase { + return errors.New("pgec hasn't been updated by operator") + } + + return nil + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Checks + Expect(updatedPgec.Status.Ready).To(BeTrue()) + Expect(updatedPgec.Status.Phase).To(BeEquivalentTo(postgresqlv1alpha1.EngineValidatedPhase)) + Expect(updatedPgec.Status.LastValidatedTime).NotTo(BeEquivalentTo("")) + Expect(updatedPgec.Status.Message).To(BeEquivalentTo("")) + Expect(updatedPgec.Spec.CheckInterval).To(BeEquivalentTo("30s")) + Expect(updatedPgec.Spec.Port).To(BeEquivalentTo(5432)) + Expect(updatedPgec.Spec.DefaultDatabase).To(BeEquivalentTo("postgres")) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.Host).To(BeEquivalentTo("localhost")) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.Port).To(BeEquivalentTo(5432)) + Expect(updatedPgec.Spec.UserConnections.PrimaryConnection.URIArgs).To(BeEquivalentTo("sslmode=disable")) + Expect(updatedPgec.Spec.UserConnections.BouncerConnection.Host).To(BeEquivalentTo("localhost")) + Expect(updatedPgec.Spec.UserConnections.BouncerConnection.Port).To(BeEquivalentTo(5432)) + Expect(updatedPgec.Spec.UserConnections.BouncerConnection.URIArgs).To(BeEquivalentTo("sslmode=disable")) }) It("should be ok to set everything", func() { diff --git a/internal/controller/postgresql/postgresqluserrole_controller.go b/internal/controller/postgresql/postgresqluserrole_controller.go index 162cc05..8c9178a 100644 --- a/internal/controller/postgresql/postgresqluserrole_controller.go +++ b/internal/controller/postgresql/postgresqluserrole_controller.go @@ -130,8 +130,14 @@ func (r *PostgresqlUserRoleReconciler) Reconcile(ctx context.Context, req ctrl.R if err != nil { return r.manageError(ctx, reqLogger, instance, originalPatch, err) } + // Find PGEC cache + pgecCache, err := r.getPGECInstances(ctx, dbCache, true) + // Check error + if err != nil { + return r.manageError(ctx, reqLogger, instance, originalPatch, err) + } // Create PG instances - pgInstancesCache, err := r.getPGInstances(ctx, reqLogger, dbCache, true) + pgInstancesCache, err := r.getPGInstances(ctx, reqLogger, pgecCache, true) // Check error if err != nil { return r.manageError(ctx, reqLogger, instance, originalPatch, err) @@ -190,6 +196,20 @@ func (r *PostgresqlUserRoleReconciler) Reconcile(ctx context.Context, req ctrl.R } } + // Find PGEC cache + pgecCache, err := r.getPGECInstances(ctx, dbCache, false) + // Check error + if err != nil { + return r.manageError(ctx, reqLogger, instance, originalPatch, err) + } + + // Validate with cluster data + err = r.validateInstanceWithClusterInfo(reqLogger, instance, dbCache, pgecCache) + // Check error + if err != nil { + return r.manageError(ctx, reqLogger, instance, originalPatch, err) + } + // Add finalizer updated, err := r.updateInstance(ctx, instance) // Check error @@ -245,7 +265,7 @@ func (r *PostgresqlUserRoleReconciler) Reconcile(ctx context.Context, req ctrl.R } // Create PG instances - pgInstancesCache, err := r.getPGInstances(ctx, reqLogger, dbCache, false) + pgInstancesCache, err := r.getPGInstances(ctx, reqLogger, pgecCache, false) // Check error if err != nil { return r.manageError(ctx, reqLogger, instance, originalPatch, err) @@ -307,7 +327,7 @@ func (r *PostgresqlUserRoleReconciler) Reconcile(ctx context.Context, req ctrl.R // // Manage secrets - err = r.manageSecrets(ctx, reqLogger, instance, pgInstancesCache, pgecDBPrivilegeCache, username, password) + err = r.manageSecrets(ctx, reqLogger, instance, pgecCache, pgecDBPrivilegeCache, username, password) // Check error if err != nil { return r.manageError(ctx, reqLogger, instance, originalPatch, err) @@ -327,7 +347,7 @@ func (r *PostgresqlUserRoleReconciler) manageSecrets( ctx context.Context, logger logr.Logger, instance *v1alpha1.PostgresqlUserRole, - pgInstanceCache map[string]postgres.PG, + pgecCache map[string]*v1alpha1.PostgresqlEngineConfiguration, pgecDBPrivilegeCache map[string][]*dbPrivilegeCache, username, password string, ) error { @@ -356,7 +376,7 @@ func (r *PostgresqlUserRoleReconciler) manageSecrets( privilegeCache.UserPrivilege, privilegeCache.DBInstance, username, password, - pgInstanceCache[key], + pgecCache[key], ) // Check error if err2 != nil { @@ -479,10 +499,17 @@ func (r *PostgresqlUserRoleReconciler) newSecretForPGUser( rolePrivilege *v1alpha1.PostgresqlUserRolePrivilege, dbInstance *v1alpha1.PostgresqlDatabase, username, password string, - pg postgres.PG, + pgec *v1alpha1.PostgresqlEngineConfiguration, ) (*corev1.Secret, error) { - pgUserURL := postgres.TemplatePostgresqlURL(pg.GetHost(), username, password, dbInstance.Status.Database, pg.GetPort()) - pgUserURLWArgs := postgres.TemplatePostgresqlURLWithArgs(pg.GetHost(), username, password, pg.GetArgs(), dbInstance.Status.Database, pg.GetPort()) + // Prepare user connections with primary as default value + uc := pgec.Spec.UserConnections.PrimaryConnection + // Check if it is a bouncer connection + if rolePrivilege.ConnectionType == v1alpha1.BouncerConnectionType { + uc = pgec.Spec.UserConnections.BouncerConnection + } + + pgUserURL := postgres.TemplatePostgresqlURL(uc.Host, username, password, dbInstance.Status.Database, uc.Port) + pgUserURLWArgs := postgres.TemplatePostgresqlURLWithArgs(uc.Host, username, password, uc.URIArgs, dbInstance.Status.Database, uc.Port) labels := map[string]string{ "app": instance.Name, } @@ -498,9 +525,9 @@ func (r *PostgresqlUserRoleReconciler) newSecretForPGUser( "PASSWORD": []byte(password), "LOGIN": []byte(username), "DATABASE": []byte(dbInstance.Status.Database), - "HOST": []byte(pg.GetHost()), - "PORT": []byte(strconv.Itoa(pg.GetPort())), - "ARGS": []byte(pg.GetArgs()), + "HOST": []byte(uc.Host), + "PORT": []byte(strconv.Itoa(uc.Port)), + "ARGS": []byte(uc.URIArgs), }, } @@ -627,7 +654,10 @@ func (r *PostgresqlUserRoleReconciler) managePGUserRights( return nil } -func (*PostgresqlUserRoleReconciler) getDBRoleFromPrivilege(dbInstance *v1alpha1.PostgresqlDatabase, userRolePrivilege *v1alpha1.PostgresqlUserRolePrivilege) string { +func (*PostgresqlUserRoleReconciler) getDBRoleFromPrivilege( + dbInstance *v1alpha1.PostgresqlDatabase, + userRolePrivilege *v1alpha1.PostgresqlUserRolePrivilege, +) string { switch userRolePrivilege.Privilege { case v1alpha1.ReaderPrivilege: return dbInstance.Status.Roles.Reader @@ -1032,13 +1062,41 @@ func (r *PostgresqlUserRoleReconciler) manageActiveSessionsAndDropOldRoles( func (r *PostgresqlUserRoleReconciler) getPGInstances( ctx context.Context, logger logr.Logger, - dbCache map[string]*v1alpha1.PostgresqlDatabase, + pgecCache map[string]*v1alpha1.PostgresqlEngineConfiguration, ignoreNotFound bool, ) (map[string]postgres.PG, error) { // Prepare result res := make(map[string]postgres.PG) - // Prepare temporary map + // Loop + for key, pgec := range pgecCache { + sec, err := utils.FindSecretPgEngineCfg(ctx, r.Client, pgec) + // Check error + if err != nil { + if errors.IsNotFound(err) && ignoreNotFound { + // Ignore and continue + continue + } + + // Return + return nil, err + } + + // Save + res[key] = utils.CreatePgInstance(logger, sec.Data, pgec) + } + + return res, nil +} + +func (r *PostgresqlUserRoleReconciler) getPGECInstances( + ctx context.Context, + dbCache map[string]*v1alpha1.PostgresqlDatabase, + ignoreNotFound bool, +) (map[string]*v1alpha1.PostgresqlEngineConfiguration, error) { + // Prepare result + res := make(map[string]*v1alpha1.PostgresqlEngineConfiguration) + // Loop for _, item := range dbCache { // Build key @@ -1065,20 +1123,8 @@ func (r *PostgresqlUserRoleReconciler) getPGInstances( return nil, err } - sec, err := utils.FindSecretPgEngineCfg(ctx, r.Client, pgec) - // Check error - if err != nil { - if errors.IsNotFound(err) && ignoreNotFound { - // Ignore and continue - continue - } - - // Return - return nil, err - } - // Save - res[key] = utils.CreatePgInstance(logger, sec.Data, pgec) + res[key] = pgec } return res, nil @@ -1132,6 +1178,32 @@ func (r *PostgresqlUserRoleReconciler) getDatabaseInstances( return res, res2, nil } +func (r *PostgresqlUserRoleReconciler) validateInstanceWithClusterInfo( + reqLogger logr.Logger, + instance *v1alpha1.PostgresqlUserRole, + dbCache map[string]*v1alpha1.PostgresqlDatabase, + pgecCache map[string]*v1alpha1.PostgresqlEngineConfiguration, +) error { + // Loop over privileges to check if primary or bouncer are enabled on pgec + for _, privi := range instance.Spec.Privileges { + // Create database key + dbKey := utils.CreateNameKey(privi.Database.Name, privi.Database.Namespace, instance.Namespace) + // Get pgdb + pgdb := dbCache[dbKey] + // Create pgec key + pgecKey := utils.CreateNameKey(pgdb.Spec.EngineConfiguration.Name, pgdb.Spec.EngineConfiguration.Namespace, pgdb.Namespace) + // Get pgec + pgec := pgecCache[pgecKey] + // Check if bouncer mode is asked and not available + if privi.ConnectionType == v1alpha1.BouncerConnectionType && pgec.Spec.UserConnections.BouncerConnection == nil { + return errors.NewBadRequest("bouncer connection asked but not supported in engine configuration") + } + } + + // Default + return nil +} + func (r *PostgresqlUserRoleReconciler) validateInstance( ctx context.Context, instance *v1alpha1.PostgresqlUserRole, @@ -1192,19 +1264,20 @@ func (r *PostgresqlUserRoleReconciler) validateInstance( // Validate not multiple time the same db in the list of privileges for i, privi := range instance.Spec.Privileges { + // Prepare values + priviNamespace := privi.Database.Namespace + // Populate with instance + if priviNamespace == "" { + priviNamespace = instance.Namespace + } + // Search for the same db for j, privi2 := range instance.Spec.Privileges { // Check that this isn't the same item if i != j { // Prepare values - priviNamespace := privi.Database.Namespace privi2Namespace := privi2.Database.Namespace - // Populate with instance - if priviNamespace == "" { - priviNamespace = instance.Namespace - } - if privi2Namespace == "" { privi2Namespace = instance.Namespace } diff --git a/internal/controller/postgresql/postgresqluserrole_controller_test.go b/internal/controller/postgresql/postgresqluserrole_controller_test.go index baab0a6..6d5a8c5 100644 --- a/internal/controller/postgresql/postgresqluserrole_controller_test.go +++ b/internal/controller/postgresql/postgresqluserrole_controller_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/easymile/postgresql-operator/api/postgresql/common" + "github.com/easymile/postgresql-operator/api/postgresql/v1alpha1" postgresqlv1alpha1 "github.com/easymile/postgresql-operator/api/postgresql/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -605,8 +606,8 @@ var _ = Describe("PostgresqlUserRole tests", func() { // Checks Expect(item.Status.Ready).To(BeFalse()) - Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleNoPhase)) Expect(item.Status.Message).To(Equal("")) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleNoPhase)) }) It("should be ok without work secret name", func() { @@ -674,6 +675,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).ToNot(Equal(pgurWorkSecretName)) Expect(item.Spec.WorkGeneratedSecretName).To(MatchRegexp(DefaultWorkGeneratedSecretNamePrefix + ".*")) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -696,7 +698,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, sec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(pgurImportUsername, pgurImportPassword) @@ -735,6 +737,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -757,7 +760,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, sec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(pgurImportUsername, pgurImportPassword) @@ -797,6 +800,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -824,8 +828,8 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, sec2)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec) - checkPGURSecretValues(pgurDBSecretName2, pgurNamespace, pgdbDBName2, pgurImportUsername, pgurImportPassword, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) + checkPGURSecretValues(pgurDBSecretName2, pgurNamespace, pgdbDBName2, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(pgurImportUsername, pgurImportPassword) @@ -872,6 +876,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -936,6 +941,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -1000,6 +1006,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -1061,6 +1068,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -1125,6 +1133,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -1170,6 +1179,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Namespace: pgurNamespace, }, item)).To(Succeed()) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d2, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d2.After(d)).To(BeTrue()) @@ -1183,7 +1193,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, sec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, updatedPass, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, updatedPass, pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(pgurImportUsername, updatedPass) @@ -1222,6 +1232,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal("")) Expect(item.Status.PostgresRole).To(Equal(pgurImportUsername)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -1267,6 +1278,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Namespace: pgurNamespace, }, item)).To(Succeed()) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d2, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d2.After(d)).To(BeTrue()) @@ -1281,7 +1293,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, sec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, updatedUser, pgurImportPassword, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, updatedUser, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(updatedUser, pgurImportPassword) @@ -1357,7 +1369,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(string(sec.Data[PasswordSecretKey])).To(Equal(pgurImportPassword)) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, updatedUser, pgurImportPassword, pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, updatedUser, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) // Get pgur Expect(k8sClient.Get(ctx, types.NamespacedName{ @@ -1925,6 +1937,222 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(worksecOri.Data[UsernameSecretKey]).To(Equal(worksec.Data[UsernameSecretKey])) }) + + It("should be ok to generate a primary user role with a bouncer enabled pgec", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGUR() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + // Validate + checkPGURSecretValues(item.Spec.Privileges[0].GeneratedSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.PrimaryConnectionType) + }) + + It("should be ok to generate a bouncer secret with a bouncer user role", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGURWithBouncer() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + // Validate + checkPGURSecretValues(item.Spec.Privileges[0].GeneratedSecretName, pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, v1alpha1.BouncerConnectionType) + }) + + It("should be fail when a bouncer user role is asked but pgec isn't supporting it", func() { + // Setup pgec + setupPGEC("30s", false) + // Create pgdb + setupPGDB(false) + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGURWithBouncer() + + // Checks + Expect(item.Status.Ready).To(BeFalse()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleFailedPhase)) + Expect(item.Status.Message).To(Equal("bouncer connection asked but not supported in engine configuration")) + }) + + It("should be ok to generate a bouncer and a primary secret with a bouncer and a primary user role", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + setupPGDB2() + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGURWith2DatabasesWithPrimaryAndBouncer() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, pgec, + v1alpha1.PrimaryConnectionType, + ) + checkPGURSecretValues( + item.Spec.Privileges[1].GeneratedSecretName, + pgurNamespace, pgdbDBName2, pgurImportUsername, pgurImportPassword, pgec, + v1alpha1.BouncerConnectionType, + ) + }) + + It("should be ok to create a primary user role and change it to a bouncer one", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGUR() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, + pgec, v1alpha1.PrimaryConnectionType, + ) + + // Get current secret + sec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec)).To(Succeed()) + + // Update privilege for a bouncer one + item.Spec.Privileges[0].ConnectionType = v1alpha1.BouncerConnectionType + // Save + Expect(k8sClient.Update(ctx, item)).To(Succeed()) + + sec2 := &corev1.Secret{} + Eventually( + func() error { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec2) + // Check error + if err != nil { + return err + } + + // Check if sec have been updated + if string(sec.Data["POSTGRES_URL"]) == string(sec2.Data["POSTGRES_URL"]) { + return errors.New("Secret not updated") + } + + return nil + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, + pgec, v1alpha1.BouncerConnectionType, + ) + }) + + It("should be ok to create a bouncer user role and change it to a primary one", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + // Create secret + setupPGURImportSecret() + + item := setupProvidedPGURWithBouncer() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, + pgec, v1alpha1.BouncerConnectionType, + ) + + // Get current secret + sec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec)).To(Succeed()) + + // Update privilege for a bouncer one + item.Spec.Privileges[0].ConnectionType = v1alpha1.PrimaryConnectionType + // Save + Expect(k8sClient.Update(ctx, item)).To(Succeed()) + + sec2 := &corev1.Secret{} + Eventually( + func() error { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec2) + // Check error + if err != nil { + return err + } + + // Check if sec have been updated + if string(sec.Data["POSTGRES_URL"]) == string(sec2.Data["POSTGRES_URL"]) { + return errors.New("Secret not updated") + } + + return nil + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, pgurImportUsername, pgurImportPassword, + pgec, v1alpha1.PrimaryConnectionType, + ) + }) }) Describe("Managed mode", func() { @@ -2399,7 +2627,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username, string(sec.Data[PasswordSecretKey])) @@ -2436,6 +2664,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2459,7 +2688,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username, string(sec.Data[PasswordSecretKey])) @@ -2498,6 +2727,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2526,8 +2756,8 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec2)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec) - checkPGURSecretValues(pgurDBSecretName2, pgurNamespace, pgdbDBName2, username, string(sec.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) + checkPGURSecretValues(pgurDBSecretName2, pgurNamespace, pgdbDBName2, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username, string(sec.Data[PasswordSecretKey])) @@ -2573,6 +2803,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2635,6 +2866,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2694,6 +2926,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2753,6 +2986,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2812,6 +3046,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -2860,6 +3095,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Namespace: pgurNamespace, }, item)).To(Succeed()) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.RolePrefix).To(Equal(updatedUserPrefix)) d2, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -2875,7 +3111,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username, string(sec.Data[PasswordSecretKey])) @@ -2951,7 +3187,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(workSec2.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(workSec2.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err := connectAs(username, string(workSec2.Data[PasswordSecretKey])) @@ -3093,6 +3329,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) Expect(d.After(preDate)).To(BeTrue()) @@ -3116,7 +3353,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username, string(sec.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username, string(sec.Data[PasswordSecretKey])) @@ -3155,6 +3392,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3226,7 +3464,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username2, string(workSec2.Data[PasswordSecretKey])) @@ -3269,6 +3507,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3344,7 +3583,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username2, string(workSec2.Data[PasswordSecretKey])) @@ -3395,6 +3634,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3470,7 +3710,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { }, dbsec)).Should(Succeed()) // Validate - checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec) + checkPGURSecretValues(pgurDBSecretName, pgurNamespace, pgdbDBName, username2, string(workSec2.Data[PasswordSecretKey]), pgec, v1alpha1.PrimaryConnectionType) // Connect to check user _, err = connectAs(username2, string(workSec2.Data[PasswordSecretKey])) @@ -3547,6 +3787,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3653,6 +3894,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3745,6 +3987,7 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(item.Status.RolePrefix).To(Equal(pgurRolePrefix)) Expect(item.Status.PostgresRole).To(Equal(username)) Expect(item.Spec.WorkGeneratedSecretName).To(Equal(pgurWorkSecretName)) + Expect(item.Spec.Privileges[0].ConnectionType).To(Equal(postgresqlv1alpha1.PrimaryConnectionType)) Expect(item.Status.OldPostgresRoles).To(Equal([]string{})) d, err := time.Parse(time.RFC3339, item.Status.LastPasswordChangedTime) Expect(err).To(Succeed()) @@ -3817,5 +4060,251 @@ var _ = Describe("PostgresqlUserRole tests", func() { Expect(worksecOri.Data[UsernameSecretKey]).To(Equal(worksec.Data[UsernameSecretKey])) }) + + It("should be ok to generate a primary secret with a bouncer enabled pgec", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + item := setupManagedPGUR("") + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + username := pgurRolePrefix + Login0Suffix + // Get work secret + workSec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.WorkGeneratedSecretName, + Namespace: pgurNamespace, + }, workSec)).Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.PrimaryConnectionType, + ) + }) + + It("should be ok to generate a bouncer secret with a bouncer user role", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + item := setupManagedPGURWithBouncer("") + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + username := pgurRolePrefix + Login0Suffix + // Get work secret + workSec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.WorkGeneratedSecretName, + Namespace: pgurNamespace, + }, workSec)).Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.BouncerConnectionType, + ) + }) + + It("should be fail when a bouncer user role is asked but pgec isn't supporting it", func() { + // Setup pgec + setupPGEC("30s", false) + // Create pgdb + setupPGDB(false) + + item := setupManagedPGURWithBouncer("") + + // Checks + Expect(item.Status.Ready).To(BeFalse()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleFailedPhase)) + Expect(item.Status.Message).To(Equal("bouncer connection asked but not supported in engine configuration")) + }) + + It("should be ok to generate a bouncer and a primary secret with a bouncer and a primary user role", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + setupPGDB2() + + item := setupManagedPGURWith2DatabasesWithPrimaryAndBouncer() + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + username := pgurRolePrefix + Login0Suffix + // Get work secret + workSec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.WorkGeneratedSecretName, + Namespace: pgurNamespace, + }, workSec)).Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.PrimaryConnectionType, + ) + checkPGURSecretValues( + item.Spec.Privileges[1].GeneratedSecretName, + pgurNamespace, pgdbDBName2, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.BouncerConnectionType, + ) + }) + + It("should be ok to create a primary user role and change it to a bouncer one", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + item := setupManagedPGUR("") + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + username := pgurRolePrefix + Login0Suffix + // Get work secret + workSec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.WorkGeneratedSecretName, + Namespace: pgurNamespace, + }, workSec)).Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.PrimaryConnectionType, + ) + + // Get current secret + sec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec)).To(Succeed()) + + // Update privilege for a bouncer one + item.Spec.Privileges[0].ConnectionType = v1alpha1.BouncerConnectionType + // Save + Expect(k8sClient.Update(ctx, item)).To(Succeed()) + + sec2 := &corev1.Secret{} + Eventually( + func() error { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec2) + // Check error + if err != nil { + return err + } + + // Check if sec have been updated + if string(sec.Data["POSTGRES_URL"]) == string(sec2.Data["POSTGRES_URL"]) { + return errors.New("Secret not updated") + } + + return nil + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.BouncerConnectionType, + ) + }) + + It("should be ok to create a bouncer user role and change it to a primary one", func() { + // Setup pgec + pgec, _ := setupPGECWithBouncer("30s", false) + // Create pgdb + setupPGDB(false) + + item := setupManagedPGURWithBouncer("") + + // Checks + Expect(item.Status.Ready).To(BeTrue()) + Expect(item.Status.Phase).To(Equal(postgresqlv1alpha1.UserRoleCreatedPhase)) + + username := pgurRolePrefix + Login0Suffix + // Get work secret + workSec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.WorkGeneratedSecretName, + Namespace: pgurNamespace, + }, workSec)).Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.BouncerConnectionType, + ) + + // Get current secret + sec := &corev1.Secret{} + Expect(k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec)).To(Succeed()) + + // Update privilege for a bouncer one + item.Spec.Privileges[0].ConnectionType = v1alpha1.PrimaryConnectionType + // Save + Expect(k8sClient.Update(ctx, item)).To(Succeed()) + + sec2 := &corev1.Secret{} + Eventually( + func() error { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: item.Spec.Privileges[0].GeneratedSecretName, + Namespace: pgurNamespace, + }, sec2) + // Check error + if err != nil { + return err + } + + // Check if sec have been updated + if string(sec.Data["POSTGRES_URL"]) == string(sec2.Data["POSTGRES_URL"]) { + return errors.New("Secret not updated") + } + + return nil + }, + generalEventuallyTimeout, + generalEventuallyInterval, + ). + Should(Succeed()) + + // Validate + checkPGURSecretValues( + item.Spec.Privileges[0].GeneratedSecretName, + pgurNamespace, pgdbDBName, username, string(workSec.Data[PasswordSecretKey]), + pgec, v1alpha1.PrimaryConnectionType, + ) + }) }) }) diff --git a/internal/controller/postgresql/suite_test.go b/internal/controller/postgresql/suite_test.go index cf795ed..ff6cde7 100644 --- a/internal/controller/postgresql/suite_test.go +++ b/internal/controller/postgresql/suite_test.go @@ -381,6 +381,30 @@ func setupProvidedPGUR() *postgresqlv1alpha1.PostgresqlUserRole { return setupSavePGURInternal(it) } +func setupProvidedPGURWithBouncer() *postgresqlv1alpha1.PostgresqlUserRole { + it := &postgresqlv1alpha1.PostgresqlUserRole{ + ObjectMeta: v1.ObjectMeta{ + Name: pgurName, + Namespace: pgurNamespace, + }, + Spec: postgresqlv1alpha1.PostgresqlUserRoleSpec{ + Mode: postgresqlv1alpha1.ProvidedMode, + ImportSecretName: pgurImportSecretName, + WorkGeneratedSecretName: pgurWorkSecretName, + Privileges: []*postgresqlv1alpha1.PostgresqlUserRolePrivilege{ + { + ConnectionType: postgresqlv1alpha1.BouncerConnectionType, + Privilege: postgresqlv1alpha1.OwnerPrivilege, + Database: &common.CRLink{Name: pgdbName, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName, + }, + }, + }, + } + + return setupSavePGURInternal(it) +} + func setupProvidedPGURWith2Databases() *postgresqlv1alpha1.PostgresqlUserRole { it := &postgresqlv1alpha1.PostgresqlUserRole{ ObjectMeta: v1.ObjectMeta{ @@ -409,6 +433,61 @@ func setupProvidedPGURWith2Databases() *postgresqlv1alpha1.PostgresqlUserRole { return setupSavePGURInternal(it) } +func setupProvidedPGURWith2DatabasesWithPrimaryAndBouncer() *postgresqlv1alpha1.PostgresqlUserRole { + it := &postgresqlv1alpha1.PostgresqlUserRole{ + ObjectMeta: v1.ObjectMeta{ + Name: pgurName, + Namespace: pgurNamespace, + }, + Spec: postgresqlv1alpha1.PostgresqlUserRoleSpec{ + Mode: postgresqlv1alpha1.ProvidedMode, + ImportSecretName: pgurImportSecretName, + WorkGeneratedSecretName: pgurWorkSecretName, + Privileges: []*postgresqlv1alpha1.PostgresqlUserRolePrivilege{ + { + ConnectionType: postgresqlv1alpha1.PrimaryConnectionType, + Privilege: postgresqlv1alpha1.OwnerPrivilege, + Database: &common.CRLink{Name: pgdbName, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName, + }, + { + ConnectionType: postgresqlv1alpha1.BouncerConnectionType, + Privilege: postgresqlv1alpha1.WriterPrivilege, + Database: &common.CRLink{Name: pgdbName2, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName2, + }, + }, + }, + } + + return setupSavePGURInternal(it) +} + +func setupManagedPGURWithBouncer(userPasswordRotationDuration string) *postgresqlv1alpha1.PostgresqlUserRole { + it := &postgresqlv1alpha1.PostgresqlUserRole{ + ObjectMeta: v1.ObjectMeta{ + Name: pgurName, + Namespace: pgurNamespace, + }, + Spec: postgresqlv1alpha1.PostgresqlUserRoleSpec{ + Mode: postgresqlv1alpha1.ManagedMode, + RolePrefix: pgurRolePrefix, + WorkGeneratedSecretName: pgurWorkSecretName, + UserPasswordRotationDuration: userPasswordRotationDuration, + Privileges: []*postgresqlv1alpha1.PostgresqlUserRolePrivilege{ + { + ConnectionType: postgresqlv1alpha1.BouncerConnectionType, + Privilege: postgresqlv1alpha1.OwnerPrivilege, + Database: &common.CRLink{Name: pgdbName, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName, + }, + }, + }, + } + + return setupSavePGURInternal(it) +} + func setupManagedPGUR(userPasswordRotationDuration string) *postgresqlv1alpha1.PostgresqlUserRole { it := &postgresqlv1alpha1.PostgresqlUserRole{ ObjectMeta: v1.ObjectMeta{ @@ -461,6 +540,36 @@ func setupManagedPGURWith2Databases() *postgresqlv1alpha1.PostgresqlUserRole { return setupSavePGURInternal(it) } +func setupManagedPGURWith2DatabasesWithPrimaryAndBouncer() *postgresqlv1alpha1.PostgresqlUserRole { + it := &postgresqlv1alpha1.PostgresqlUserRole{ + ObjectMeta: v1.ObjectMeta{ + Name: pgurName, + Namespace: pgurNamespace, + }, + Spec: postgresqlv1alpha1.PostgresqlUserRoleSpec{ + Mode: postgresqlv1alpha1.ManagedMode, + RolePrefix: pgurRolePrefix, + WorkGeneratedSecretName: pgurWorkSecretName, + Privileges: []*postgresqlv1alpha1.PostgresqlUserRolePrivilege{ + { + ConnectionType: postgresqlv1alpha1.PrimaryConnectionType, + Privilege: postgresqlv1alpha1.OwnerPrivilege, + Database: &common.CRLink{Name: pgdbName, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName, + }, + { + ConnectionType: postgresqlv1alpha1.BouncerConnectionType, + Privilege: postgresqlv1alpha1.WriterPrivilege, + Database: &common.CRLink{Name: pgdbName2, Namespace: pgdbNamespace}, + GeneratedSecretName: pgurDBSecretName2, + }, + }, + }, + } + + return setupSavePGURInternal(it) +} + func setupSavePGURInternal(it *postgresqlv1alpha1.PostgresqlUserRole) *postgresqlv1alpha1.PostgresqlUserRole { // Create user Expect(k8sClient.Create(ctx, it)).Should(Succeed()) @@ -495,6 +604,33 @@ func setupSavePGURInternal(it *postgresqlv1alpha1.PostgresqlUserRole) *postgresq func setupPGEC( checkInterval string, waitLinkedResourcesDeletion bool, +) (*postgresqlv1alpha1.PostgresqlEngineConfiguration, *corev1.Secret) { + return setupPGECInternal(checkInterval, waitLinkedResourcesDeletion, &postgresqlv1alpha1.GenericUserConnection{ + Host: "localhost", + Port: 5432, + URIArgs: "sslmode=disable", + }, nil) +} + +func setupPGECWithBouncer( + checkInterval string, + waitLinkedResourcesDeletion bool, +) (*postgresqlv1alpha1.PostgresqlEngineConfiguration, *corev1.Secret) { + return setupPGECInternal(checkInterval, waitLinkedResourcesDeletion, &postgresqlv1alpha1.GenericUserConnection{ + Host: "localhost", + Port: 5432, + URIArgs: "sslmode=disable", + }, &postgresqlv1alpha1.GenericUserConnection{ + Host: "localhost", + Port: 5433, + URIArgs: "sslmode=disable", + }) +} + +func setupPGECInternal( + checkInterval string, + waitLinkedResourcesDeletion bool, + primaryUserConnection, bouncerUserConnection *postgresqlv1alpha1.GenericUserConnection, ) (*postgresqlv1alpha1.PostgresqlEngineConfiguration, *corev1.Secret) { // Create secret sec := setupPGECSecret() @@ -514,6 +650,10 @@ func setupPGEC( CheckInterval: checkInterval, WaitLinkedResourcesDeletion: waitLinkedResourcesDeletion, SecretName: pgecSecretName, + UserConnections: &postgresqlv1alpha1.UserConnections{ + PrimaryConnection: primaryUserConnection, + BouncerConnection: bouncerUserConnection, + }, }, } @@ -1106,7 +1246,7 @@ func checkPGUSecretValues(name, namespace, rolePrefix string, pgec *postgresqlv1 }, secret) Expect(err).ToNot(HaveOccurred()) - Expect(string(secret.Data["POSTGRES_URL"])).To(Equal(fmt.Sprintf("postgresql://%s:%s@localhost:5432/%s", secret.Data["LOGIN"], secret.Data["PASSWORD"], pgdbDBName))) + Expect(string(secret.Data["POSTGRES_URL"])).To(Equal(fmt.Sprintf("postgresql://%s:%s@localhost:%d/%s", secret.Data["LOGIN"], secret.Data["PASSWORD"], pgec.Spec.Port, pgdbDBName))) Expect(string(secret.Data["POSTGRES_URL_ARGS"])).To(Equal(fmt.Sprintf("%s?%s", secret.Data["POSTGRES_URL"], secret.Data["ARGS"]))) Expect(string(secret.Data["ROLE"])).To(MatchRegexp(fmt.Sprintf("%s-.+", rolePrefix))) Expect(secret.Data["PASSWORD"]).ToNot(BeEmpty()) @@ -1117,7 +1257,11 @@ func checkPGUSecretValues(name, namespace, rolePrefix string, pgec *postgresqlv1 Expect(string(secret.Data["ARGS"])).To(Equal(pgec.Spec.URIArgs)) } -func checkPGURSecretValues(name, namespace, dbName, username, password string, pgec *postgresqlv1alpha1.PostgresqlEngineConfiguration) { +func checkPGURSecretValues( + name, namespace, dbName, username, password string, + pgec *postgresqlv1alpha1.PostgresqlEngineConfiguration, + userConnectionType postgresqlv1alpha1.ConnectionTypesSpecEnum, +) { secret := &corev1.Secret{} err := k8sClient.Get(ctx, types.NamespacedName{ Name: name, @@ -1125,15 +1269,22 @@ func checkPGURSecretValues(name, namespace, dbName, username, password string, p }, secret) Expect(err).ToNot(HaveOccurred()) + // Init user connection with PRIMARY choice + userCon := pgec.Spec.UserConnections.PrimaryConnection + // Check if bouncer is selected + if userConnectionType == postgresqlv1alpha1.BouncerConnectionType { + userCon = pgec.Spec.UserConnections.BouncerConnection + } + Expect(string(secret.Data["POSTGRES_URL"])).To(Equal( - fmt.Sprintf("postgresql://%s:%s@localhost:5432/%s", secret.Data["LOGIN"], secret.Data["PASSWORD"], dbName), + fmt.Sprintf("postgresql://%s:%s@%s:%d/%s", secret.Data["LOGIN"], secret.Data["PASSWORD"], userCon.Host, userCon.Port, dbName), )) Expect(string(secret.Data["POSTGRES_URL_ARGS"])).To(Equal(fmt.Sprintf("%s?%s", secret.Data["POSTGRES_URL"], secret.Data["ARGS"]))) Expect(secret.Data["PASSWORD"]).ToNot(BeEmpty()) Expect(string(secret.Data["PASSWORD"])).To(Equal(password)) Expect(string(secret.Data["LOGIN"])).To(Equal(username)) Expect(string(secret.Data["DATABASE"])).To(Equal(dbName)) - Expect(string(secret.Data["HOST"])).To(Equal(pgec.Spec.Host)) - Expect(string(secret.Data["PORT"])).To(Equal(fmt.Sprint(pgec.Spec.Port))) - Expect(string(secret.Data["ARGS"])).To(Equal(pgec.Spec.URIArgs)) + Expect(string(secret.Data["HOST"])).To(Equal(userCon.Host)) + Expect(string(secret.Data["PORT"])).To(Equal(fmt.Sprint(userCon.Port))) + Expect(string(secret.Data["ARGS"])).To(Equal(userCon.URIArgs)) }