Skip to content

Commit

Permalink
feat: Add support for bouncer url in engine configuration and selecti…
Browse files Browse the repository at this point in the history
…on in user role
  • Loading branch information
oxyno-zeta committed Feb 28, 2024
1 parent 0ed9996 commit 0085ee2
Show file tree
Hide file tree
Showing 11 changed files with 1,026 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions api/postgresql/v1alpha1/postgresqlengineconfiguration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions api/postgresql/v1alpha1/postgresqluserrole_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 46 additions & 1 deletion api/postgresql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (

const (
PGECRequeueDelayErrorNumberSeconds = 5
DefaultPGPort = 5432
)

// PostgresqlEngineConfigurationReconciler reconciles a PostgresqlEngineConfiguration object.
Expand Down Expand Up @@ -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 == "" {
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit 0085ee2

Please sign in to comment.