From d9fc2bbfdb3ec4c1b27d04c75ec66aaef360de2b Mon Sep 17 00:00:00 2001 From: TK Date: Sat, 30 Aug 2025 01:11:29 +0300 Subject: [PATCH 01/37] feat: Helm-Chart Support External Secret Operator / feat: support for reload when secret changed --- charts/ext-postgres-operator/Chart.yaml | 2 +- .../templates/external-secret.yaml | 35 +++++++++++++++++++ .../templates/operator.yaml | 2 ++ .../templates/secret.yaml | 4 +-- .../templates/serviceaccount.yaml | 2 +- charts/ext-postgres-operator/values.yaml | 33 +++++++++++------ 6 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 charts/ext-postgres-operator/templates/external-secret.yaml diff --git a/charts/ext-postgres-operator/Chart.yaml b/charts/ext-postgres-operator/Chart.yaml index c3105b798..2666768be 100644 --- a/charts/ext-postgres-operator/Chart.yaml +++ b/charts/ext-postgres-operator/Chart.yaml @@ -8,5 +8,5 @@ description: | type: application -version: 2.1.0 +version: 2.1.1 appVersion: "2.0.0" diff --git a/charts/ext-postgres-operator/templates/external-secret.yaml b/charts/ext-postgres-operator/templates/external-secret.yaml new file mode 100644 index 000000000..5ecaf1cf3 --- /dev/null +++ b/charts/ext-postgres-operator/templates/external-secret.yaml @@ -0,0 +1,35 @@ +{{- if and (.Capabilities.APIVersions.Has "external-secrets.io/v1beta1") (.Values.ExternalSecret) }} +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: {{ include "chart.fullname" . }}-external-secret + namespace: {{ if .Values.ExternalSecret.namespace }}{{ .Values.ExternalSecret.namespace }}{{ else }}{{ .Release.Namespace }}{{ end }} + labels: + {{- include "chart.labels" . | nindent 4 }} +spec: + refreshInterval: {{ .Values.ExternalSecret.refreshInterval | default "2s"}} + secretStoreRef: + kind: {{ .Values.ExternalSecret.secretStoreKind | default "SecretStore" }} + name: {{ .Values.ExternalSecret.secretStore | quote }} + target: + creationPolicy: Owner + deletionPolicy: Retain + name: {{ include "chart.fullname" . }} + template: + data: + POSTGRES_HOST: {{ .Values.postgres.host | quote }} + POSTGRES_USER: "{{ `{{ .username }}` }}" + POSTGRES_PASS: "{{ `{{ .password }}` }}" + POSTGRES_URI_ARGS: {{ .Values.postgres.uri_args | quote }} + POSTGRES_CLOUD_PROVIDER: {{ .Values.postgres.cloud_provider | quote }} + POSTGRES_DEFAULT_DATABASE: {{ .Values.postgres.default_database | quote }} + data: + - secretKey: username + remoteRef: + key: {{ .Values.ExternalSecret.remoteKey | quote }} + property: username + - secretKey: password + remoteRef: + key: {{ .Values.ExternalSecret.remoteKey | quote }} + property: password +{{- end }} diff --git a/charts/ext-postgres-operator/templates/operator.yaml b/charts/ext-postgres-operator/templates/operator.yaml index 62d2f7b59..f8c98a899 100644 --- a/charts/ext-postgres-operator/templates/operator.yaml +++ b/charts/ext-postgres-operator/templates/operator.yaml @@ -7,6 +7,8 @@ metadata: namespace: {{ .Release.Namespace }} {{- with .Values.deploymentAnnotations }} annotations: + checksum/env_config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + checksum/env_config: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} {{- toYaml . | nindent 4 }} {{- end }} spec: diff --git a/charts/ext-postgres-operator/templates/secret.yaml b/charts/ext-postgres-operator/templates/secret.yaml index a245cbe4e..f285f6d0e 100644 --- a/charts/ext-postgres-operator/templates/secret.yaml +++ b/charts/ext-postgres-operator/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if (not .Values.existingSecret) }} +{{- if and (not .Values.existingSecret) (not .Values.ExternalSecret) }} --- apiVersion: v1 kind: Secret @@ -6,7 +6,7 @@ metadata: annotations: "helm.sh/resource-policy": keep name: {{ include "chart.fullname" . }} - namespace: {{ .Release.namespace }} + namespace: {{ .Release.Namespace }} labels: {{- include "chart.labels" . | nindent 4 }} type: Opaque diff --git a/charts/ext-postgres-operator/templates/serviceaccount.yaml b/charts/ext-postgres-operator/templates/serviceaccount.yaml index 15642ad0c..79e193c15 100644 --- a/charts/ext-postgres-operator/templates/serviceaccount.yaml +++ b/charts/ext-postgres-operator/templates/serviceaccount.yaml @@ -9,4 +9,4 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} namespace: {{ .Release.Namespace }} - +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} \ No newline at end of file diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index a0add7416..c61ff4f43 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -4,7 +4,7 @@ replicaCount: 1 -revisionHistoryLimit: 10 +revisionHistoryLimit: 3 image: repository: ghcr.io/movetokube/postgres-operator @@ -27,12 +27,13 @@ serviceAccount: # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template name: "" + automount: true deploymentAnnotations: {} podAnnotations: {} -# Additionnal labels to add to the pod. +# Additional labels to add to the pod. podLabels: {} podSecurityContext: @@ -45,8 +46,7 @@ securityContext: drop: - "ALL" -resources: - {} +resources: {} # We usually recommend not to specify default resources and to leave this as a conscious # choice for the user. This also increases chances charts run on environments with little # resources, such as Minikube. If you do want to specify resources, uncomment the following @@ -79,11 +79,11 @@ watchNamespace: "" # Define connection to postgres database server postgres: # postgres hostname - host: "localhost" - # postgres admin user and password - user: "admin" - password: "password" - # additional connection args to pg driver + host: "xxxxxxxxxx" + # postgres admin user and password ( ignored if existingSecret or ExternalSecret is set ) + user: "XXXXXXXXXX" + password: "XXXXXXXXXX" + # additional connection args to pg driver (Example "sslmode=disable") uri_args: "" # postgres cloud provider, could be AWS, Azure, GCP or empty (default) cloud_provider: "" @@ -98,10 +98,21 @@ volumeMounts: [] # Existing secret where values to connect to Postgres are defined. # If not set a new secret will be created, filled with information under the postgres key above. +# If ExternalSecret is set, existingSecret is ignored. existingSecret: "" -# Additionnal environment variables to add to the pod (map of key / value) -env: {} +# Support for ExternalSecret Operator to fetch Postgres credentials from an external secret store. +ExternalSecret: {} + # secretStore: "aws-secretsmanager-euc1" # (Mandatory) Name of the SecretStore or ClusterSecretStore to reference in the ExternalSecret + # remoteKey: "rds!db-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # (Mandatory) Remote key in the external secret store where Postgres credentials are stored + # namespace: "" # (Optional), defaults to release namespace + # secretStoreKind: "" # (Optional), defaults to SecretStore / SecretStore or ClusterSecretStore + # refreshInterval: "2s" # (Optional), defaults to SecretStore / SecretStore or ClusterSecretStore + +# Additional environment variables to add to the pod (map of key / value) +env: + POSTGRES_INSTANCE: "XXXXXXXXXX" + # POSTGRES_CLOUD_PROVIDER: "AWS" nodeSelector: {} From 1fa5e1346ac6cf8e39cf8dd49acb1c0208245128 Mon Sep 17 00:00:00 2001 From: tkcontiant <157844717+tkcontiant@users.noreply.github.com> Date: Wed, 10 Sep 2025 12:53:27 +0300 Subject: [PATCH 02/37] Update charts/ext-postgres-operator/values.yaml Co-authored-by: Pieter C --- charts/ext-postgres-operator/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index c61ff4f43..b75d7211f 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -102,7 +102,7 @@ volumeMounts: [] existingSecret: "" # Support for ExternalSecret Operator to fetch Postgres credentials from an external secret store. -ExternalSecret: {} +externalSecret: {} # secretStore: "aws-secretsmanager-euc1" # (Mandatory) Name of the SecretStore or ClusterSecretStore to reference in the ExternalSecret # remoteKey: "rds!db-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # (Mandatory) Remote key in the external secret store where Postgres credentials are stored # namespace: "" # (Optional), defaults to release namespace From 263b869eea88528196da830b945bf3892480a2c3 Mon Sep 17 00:00:00 2001 From: TK Date: Wed, 10 Sep 2025 12:55:39 +0300 Subject: [PATCH 03/37] fix: values.yaml after code-review --- charts/ext-postgres-operator/Chart.yaml | 2 +- charts/ext-postgres-operator/values.yaml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/charts/ext-postgres-operator/Chart.yaml b/charts/ext-postgres-operator/Chart.yaml index 2666768be..ce2d480ff 100644 --- a/charts/ext-postgres-operator/Chart.yaml +++ b/charts/ext-postgres-operator/Chart.yaml @@ -8,5 +8,5 @@ description: | type: application -version: 2.1.1 +version: 2.2.0 appVersion: "2.0.0" diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index b75d7211f..ba6dc18ac 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -4,7 +4,7 @@ replicaCount: 1 -revisionHistoryLimit: 3 +revisionHistoryLimit: 10 image: repository: ghcr.io/movetokube/postgres-operator @@ -79,10 +79,10 @@ watchNamespace: "" # Define connection to postgres database server postgres: # postgres hostname - host: "xxxxxxxxxx" + host: "localhost" # postgres admin user and password ( ignored if existingSecret or ExternalSecret is set ) - user: "XXXXXXXXXX" - password: "XXXXXXXXXX" + user: "admin" + password: "password" # additional connection args to pg driver (Example "sslmode=disable") uri_args: "" # postgres cloud provider, could be AWS, Azure, GCP or empty (default) @@ -110,8 +110,8 @@ externalSecret: {} # refreshInterval: "2s" # (Optional), defaults to SecretStore / SecretStore or ClusterSecretStore # Additional environment variables to add to the pod (map of key / value) -env: - POSTGRES_INSTANCE: "XXXXXXXXXX" +env: {} + # POSTGRES_INSTANCE: "XXXXXXXXXX" # POSTGRES_CLOUD_PROVIDER: "AWS" nodeSelector: {} From 2c67d4206b1206676803d2bae7058a979e3228d8 Mon Sep 17 00:00:00 2001 From: TK Date: Wed, 10 Sep 2025 13:02:49 +0300 Subject: [PATCH 04/37] fix: values.yaml after code-review :02 --- charts/ext-postgres-operator/templates/operator.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/charts/ext-postgres-operator/templates/operator.yaml b/charts/ext-postgres-operator/templates/operator.yaml index f8c98a899..b83b58f3b 100644 --- a/charts/ext-postgres-operator/templates/operator.yaml +++ b/charts/ext-postgres-operator/templates/operator.yaml @@ -7,8 +7,12 @@ metadata: namespace: {{ .Release.Namespace }} {{- with .Values.deploymentAnnotations }} annotations: - checksum/env_config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} - checksum/env_config: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} + {{ if not .Values.externalSecret }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + {{ if .Values.externalSecret }} + checksum/external-secret: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} + {{- end }} {{- toYaml . | nindent 4 }} {{- end }} spec: From f281eb277886ca325e2ecf36ca3a841de95129fd Mon Sep 17 00:00:00 2001 From: TK Date: Wed, 10 Sep 2025 13:10:18 +0300 Subject: [PATCH 05/37] fix: values.yaml after code-review :03 --- charts/ext-postgres-operator/templates/operator.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charts/ext-postgres-operator/templates/operator.yaml b/charts/ext-postgres-operator/templates/operator.yaml index b83b58f3b..3ef609084 100644 --- a/charts/ext-postgres-operator/templates/operator.yaml +++ b/charts/ext-postgres-operator/templates/operator.yaml @@ -5,14 +5,14 @@ metadata: labels: {{- include "chart.labels" . | nindent 4 }} namespace: {{ .Release.Namespace }} - {{- with .Values.deploymentAnnotations }} annotations: - {{ if not .Values.externalSecret }} + {{- if not .Values.externalSecret }} checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} {{- end }} - {{ if .Values.externalSecret }} + {{- if .Values.externalSecret }} checksum/external-secret: {{ include (print $.Template.BasePath "/external-secret.yaml") . | sha256sum }} {{- end }} + {{- with .Values.deploymentAnnotations }} {{- toYaml . | nindent 4 }} {{- end }} spec: From 2b0edd4411a81203edb2ba4a0b739076e9a78eca Mon Sep 17 00:00:00 2001 From: TK Date: Tue, 16 Sep 2025 13:39:39 +0300 Subject: [PATCH 06/37] fix: .Values.externalSecret --- .../templates/external-secret.yaml | 14 +++++++------- charts/ext-postgres-operator/templates/secret.yaml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/charts/ext-postgres-operator/templates/external-secret.yaml b/charts/ext-postgres-operator/templates/external-secret.yaml index 5ecaf1cf3..5e84b0fa1 100644 --- a/charts/ext-postgres-operator/templates/external-secret.yaml +++ b/charts/ext-postgres-operator/templates/external-secret.yaml @@ -1,16 +1,16 @@ -{{- if and (.Capabilities.APIVersions.Has "external-secrets.io/v1beta1") (.Values.ExternalSecret) }} +{{- if and (.Capabilities.APIVersions.Has "external-secrets.io/v1beta1") (.Values.externalSecret) }} apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: {{ include "chart.fullname" . }}-external-secret - namespace: {{ if .Values.ExternalSecret.namespace }}{{ .Values.ExternalSecret.namespace }}{{ else }}{{ .Release.Namespace }}{{ end }} + namespace: {{ if .Values.externalSecret.namespace }}{{ .Values.externalSecret.namespace }}{{ else }}{{ .Release.Namespace }}{{ end }} labels: {{- include "chart.labels" . | nindent 4 }} spec: - refreshInterval: {{ .Values.ExternalSecret.refreshInterval | default "2s"}} + refreshInterval: {{ .Values.externalSecret.refreshInterval | default "2s"}} secretStoreRef: - kind: {{ .Values.ExternalSecret.secretStoreKind | default "SecretStore" }} - name: {{ .Values.ExternalSecret.secretStore | quote }} + kind: {{ .Values.externalSecret.secretStoreKind | default "SecretStore" }} + name: {{ .Values.externalSecret.secretStore | quote }} target: creationPolicy: Owner deletionPolicy: Retain @@ -26,10 +26,10 @@ spec: data: - secretKey: username remoteRef: - key: {{ .Values.ExternalSecret.remoteKey | quote }} + key: {{ .Values.externalSecret.remoteKey | quote }} property: username - secretKey: password remoteRef: - key: {{ .Values.ExternalSecret.remoteKey | quote }} + key: {{ .Values.externalSecret.remoteKey | quote }} property: password {{- end }} diff --git a/charts/ext-postgres-operator/templates/secret.yaml b/charts/ext-postgres-operator/templates/secret.yaml index f285f6d0e..088afa838 100644 --- a/charts/ext-postgres-operator/templates/secret.yaml +++ b/charts/ext-postgres-operator/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if and (not .Values.existingSecret) (not .Values.ExternalSecret) }} +{{- if and (not .Values.existingSecret) (not .Values.externalSecret) }} --- apiVersion: v1 kind: Secret From 09d4aa23093361b850766b6df8cefe2e7c623878 Mon Sep 17 00:00:00 2001 From: TK Date: Wed, 17 Sep 2025 19:22:15 +0300 Subject: [PATCH 07/37] feat: Gratn Additional Access for Sequences and Functions --- cmd/main.go | 8 +-- internal/controller/postgres_controller.go | 45 ++++++++++------- pkg/postgres/database.go | 58 +++++++++++++++++----- pkg/postgres/postgres.go | 12 +++-- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c445a64a2..14fbecc64 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -47,15 +47,15 @@ func main() { var secureMetrics bool var enableHTTP2 bool var tlsOpts []func(*tls.Config) - flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metrics endpoint binds to. "+ "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, + flag.BoolVar(&enableLeaderElection, "leader-elect", true, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&secureMetrics, "metrics-secure", true, + flag.BoolVar(&secureMetrics, "metrics-secure", false, "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") - flag.BoolVar(&enableHTTP2, "enable-http2", false, + flag.BoolVar(&enableHTTP2, "enable-http2", true, "If set, HTTP/2 will be enabled for the metrics and webhook servers") opts := zap.Options{ Development: true, diff --git a/internal/controller/postgres_controller.go b/internal/controller/postgres_controller.go index 151aaea15..ee9670664 100644 --- a/internal/controller/postgres_controller.go +++ b/internal/controller/postgres_controller.go @@ -184,12 +184,17 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // create schemas var ( - database = instance.Spec.Database - owner = instance.Status.Roles.Owner - reader = instance.Status.Roles.Reader - writer = instance.Status.Roles.Writer - readerPrivs = "SELECT" - writerPrivs = "SELECT,INSERT,DELETE,UPDATE" + database = instance.Spec.Database + owner = instance.Status.Roles.Owner + reader = instance.Status.Roles.Reader + writer = instance.Status.Roles.Writer + readerPrivs = "SELECT" + writerPrivs = "SELECT,INSERT,DELETE,UPDATE" + writerSequencePrivs = "USAGE,SELECT" + writerFunctionPrivs = "EXECUTE" + ownerPrivs = "ALL" + ownerFunctionPrivs = "ALL" + ownerSequencePrivs = "ALL" ) for _, schema := range instance.Spec.Schemas { // Schema was previously created @@ -218,27 +223,31 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c continue } schemaPrivilegesWriter := postgres.PostgresSchemaPrivileges{ - DB: database, - Role: writer, - Schema: schema, - Privs: writerPrivs, - CreateSchema: true, + DB: database, + Role: writer, + Schema: schema, + Privs: writerPrivs, + SequencePrivs: writerSequencePrivs, + FunctionPrivs: writerFunctionPrivs, + CreateSchema: true, } err = r.pg.SetSchemaPrivileges(schemaPrivilegesWriter, reqLogger) if err != nil { - reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs)) + reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\", sequence privileges \"%s\", and function privileges \"%s\"", writer, writerPrivs, writerSequencePrivs, writerFunctionPrivs)) continue } schemaPrivilegesOwner := postgres.PostgresSchemaPrivileges{ - DB: database, - Role: owner, - Schema: schema, - Privs: writerPrivs, - CreateSchema: true, + DB: database, + Role: owner, + Schema: schema, + Privs: ownerPrivs, + SequencePrivs: ownerSequencePrivs, + FunctionPrivs: ownerFunctionPrivs, + CreateSchema: true, } err = r.pg.SetSchemaPrivileges(schemaPrivilegesOwner, reqLogger) if err != nil { - reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\"", writer, writerPrivs)) + reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\", sequence privileges \"%s\", and function privileges \"%s\"", owner, ownerPrivs, ownerSequencePrivs, ownerFunctionPrivs)) continue } diff --git a/pkg/postgres/database.go b/pkg/postgres/database.go index a6b260f57..087988370 100644 --- a/pkg/postgres/database.go +++ b/pkg/postgres/database.go @@ -8,19 +8,23 @@ import ( ) const ( - CREATE_DB = `CREATE DATABASE "%s"` - CREATE_SCHEMA = `CREATE SCHEMA IF NOT EXISTS "%s" AUTHORIZATION "%s"` - CREATE_EXTENSION = `CREATE EXTENSION IF NOT EXISTS "%s"` - ALTER_DB_OWNER = `ALTER DATABASE "%s" OWNER TO "%s"` - DROP_DATABASE = `DROP DATABASE "%s"` - GRANT_USAGE_SCHEMA = `GRANT USAGE ON SCHEMA "%s" TO "%s"` - GRANT_CREATE_TABLE = `GRANT CREATE ON SCHEMA "%s" TO "%s"` - GRANT_ALL_TABLES = `GRANT %s ON ALL TABLES IN SCHEMA "%s" TO "%s"` - DEFAULT_PRIVS_SCHEMA = `ALTER DEFAULT PRIVILEGES IN SCHEMA "%s" GRANT %s ON TABLES TO "%s"` - REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public` - TERMINATE_BACKEND = `SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid()` - GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'` - GRANT_CREATE_SCHEMA = `GRANT CREATE ON DATABASE "%s" TO "%s"` + CREATE_DB = `CREATE DATABASE "%s"` + CREATE_SCHEMA = `CREATE SCHEMA IF NOT EXISTS "%s" AUTHORIZATION "%s"` + CREATE_EXTENSION = `CREATE EXTENSION IF NOT EXISTS "%s"` + ALTER_DB_OWNER = `ALTER DATABASE "%s" OWNER TO "%s"` + DROP_DATABASE = `DROP DATABASE "%s"` + GRANT_USAGE_SCHEMA = `GRANT USAGE ON SCHEMA "%s" TO "%s"` + GRANT_CREATE_TABLE = `GRANT CREATE ON SCHEMA "%s" TO "%s"` + GRANT_ALL_TABLES = `GRANT %s ON ALL TABLES IN SCHEMA "%s" TO "%s"` + DEFAULT_PRIVS_SCHEMA = `ALTER DEFAULT PRIVILEGES IN SCHEMA "%s" GRANT %s ON TABLES TO "%s"` + GRANT_ALL_FUNCTIONS = `GRANT %s ON ALL FUNCTIONS IN SCHEMA "%s" TO "%s"` + DEFAULT_PRIVS_FUNCTIONS = `ALTER DEFAULT PRIVILEGES IN SCHEMA "%s" GRANT %s ON FUNCTIONS TO "%s"` + GRANT_ALL_SEQUENCES = `GRANT %s ON ALL SEQUENCES IN SCHEMA "%s" TO "%s"` + DEFAULT_PRIVS_SEQUENCES = `ALTER DEFAULT PRIVILEGES IN SCHEMA "%s" GRANT %s ON SEQUENCES TO "%s"` + REVOKE_CONNECT = `REVOKE CONNECT ON DATABASE "%s" FROM public` + TERMINATE_BACKEND = `SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '%s' AND pid <> pg_backend_pid()` + GET_DB_OWNER = `SELECT pg_catalog.pg_get_userbyid(d.datdba) FROM pg_catalog.pg_database d WHERE d.datname = '%s'` + GRANT_CREATE_SCHEMA = `GRANT CREATE ON DATABASE "%s" TO "%s"` ) func (c *pg) CreateDB(dbname, role string) error { @@ -120,6 +124,34 @@ func (c *pg) SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logg return err } + if schemaPrivileges.SequencePrivs != "" { + // Grant role privs on existing sequences in schema + _, err = tmpDb.Exec(fmt.Sprintf(GRANT_ALL_SEQUENCES, schemaPrivileges.SequencePrivs, schemaPrivileges.Schema, schemaPrivileges.Role)) + if err != nil { + return err + } + + // Grant role privs on future sequences in schema + _, err = tmpDb.Exec(fmt.Sprintf(DEFAULT_PRIVS_SEQUENCES, schemaPrivileges.Schema, schemaPrivileges.SequencePrivs, schemaPrivileges.Role)) + if err != nil { + return err + } + } + + if schemaPrivileges.FunctionPrivs != "" { + // Grant role privs on existing functions in schema + _, err = tmpDb.Exec(fmt.Sprintf(GRANT_ALL_FUNCTIONS, schemaPrivileges.FunctionPrivs, schemaPrivileges.Schema, schemaPrivileges.Role)) + if err != nil { + return err + } + + // Grant role privs on future functions in schema + _, err = tmpDb.Exec(fmt.Sprintf(DEFAULT_PRIVS_FUNCTIONS, schemaPrivileges.Schema, schemaPrivileges.FunctionPrivs, schemaPrivileges.Role)) + if err != nil { + return err + } + } + // Grant role usage on schema if createSchema if schemaPrivileges.CreateSchema { _, err = tmpDb.Exec(fmt.Sprintf(GRANT_CREATE_TABLE, schemaPrivileges.Schema, schemaPrivileges.Role)) diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 033640abe..0abea7da7 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -37,11 +37,13 @@ type pg struct { } type PostgresSchemaPrivileges struct { - DB string - Role string - Schema string - Privs string - CreateSchema bool + DB string + Role string + Schema string + Privs string + SequencePrivs string + FunctionPrivs string + CreateSchema bool } func NewPG(cfg *config.Cfg, logger logr.Logger) (PG, error) { From 892c3b5511b715d3134e00e7e9a1bd104c8682fc Mon Sep 17 00:00:00 2001 From: TK Date: Wed, 17 Sep 2025 19:50:20 +0300 Subject: [PATCH 08/37] fix: increase helm install timeout --- tests/kuttl-test-self-hosted-postgres.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kuttl-test-self-hosted-postgres.yaml b/tests/kuttl-test-self-hosted-postgres.yaml index 1f9769f50..753bd3606 100644 --- a/tests/kuttl-test-self-hosted-postgres.yaml +++ b/tests/kuttl-test-self-hosted-postgres.yaml @@ -14,7 +14,7 @@ commands: --set global.postgresql.auth.password=postgres --set global.postgresql.auth.username=postgres --wait - timeout: 120 + timeout: 240 - command: >- helm install -n $NAMESPACE ext-postgres-operator ./charts/ext-postgres-operator --set image.repository=postgres-operator From a329ee7d38440ec8f93b9159007cb8bed9bc0a2e Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 09:47:22 +0300 Subject: [PATCH 09/37] feat: Support for Leader election --- .../ext-postgres-operator/templates/clusterrole.yaml | 12 ++++++++++++ charts/ext-postgres-operator/templates/role.yaml | 12 ++++++++++++ config/rbac/cluster_role.yaml | 12 ++++++++++++ config/rbac/role.yaml | 12 ++++++++++++ 4 files changed, 48 insertions(+) diff --git a/charts/ext-postgres-operator/templates/clusterrole.yaml b/charts/ext-postgres-operator/templates/clusterrole.yaml index a37699feb..990555714 100644 --- a/charts/ext-postgres-operator/templates/clusterrole.yaml +++ b/charts/ext-postgres-operator/templates/clusterrole.yaml @@ -11,6 +11,18 @@ rules: - secrets verbs: - "*" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - apps resourceNames: diff --git a/charts/ext-postgres-operator/templates/role.yaml b/charts/ext-postgres-operator/templates/role.yaml index ad37ae5cf..50c9f8d33 100644 --- a/charts/ext-postgres-operator/templates/role.yaml +++ b/charts/ext-postgres-operator/templates/role.yaml @@ -19,6 +19,18 @@ rules: - pods verbs: - "get" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - "apps" resources: diff --git a/config/rbac/cluster_role.yaml b/config/rbac/cluster_role.yaml index 37104b321..47e51711f 100644 --- a/config/rbac/cluster_role.yaml +++ b/config/rbac/cluster_role.yaml @@ -9,6 +9,18 @@ rules: - secrets verbs: - "*" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - apps resourceNames: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index f803d4b21..90271295e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -17,6 +17,18 @@ rules: - pods verbs: - "get" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete - apiGroups: - "apps" resources: From 249b9eea7d8e08cc4d2ea4f97657228acb4d8546 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 09:55:00 +0300 Subject: [PATCH 10/37] feat: Support for oeprator metrics port --- charts/ext-postgres-operator/templates/operator.yaml | 4 ++++ config/manager/operator.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/charts/ext-postgres-operator/templates/operator.yaml b/charts/ext-postgres-operator/templates/operator.yaml index 3ef609084..38d075622 100644 --- a/charts/ext-postgres-operator/templates/operator.yaml +++ b/charts/ext-postgres-operator/templates/operator.yaml @@ -44,6 +44,10 @@ spec: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: metrics + containerPort: 8080 + protocol: "TCP" envFrom: - secretRef: {{- if .Values.existingSecret }} diff --git a/config/manager/operator.yaml b/config/manager/operator.yaml index 416597594..d5a6092e2 100644 --- a/config/manager/operator.yaml +++ b/config/manager/operator.yaml @@ -19,6 +19,10 @@ spec: - name: ext-postgres-operator image: movetokube/postgres-operator:2.0.0 imagePullPolicy: Always + ports: + - name: metrics + containerPort: 8080 + protocol: "TCP" envFrom: - secretRef: name: ext-postgres-operator From acafa012c0758477b412abeffd02359d1e698045 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 10:32:13 +0300 Subject: [PATCH 11/37] feat: ServiceMonitor Support for Promtheus-Operator --- .../templates/service-monitor.yaml | 30 +++++++++++++++++++ charts/ext-postgres-operator/values.yaml | 8 +++++ 2 files changed, 38 insertions(+) create mode 100644 charts/ext-postgres-operator/templates/service-monitor.yaml diff --git a/charts/ext-postgres-operator/templates/service-monitor.yaml b/charts/ext-postgres-operator/templates/service-monitor.yaml new file mode 100644 index 000000000..48ec3673c --- /dev/null +++ b/charts/ext-postgres-operator/templates/service-monitor.yaml @@ -0,0 +1,30 @@ +{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") ( .Values.serviceMonitor ) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + annotations: + meta.helm.sh/release-name: {{ .Release.Name }} + meta.helm.sh/release-namespace: {{ .Release.Namespace }} + labels: + {{- include "chart.labels" . | nindent 4 }} + name: {{ include "chart.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + endpoints: + - port: "metrics" + path: "/metrics" + interval: {{ .Values.serviceMonitor.interval | default "30s" }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout | default "10s" }} + scheme: http + {{- if .Values.serviceMonitor.relabelings }} + relabelings: + {{- tpl (toYaml .Values.serviceMonitor.relabelings) . | nindent 6 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + selector: + matchLabels: + {{- include "chart.selectorLabels" . | nindent 6 }} + +{{- end -}} diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index ba6dc18ac..4db9a341f 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -114,6 +114,14 @@ env: {} # POSTGRES_INSTANCE: "XXXXXXXXXX" # POSTGRES_CLOUD_PROVIDER: "AWS" +# ServiceMonitor is a custom resource defined by the Prometheus Operator +serviceMonitor: {} + # interval: 30s + # scrapeTimeout: 10s + # relabeling: [] + # # - targetLabel: app + # # replacement: '{{ include "chart.name" . }}' + nodeSelector: {} tolerations: [] From 90787b3aa06abc6574dfbdeef2fcde4c83344cd7 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 10:38:29 +0300 Subject: [PATCH 12/37] feat: ServiceMonitor Support for Promtheus-Operator :02 --- charts/ext-postgres-operator/out | 248 ++++++++++++++++++ .../templates/service-monitor.yaml | 7 +- charts/ext-postgres-operator/values.yaml | 17 +- 3 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 charts/ext-postgres-operator/out diff --git a/charts/ext-postgres-operator/out b/charts/ext-postgres-operator/out new file mode 100644 index 000000000..295a8dc9b --- /dev/null +++ b/charts/ext-postgres-operator/out @@ -0,0 +1,248 @@ +--- +# Source: ext-postgres-operator/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm + namespace: ext-postgres-operator +automountServiceAccountToken: true +--- +# Source: ext-postgres-operator/templates/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + annotations: + "helm.sh/resource-policy": keep + name: ext-postgres-operator + namespace: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm +type: Opaque +data: + POSTGRES_HOST: "cHNvdGdyZXMtcG9zdGdyZXNxbC5kZXYuc3Zj" + POSTGRES_USER: "cG9zdGdyZXM=" + POSTGRES_PASS: "Zm5wMjM5NDhyaDIzWzA4MjFHMTMxQCE=" + POSTGRES_URI_ARGS: "c3NsbW9kZT1kaXNhYmxl" + POSTGRES_CLOUD_PROVIDER: "" + POSTGRES_DEFAULT_DATABASE: "cG9zdGdyZXM=" +--- +# Source: ext-postgres-operator/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - "*" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - apps + resourceNames: + - ext-postgres-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - db.movetokube.com + resources: + - "*" + verbs: + - "*" + - apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - "*" +--- +# Source: ext-postgres-operator/templates/clusterrole_binding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: ext-postgres-operator + namespace: ext-postgres-operator +roleRef: + kind: ClusterRole + name: ext-postgres-operator + apiGroup: rbac.authorization.k8s.io +--- +# Source: ext-postgres-operator/templates/role.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm +rules: + - apiGroups: + - "" + resources: + - configmaps + - secrets + - services + verbs: + - "*" + - apiGroups: + - "" + resources: + - pods + verbs: + - "get" + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "apps" + resources: + - replicasets + - deployments + verbs: + - "get" +--- +# Source: ext-postgres-operator/templates/role_binding.yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm +subjects: +- kind: ServiceAccount + name: ext-postgres-operator + namespace: ext-postgres-operator +roleRef: + kind: Role + name: ext-postgres-operator + apiGroup: rbac.authorization.k8s.io +--- +# Source: ext-postgres-operator/templates/operator.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ext-postgres-operator + labels: + helm.sh/chart: ext-postgres-operator-2.2.0 + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + app.kubernetes.io/version: "2.0.0" + app.kubernetes.io/managed-by: Helm + namespace: ext-postgres-operator + annotations: + checksum/secret: 33e44038e70343372f114503490b61378eba5a59ed22f93f508269be4f71e770 +spec: + replicas: 2 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + template: + metadata: + labels: + app.kubernetes.io/name: ext-postgres-operator + app.kubernetes.io/instance: ext-postgres-operator + spec: + serviceAccountName: ext-postgres-operator + securityContext: + runAsNonRoot: true + imagePullSecrets: + [] + containers: + - name: ext-postgres-operator + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + image: "postgres-operator:3" + imagePullPolicy: IfNotPresent + ports: + - name: metrics + containerPort: 8080 + protocol: "TCP" + envFrom: + - secretRef: + name: ext-postgres-operator + env: + - name: WATCH_NAMESPACE + value: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POSTGRES_INSTANCE + value: "postgres-kind" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + nodeSelector: + {} + tolerations: + [] diff --git a/charts/ext-postgres-operator/templates/service-monitor.yaml b/charts/ext-postgres-operator/templates/service-monitor.yaml index 48ec3673c..8905d9473 100644 --- a/charts/ext-postgres-operator/templates/service-monitor.yaml +++ b/charts/ext-postgres-operator/templates/service-monitor.yaml @@ -5,8 +5,14 @@ metadata: annotations: meta.helm.sh/release-name: {{ .Release.Name }} meta.helm.sh/release-namespace: {{ .Release.Namespace }} + {{- with .Values.serviceMonitor.additionalAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} labels: {{- include "chart.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.additonalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} name: {{ include "chart.fullname" . }} namespace: {{ .Release.Namespace }} spec: @@ -26,5 +32,4 @@ spec: selector: matchLabels: {{- include "chart.selectorLabels" . | nindent 6 }} - {{- end -}} diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index 4db9a341f..f5400d47e 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -115,12 +115,17 @@ env: {} # POSTGRES_CLOUD_PROVIDER: "AWS" # ServiceMonitor is a custom resource defined by the Prometheus Operator -serviceMonitor: {} - # interval: 30s - # scrapeTimeout: 10s - # relabeling: [] - # # - targetLabel: app - # # replacement: '{{ include "chart.name" . }}' +serviceMonitor: + interval: 30s + scrapeTimeout: 10s + relabeling: [] + # - targetLabel: app + # replacement: '{{ include "chart.name" . }}' + additonalLabels: {} + # e.g. release label of the prometheus operator + # release: prometheus-operator + additionalAnnotations: {} + # e.g. {} nodeSelector: {} From 2b1b6f4009d6c87711e987356b35f343f065319a Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 10:38:55 +0300 Subject: [PATCH 13/37] feat: ServiceMonitor Support for Promtheus-Operator :03 --- charts/ext-postgres-operator/values.yaml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index f5400d47e..1fe8e4ad6 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -115,17 +115,17 @@ env: {} # POSTGRES_CLOUD_PROVIDER: "AWS" # ServiceMonitor is a custom resource defined by the Prometheus Operator -serviceMonitor: - interval: 30s - scrapeTimeout: 10s - relabeling: [] - # - targetLabel: app - # replacement: '{{ include "chart.name" . }}' - additonalLabels: {} - # e.g. release label of the prometheus operator - # release: prometheus-operator - additionalAnnotations: {} - # e.g. {} +serviceMonitor: {} + # interval: 30s + # scrapeTimeout: 10s + # relabeling: [] + # # - targetLabel: app + # # replacement: '{{ include "chart.name" . }}' + # additonalLabels: {} + # # e.g. release label of the prometheus operator + # # release: prometheus-operator + # additionalAnnotations: {} + # # e.g. {} nodeSelector: {} From ad0b82d5a7fbf1ed5b7e6417710f02b3cba97bd3 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 10:40:35 +0300 Subject: [PATCH 14/37] chore: revert test timout value --- tests/kuttl-test-self-hosted-postgres.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kuttl-test-self-hosted-postgres.yaml b/tests/kuttl-test-self-hosted-postgres.yaml index 753bd3606..1f9769f50 100644 --- a/tests/kuttl-test-self-hosted-postgres.yaml +++ b/tests/kuttl-test-self-hosted-postgres.yaml @@ -14,7 +14,7 @@ commands: --set global.postgresql.auth.password=postgres --set global.postgresql.auth.username=postgres --wait - timeout: 240 + timeout: 120 - command: >- helm install -n $NAMESPACE ext-postgres-operator ./charts/ext-postgres-operator --set image.repository=postgres-operator From fae7f4a37bc57198c2eff38579c39939bbdccbeb Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 10:48:45 +0300 Subject: [PATCH 15/37] feat: ingore test helm template output files --- charts/ext-postgres-operator/out | 248 ------------------------------- 1 file changed, 248 deletions(-) delete mode 100644 charts/ext-postgres-operator/out diff --git a/charts/ext-postgres-operator/out b/charts/ext-postgres-operator/out deleted file mode 100644 index 295a8dc9b..000000000 --- a/charts/ext-postgres-operator/out +++ /dev/null @@ -1,248 +0,0 @@ ---- -# Source: ext-postgres-operator/templates/serviceaccount.yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm - namespace: ext-postgres-operator -automountServiceAccountToken: true ---- -# Source: ext-postgres-operator/templates/secret.yaml -apiVersion: v1 -kind: Secret -metadata: - annotations: - "helm.sh/resource-policy": keep - name: ext-postgres-operator - namespace: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm -type: Opaque -data: - POSTGRES_HOST: "cHNvdGdyZXMtcG9zdGdyZXNxbC5kZXYuc3Zj" - POSTGRES_USER: "cG9zdGdyZXM=" - POSTGRES_PASS: "Zm5wMjM5NDhyaDIzWzA4MjFHMTMxQCE=" - POSTGRES_URI_ARGS: "c3NsbW9kZT1kaXNhYmxl" - POSTGRES_CLOUD_PROVIDER: "" - POSTGRES_DEFAULT_DATABASE: "cG9zdGdyZXM=" ---- -# Source: ext-postgres-operator/templates/clusterrole.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm -rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - "*" - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - apps - resourceNames: - - ext-postgres-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - db.movetokube.com - resources: - - "*" - verbs: - - "*" - - apiGroups: - - monitoring.coreos.com - resources: - - servicemonitors - verbs: - - "*" ---- -# Source: ext-postgres-operator/templates/clusterrole_binding.yaml -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm -subjects: -- kind: ServiceAccount - name: ext-postgres-operator - namespace: ext-postgres-operator -roleRef: - kind: ClusterRole - name: ext-postgres-operator - apiGroup: rbac.authorization.k8s.io ---- -# Source: ext-postgres-operator/templates/role.yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm -rules: - - apiGroups: - - "" - resources: - - configmaps - - secrets - - services - verbs: - - "*" - - apiGroups: - - "" - resources: - - pods - verbs: - - "get" - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - "apps" - resources: - - replicasets - - deployments - verbs: - - "get" ---- -# Source: ext-postgres-operator/templates/role_binding.yaml -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm -subjects: -- kind: ServiceAccount - name: ext-postgres-operator - namespace: ext-postgres-operator -roleRef: - kind: Role - name: ext-postgres-operator - apiGroup: rbac.authorization.k8s.io ---- -# Source: ext-postgres-operator/templates/operator.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ext-postgres-operator - labels: - helm.sh/chart: ext-postgres-operator-2.2.0 - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - app.kubernetes.io/version: "2.0.0" - app.kubernetes.io/managed-by: Helm - namespace: ext-postgres-operator - annotations: - checksum/secret: 33e44038e70343372f114503490b61378eba5a59ed22f93f508269be4f71e770 -spec: - replicas: 2 - revisionHistoryLimit: 10 - selector: - matchLabels: - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - template: - metadata: - labels: - app.kubernetes.io/name: ext-postgres-operator - app.kubernetes.io/instance: ext-postgres-operator - spec: - serviceAccountName: ext-postgres-operator - securityContext: - runAsNonRoot: true - imagePullSecrets: - [] - containers: - - name: ext-postgres-operator - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - image: "postgres-operator:3" - imagePullPolicy: IfNotPresent - ports: - - name: metrics - containerPort: 8080 - protocol: "TCP" - envFrom: - - secretRef: - name: ext-postgres-operator - env: - - name: WATCH_NAMESPACE - value: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POSTGRES_INSTANCE - value: "postgres-kind" - livenessProbe: - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 10 - nodeSelector: - {} - tolerations: - [] From 67fd3fab5c4cf6ac0cf61e8af8645e1185602163 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 11:39:51 +0300 Subject: [PATCH 16/37] feat: bump helm-chart application version to 2.2.0 --- charts/ext-postgres-operator/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/ext-postgres-operator/Chart.yaml b/charts/ext-postgres-operator/Chart.yaml index ce2d480ff..6bdad7f5c 100644 --- a/charts/ext-postgres-operator/Chart.yaml +++ b/charts/ext-postgres-operator/Chart.yaml @@ -8,5 +8,5 @@ description: | type: application -version: 2.2.0 -appVersion: "2.0.0" +version: 2.3.0 +appVersion: "2.2.0" From 9cdc19a177a99d3aae91cb496d28192c39c69a20 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 15:15:42 +0300 Subject: [PATCH 17/37] feat: External-Postgres-Operator 2.3.3 build --- internal/controller/postgres_controller.go | 26 +++++++++++++++++++--- pkg/postgres/mock/postgres.go | 14 ++++++++++++ pkg/postgres/postgres.go | 1 + pkg/postgres/role.go | 15 +++++++++++++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/internal/controller/postgres_controller.go b/internal/controller/postgres_controller.go index ee9670664..1135e8950 100644 --- a/internal/controller/postgres_controller.go +++ b/internal/controller/postgres_controller.go @@ -168,6 +168,22 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c instance.Status.Roles.Writer = writer instance.Status.Succeeded = true } + + desiredOwner := instance.Spec.MasterRole + // handle owner rename if was previously set by instance.Spec.MasterRole then was removed + if desiredOwner == "" { + desiredOwner = fmt.Sprintf("%s-group", instance.Spec.Database) + } + // rename owner role if instance.Spec.MasterRole was changed + ownerChanged := instance.Status.Roles.Owner != "" && instance.Status.Roles.Owner != desiredOwner + if ownerChanged { + err = r.pg.RenameGroupRole(instance.Status.Roles.Owner, desiredOwner) + if err != nil { + return requeue(errors.NewInternalError(err)) + } + instance.Status.Roles.Owner = desiredOwner + } + // create extensions for _, extension := range instance.Spec.Extensions { // Check if extension is already added. Skip if already is added. @@ -192,7 +208,7 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c writerPrivs = "SELECT,INSERT,DELETE,UPDATE" writerSequencePrivs = "USAGE,SELECT" writerFunctionPrivs = "EXECUTE" - ownerPrivs = "ALL" + ownerPrivs = "ALL,MAINTAIN" ownerFunctionPrivs = "ALL" ownerSequencePrivs = "ALL" ) @@ -208,6 +224,11 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c reqLogger.Error(err, fmt.Sprintf("Could not create schema %s", schema)) continue } + instance.Status.Schemas = append(instance.Status.Schemas, schema) + } + + // Set privileges on schemas during every reconcile to ensure privileges are correct + for _, schema := range instance.Spec.Schemas { // Set privileges on schema schemaPrivilegesReader := postgres.PostgresSchemaPrivileges{ @@ -250,9 +271,8 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c reqLogger.Error(err, fmt.Sprintf("Could not give %s permissions \"%s\", sequence privileges \"%s\", and function privileges \"%s\"", owner, ownerPrivs, ownerSequencePrivs, ownerFunctionPrivs)) continue } - - instance.Status.Schemas = append(instance.Status.Schemas, schema) } + err = r.Status().Patch(ctx, instance, client.MergeFrom(before)) if err != nil { return requeue(err) diff --git a/pkg/postgres/mock/postgres.go b/pkg/postgres/mock/postgres.go index 4f53e59ec..962ebe77d 100644 --- a/pkg/postgres/mock/postgres.go +++ b/pkg/postgres/mock/postgres.go @@ -97,6 +97,20 @@ func (mr *MockPGMockRecorder) CreateGroupRole(role any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroupRole", reflect.TypeOf((*MockPG)(nil).CreateGroupRole), role) } +// RenameGroupRole mocks base method. +func (m *MockPG) RenameGroupRole(currentRole, newRole string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RenameGroupRole", currentRole, newRole) + ret0, _ := ret[0].(error) + return ret0 +} + +// RenameGroupRole indicates an expected call of RenameGroupRole. +func (mr *MockPGMockRecorder) RenameGroupRole(currentRole, newRole any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenameGroupRole", reflect.TypeOf((*MockPG)(nil).RenameGroupRole), currentRole, newRole) +} + // CreateSchema mocks base method. func (m *MockPG) CreateSchema(db, role, schema string, logger logr.Logger) error { m.ctrl.T.Helper() diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 0abea7da7..ec5103049 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -14,6 +14,7 @@ type PG interface { CreateSchema(db, role, schema string, logger logr.Logger) error CreateExtension(db, extension string, logger logr.Logger) error CreateGroupRole(role string) error + RenameGroupRole(currentRole, newRole string) error CreateUserRole(role, password string) (string, error) UpdatePassword(role, password string) error GrantRole(role, grantee string) error diff --git a/pkg/postgres/role.go b/pkg/postgres/role.go index 8bf4f4b71..0b4d4b240 100644 --- a/pkg/postgres/role.go +++ b/pkg/postgres/role.go @@ -9,6 +9,7 @@ import ( const ( CREATE_GROUP_ROLE = `CREATE ROLE "%s"` + RENAME_GROUP_ROLE = `ALTER ROLE "%s" RENAME TO "%s"` CREATE_USER_ROLE = `CREATE ROLE "%s" WITH LOGIN PASSWORD '%s'` GRANT_ROLE = `GRANT "%s" TO "%s"` ALTER_USER_SET_ROLE = `ALTER USER "%s" SET ROLE "%s"` @@ -28,6 +29,20 @@ func (c *pg) CreateGroupRole(role string) error { return nil } +func (c *pg) RenameGroupRole(currentRole, newRole string) error { + _, err := c.db.Exec(fmt.Sprintf(RENAME_GROUP_ROLE, currentRole, newRole)) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + // 42704 => role does not exist; treat as success so caller can recreate + if pqErr.Code == "42704" { + return nil + } + } + return err + } + return nil +} + func (c *pg) CreateUserRole(role, password string) (string, error) { _, err := c.db.Exec(fmt.Sprintf(CREATE_USER_ROLE, role, password)) if err != nil { From 92de57cbd70231ce80196d049a00a659ee564eb7 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:34:49 +0300 Subject: [PATCH 18/37] reoslve conflicts after rebase --- .../controller/postgresuser_controller.go | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index 6870a683d..3ba210ed3 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -116,7 +116,9 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request } // Creation logic - var role, login string + var ( + role, login string + ) password, err := utils.GetSecureRandomString(15) if err != nil { @@ -201,6 +203,57 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request } } else if awsIamRequested { reqLogger.WithValues("role", role).Info("IAM Auth requested while we are not running with AWS cloud provider config") + // Reconcile logic for changes in group membership + // This is only applicable if user role is already created + // and privileges are changed in spec + if instance.Status.PostgresRole != "" { + + // We need to get the Postgres CR to get the group role name + database, err := r.getPostgresCR(ctx, instance) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + // Determine desired group role + var desiredGroup string + switch instance.Spec.Privileges { + case "READ": + desiredGroup = database.Status.Roles.Reader + case "WRITE": + desiredGroup = database.Status.Roles.Writer + default: + desiredGroup = database.Status.Roles.Owner + } + + currentGroup := instance.Status.PostgresGroup + if desiredGroup != "" && currentGroup != desiredGroup { + + // Remove the old group membership if present + if currentGroup != "" { + err = r.pg.RevokeRole(currentGroup, role) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + } + + // Grant the new group role + err = r.pg.GrantRole(desiredGroup, role) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + // Ensure objects created by the user are owned by the new group + err = r.pg.AlterDefaultLoginRole(role, desiredGroup) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + instance.Status.PostgresGroup = desiredGroup + err = r.Status().Update(ctx, instance) + if err != nil { + return r.requeue(ctx, instance, err) + } + } } err = r.addFinalizer(ctx, reqLogger, instance) From cd7f2cd2bd551f5ab527831ec54e22f642dcbd1e Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 21:54:30 +0300 Subject: [PATCH 19/37] feat: Update operator k8s role --- .../templates/clusterrole.yaml | 8 ++++++++ charts/ext-postgres-operator/templates/role.yaml | 14 +++++++++++--- internal/controller/postgres_controller.go | 12 ++++++++++-- internal/controller/postgresuser_controller.go | 3 +++ pkg/postgres/aws.go | 4 ++++ pkg/postgres/azure.go | 4 ++++ pkg/postgres/database.go | 9 +++++++++ pkg/postgres/gcp.go | 4 ++++ pkg/postgres/postgres.go | 1 + 9 files changed, 54 insertions(+), 5 deletions(-) diff --git a/charts/ext-postgres-operator/templates/clusterrole.yaml b/charts/ext-postgres-operator/templates/clusterrole.yaml index 990555714..eb8a3dcba 100644 --- a/charts/ext-postgres-operator/templates/clusterrole.yaml +++ b/charts/ext-postgres-operator/templates/clusterrole.yaml @@ -11,6 +11,14 @@ rules: - secrets verbs: - "*" + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update - apiGroups: - coordination.k8s.io resources: diff --git a/charts/ext-postgres-operator/templates/role.yaml b/charts/ext-postgres-operator/templates/role.yaml index 50c9f8d33..97ca0500b 100644 --- a/charts/ext-postgres-operator/templates/role.yaml +++ b/charts/ext-postgres-operator/templates/role.yaml @@ -18,7 +18,15 @@ rules: resources: - pods verbs: - - "get" + - get + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update - apiGroups: - coordination.k8s.io resources: @@ -32,9 +40,9 @@ rules: - patch - delete - apiGroups: - - "apps" + - apps resources: - replicasets - deployments verbs: - - "get" + - get diff --git a/internal/controller/postgres_controller.go b/internal/controller/postgres_controller.go index 1135e8950..f709927b4 100644 --- a/internal/controller/postgres_controller.go +++ b/internal/controller/postgres_controller.go @@ -170,7 +170,7 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } desiredOwner := instance.Spec.MasterRole - // handle owner rename if was previously set by instance.Spec.MasterRole then was removed + // reconcile instance.Spec.MasterRole if it was changed if desiredOwner == "" { desiredOwner = fmt.Sprintf("%s-group", instance.Spec.Database) } @@ -184,6 +184,14 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c instance.Status.Roles.Owner = desiredOwner } + // reconcile the desired owner of the database + if instance.Status.Roles.Owner != "" { + err = r.pg.AlterDatabaseOwner(instance.Spec.Database, instance.Status.Roles.Owner) + if err != nil { + return requeue(errors.NewInternalError(err)) + } + } + // create extensions for _, extension := range instance.Spec.Extensions { // Check if extension is already added. Skip if already is added. @@ -208,7 +216,7 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c writerPrivs = "SELECT,INSERT,DELETE,UPDATE" writerSequencePrivs = "USAGE,SELECT" writerFunctionPrivs = "EXECUTE" - ownerPrivs = "ALL,MAINTAIN" + ownerPrivs = "ALL" ownerFunctionPrivs = "ALL" ownerSequencePrivs = "ALL" ) diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index 3ba210ed3..f727ec51f 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -254,6 +254,9 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request return r.requeue(ctx, instance, err) } } + } else { + role = instance.Status.PostgresRole + login = instance.Status.PostgresLogin } err = r.addFinalizer(ctx, reqLogger, instance) diff --git a/pkg/postgres/aws.go b/pkg/postgres/aws.go index 61e732357..2073a213a 100644 --- a/pkg/postgres/aws.go +++ b/pkg/postgres/aws.go @@ -78,3 +78,7 @@ func (c *awspg) DropRole(role, newOwner, database string, logger logr.Logger) er return c.pg.DropRole(role, newOwner, database, logger) } + +func (c *awspg) AlterDatabaseOwner(dbName, owner string) error { + return c.pg.AlterDatabaseOwner(dbName, owner) +} diff --git a/pkg/postgres/azure.go b/pkg/postgres/azure.go index 99628bcb7..72a9bafa7 100644 --- a/pkg/postgres/azure.go +++ b/pkg/postgres/azure.go @@ -48,3 +48,7 @@ func (azpg *azurepg) DropRole(role, newOwner, database string, logger logr.Logge // Delegate to parent implementation to perform the actual drop return azpg.pg.DropRole(role, newOwner, database, logger) } + +func (azpg *azurepg) AlterDatabaseOwner(dbName, owner string) error { + return azpg.pg.AlterDatabaseOwner(dbName, owner) +} diff --git a/pkg/postgres/database.go b/pkg/postgres/database.go index 087988370..a5637af91 100644 --- a/pkg/postgres/database.go +++ b/pkg/postgres/database.go @@ -48,6 +48,15 @@ func (c *pg) CreateDB(dbname, role string) error { return nil } +// reconcile the desired owner of the database +func (c *pg) AlterDatabaseOwner(dbname, owner string) error { + _, err := c.db.Exec(fmt.Sprintf(ALTER_DB_OWNER, dbname, owner)) + if err != nil { + return err + } + return nil +} + func (c *pg) CreateSchema(db, role, schema string, logger logr.Logger) error { tmpDb, err := GetConnection(c.user, c.pass, c.host, db, c.args, logger) if err != nil { diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go index 1531ffb84..8fda76bbf 100644 --- a/pkg/postgres/gcp.go +++ b/pkg/postgres/gcp.go @@ -83,3 +83,7 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er } return nil } + +func (c *gcppg) AlterDatabaseOwner(dbName, owner string) error { + return c.pg.AlterDatabaseOwner(dbName, owner) +} diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index ec5103049..007bba353 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -18,6 +18,7 @@ type PG interface { CreateUserRole(role, password string) (string, error) UpdatePassword(role, password string) error GrantRole(role, grantee string) error + AlterDatabaseOwner(dbName, owner string) error SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logger logr.Logger) error RevokeRole(role, revoked string) error AlterDefaultLoginRole(role, setRole string) error From 7ec4bde9b12bb74de7918d742613f90e026a82fb Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 18 Sep 2025 21:54:52 +0300 Subject: [PATCH 20/37] feat: Update operator k8s role --- config/rbac/cluster_role.yaml | 1 + config/rbac/role.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config/rbac/cluster_role.yaml b/config/rbac/cluster_role.yaml index 47e51711f..e2ae7bf28 100644 --- a/config/rbac/cluster_role.yaml +++ b/config/rbac/cluster_role.yaml @@ -7,6 +7,7 @@ rules: - "" resources: - secrets + - events verbs: - "*" - apiGroups: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 90271295e..77c714612 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -9,6 +9,7 @@ rules: - configmaps - secrets - services + - events verbs: - "*" - apiGroups: From 42ff375d0efeb31a6aee45e1a74052cfa5b20895 Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 11:53:59 +0300 Subject: [PATCH 21/37] feat: stage podMonitor Changes --- .../templates/service-monitor.yaml | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/charts/ext-postgres-operator/templates/service-monitor.yaml b/charts/ext-postgres-operator/templates/service-monitor.yaml index 8905d9473..e5dc30faf 100644 --- a/charts/ext-postgres-operator/templates/service-monitor.yaml +++ b/charts/ext-postgres-operator/templates/service-monitor.yaml @@ -1,34 +1,30 @@ -{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") ( .Values.serviceMonitor ) }} +{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") ( .Values.podMonitor ) }} apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor +kind: PodMonitor metadata: annotations: - meta.helm.sh/release-name: {{ .Release.Name }} - meta.helm.sh/release-namespace: {{ .Release.Namespace }} - {{- with .Values.serviceMonitor.additionalAnnotations }} + {{- with .Values.podMonitor.additionalAnnotations }} {{- toYaml . | nindent 4 }} {{- end }} labels: {{- include "chart.labels" . | nindent 4 }} - {{- with .Values.serviceMonitor.additonalLabels }} + {{- with .Values.podMonitor.additonalLabels }} {{- toYaml . | nindent 4 }} {{- end }} name: {{ include "chart.fullname" . }} namespace: {{ .Release.Namespace }} spec: - endpoints: - - port: "metrics" - path: "/metrics" - interval: {{ .Values.serviceMonitor.interval | default "30s" }} - scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout | default "10s" }} - scheme: http - {{- if .Values.serviceMonitor.relabelings }} - relabelings: - {{- tpl (toYaml .Values.serviceMonitor.relabelings) . | nindent 6 }} - {{- end }} namespaceSelector: matchNames: - - {{ .Release.Namespace }} + - {{ .Release.Namespace }} + podMetricsEndpoints: + - interval: 30s + path: /metrics + port: metrics + {{- if .Values.podMonitor.relabelings }} + relabelings: + {{- tpl (toYaml .Values.podMonitor.relabelings) . | nindent 6 }} + {{- end }} selector: matchLabels: {{- include "chart.selectorLabels" . | nindent 6 }} From 8547b5c8e6d75f13c5a7651070004547d3c20dcb Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 11:54:20 +0300 Subject: [PATCH 22/37] feat: stage podMonitor Changes --- .../templates/{service-monitor.yaml => pod-monitor.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename charts/ext-postgres-operator/templates/{service-monitor.yaml => pod-monitor.yaml} (100%) diff --git a/charts/ext-postgres-operator/templates/service-monitor.yaml b/charts/ext-postgres-operator/templates/pod-monitor.yaml similarity index 100% rename from charts/ext-postgres-operator/templates/service-monitor.yaml rename to charts/ext-postgres-operator/templates/pod-monitor.yaml From d7ac2979b96aa64c0d06d3c9049feb84aebd3a78 Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 12:41:39 +0300 Subject: [PATCH 23/37] feat: add podMonitor since no service to be discvered --- charts/ext-postgres-operator/templates/pod-monitor.yaml | 1 + charts/ext-postgres-operator/values.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/charts/ext-postgres-operator/templates/pod-monitor.yaml b/charts/ext-postgres-operator/templates/pod-monitor.yaml index e5dc30faf..aa6afa77c 100644 --- a/charts/ext-postgres-operator/templates/pod-monitor.yaml +++ b/charts/ext-postgres-operator/templates/pod-monitor.yaml @@ -19,6 +19,7 @@ spec: - {{ .Release.Namespace }} podMetricsEndpoints: - interval: 30s + scrapeTimeout: 10s path: /metrics port: metrics {{- if .Values.podMonitor.relabelings }} diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index 1fe8e4ad6..eed76b7d4 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -114,8 +114,8 @@ env: {} # POSTGRES_INSTANCE: "XXXXXXXXXX" # POSTGRES_CLOUD_PROVIDER: "AWS" -# ServiceMonitor is a custom resource defined by the Prometheus Operator -serviceMonitor: {} +# podMonitor is a custom resource used by the Prometheus-Operator and others +podMonitor: {} # interval: 30s # scrapeTimeout: 10s # relabeling: [] From bdcb5ed39dff028b3aeaaa8de82c4a97bb93b35e Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 12:42:32 +0300 Subject: [PATCH 24/37] feat: add podMonitor since no service to be discvered --- charts/ext-postgres-operator/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index eed76b7d4..f4b325e60 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -79,7 +79,7 @@ watchNamespace: "" # Define connection to postgres database server postgres: # postgres hostname - host: "localhost" + host: "localhost:5432" # postgres admin user and password ( ignored if existingSecret or ExternalSecret is set ) user: "admin" password: "password" From 5ad87134241998a3b42a3f8f2daf6b4f32121af4 Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 15:13:39 +0300 Subject: [PATCH 25/37] fix: tests --- pkg/postgres/mock/postgres.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/postgres/mock/postgres.go b/pkg/postgres/mock/postgres.go index 962ebe77d..1a9649e3b 100644 --- a/pkg/postgres/mock/postgres.go +++ b/pkg/postgres/mock/postgres.go @@ -210,6 +210,20 @@ func (mr *MockPGMockRecorder) GrantRole(role, grantee any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantRole", reflect.TypeOf((*MockPG)(nil).GrantRole), role, grantee) } +// AlterDatabaseOwner mocks base method. +func (m *MockPG) AlterDatabaseOwner(dbName, owner string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AlterDatabaseOwner", dbName, owner) + ret0, _ := ret[0].(error) + return ret0 +} + +// AlterDatabaseOwner indicates an expected call of AlterDatabaseOwner. +func (mr *MockPGMockRecorder) AlterDatabaseOwner(dbName, owner any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AlterDatabaseOwner", reflect.TypeOf((*MockPG)(nil).AlterDatabaseOwner), dbName, owner) +} + // RevokeRole mocks base method. func (m *MockPG) RevokeRole(role, revoked string) error { m.ctrl.T.Helper() From 1333a0e1f9e0839d65c3b7b419e931eff9ec855f Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 15:24:30 +0300 Subject: [PATCH 26/37] fix: tests --- .../controller/postgres_controller_test.go | 3 +++ pkg/postgres/aws.go | 4 ++++ pkg/postgres/azure.go | 4 ++++ pkg/postgres/database.go | 22 +++++++++++++++++++ pkg/postgres/gcp.go | 4 ++++ pkg/postgres/mock/postgres.go | 14 ++++++++++++ pkg/postgres/postgres.go | 1 + 7 files changed, 52 insertions(+) diff --git a/internal/controller/postgres_controller_test.go b/internal/controller/postgres_controller_test.go index 86e2bfa3e..f4c609622 100644 --- a/internal/controller/postgres_controller_test.go +++ b/internal/controller/postgres_controller_test.go @@ -71,6 +71,9 @@ var _ = Describe("PostgresReconciler", func() { // Gomock mockCtrl = gomock.NewController(GinkgoT()) pg = mockpg.NewMockPG(mockCtrl) + pg.EXPECT().AlterDatabaseOwner(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + pg.EXPECT().ReassignDatabaseOwner(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + pg.EXPECT().ReassignDatabaseOwner(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() cl = k8sClient // Create runtime scheme sc = scheme.Scheme diff --git a/pkg/postgres/aws.go b/pkg/postgres/aws.go index 2073a213a..9a5aa96bc 100644 --- a/pkg/postgres/aws.go +++ b/pkg/postgres/aws.go @@ -82,3 +82,7 @@ func (c *awspg) DropRole(role, newOwner, database string, logger logr.Logger) er func (c *awspg) AlterDatabaseOwner(dbName, owner string) error { return c.pg.AlterDatabaseOwner(dbName, owner) } + +func (c *awspg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { + return c.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) +} diff --git a/pkg/postgres/azure.go b/pkg/postgres/azure.go index 72a9bafa7..5cdb04b0c 100644 --- a/pkg/postgres/azure.go +++ b/pkg/postgres/azure.go @@ -52,3 +52,7 @@ func (azpg *azurepg) DropRole(role, newOwner, database string, logger logr.Logge func (azpg *azurepg) AlterDatabaseOwner(dbName, owner string) error { return azpg.pg.AlterDatabaseOwner(dbName, owner) } + +func (azpg *azurepg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { + return azpg.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) +} diff --git a/pkg/postgres/database.go b/pkg/postgres/database.go index a5637af91..7608f8da3 100644 --- a/pkg/postgres/database.go +++ b/pkg/postgres/database.go @@ -12,6 +12,7 @@ const ( CREATE_SCHEMA = `CREATE SCHEMA IF NOT EXISTS "%s" AUTHORIZATION "%s"` CREATE_EXTENSION = `CREATE EXTENSION IF NOT EXISTS "%s"` ALTER_DB_OWNER = `ALTER DATABASE "%s" OWNER TO "%s"` + REASSIGN_DB_OWNER = `REASSIGN OWNED BY "%s" TO "%s"` DROP_DATABASE = `DROP DATABASE "%s"` GRANT_USAGE_SCHEMA = `GRANT USAGE ON SCHEMA "%s" TO "%s"` GRANT_CREATE_TABLE = `GRANT CREATE ON SCHEMA "%s" TO "%s"` @@ -50,10 +51,31 @@ func (c *pg) CreateDB(dbname, role string) error { // reconcile the desired owner of the database func (c *pg) AlterDatabaseOwner(dbname, owner string) error { + if owner == "" { + return nil + } _, err := c.db.Exec(fmt.Sprintf(ALTER_DB_OWNER, dbname, owner)) + return err +} + +func (c *pg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { + if currentOwner == "" || newOwner == "" || currentOwner == newOwner { + return nil + } + + tmpDb, err := GetConnection(c.user, c.pass, c.host, dbName, c.args, logger) if err != nil { return err } + defer tmpDb.Close() + + _, err = tmpDb.Exec(fmt.Sprintf(REASSIGN_DB_OWNER, currentOwner, newOwner)) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok && pqErr.Code == "42704" { + return nil + } + return err + } return nil } diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go index 8fda76bbf..e88ab461e 100644 --- a/pkg/postgres/gcp.go +++ b/pkg/postgres/gcp.go @@ -87,3 +87,7 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er func (c *gcppg) AlterDatabaseOwner(dbName, owner string) error { return c.pg.AlterDatabaseOwner(dbName, owner) } + +func (c *gcppg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { + return c.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) +} diff --git a/pkg/postgres/mock/postgres.go b/pkg/postgres/mock/postgres.go index 1a9649e3b..3c920cd09 100644 --- a/pkg/postgres/mock/postgres.go +++ b/pkg/postgres/mock/postgres.go @@ -224,6 +224,20 @@ func (mr *MockPGMockRecorder) AlterDatabaseOwner(dbName, owner any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AlterDatabaseOwner", reflect.TypeOf((*MockPG)(nil).AlterDatabaseOwner), dbName, owner) } +// ReassignDatabaseOwner mocks base method. +func (m *MockPG) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReassignDatabaseOwner", dbName, currentOwner, newOwner, logger) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReassignDatabaseOwner indicates an expected call of ReassignDatabaseOwner. +func (mr *MockPGMockRecorder) ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReassignDatabaseOwner", reflect.TypeOf((*MockPG)(nil).ReassignDatabaseOwner), dbName, currentOwner, newOwner, logger) +} + // RevokeRole mocks base method. func (m *MockPG) RevokeRole(role, revoked string) error { m.ctrl.T.Helper() diff --git a/pkg/postgres/postgres.go b/pkg/postgres/postgres.go index 007bba353..c4f12e527 100644 --- a/pkg/postgres/postgres.go +++ b/pkg/postgres/postgres.go @@ -19,6 +19,7 @@ type PG interface { UpdatePassword(role, password string) error GrantRole(role, grantee string) error AlterDatabaseOwner(dbName, owner string) error + ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error SetSchemaPrivileges(schemaPrivileges PostgresSchemaPrivileges, logger logr.Logger) error RevokeRole(role, revoked string) error AlterDefaultLoginRole(role, setRole string) error From f89bc323fe00cab9ffd22e2e90e21fe606993ced Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 15:28:49 +0300 Subject: [PATCH 27/37] fix: tests --- internal/controller/postgres_controller_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/postgres_controller_test.go b/internal/controller/postgres_controller_test.go index f4c609622..ca2129e1f 100644 --- a/internal/controller/postgres_controller_test.go +++ b/internal/controller/postgres_controller_test.go @@ -687,10 +687,10 @@ var _ = Describe("PostgresReconciler", func() { // Expected method calls // customers schema pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(nil).Times(1) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(3) + pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() // stores schema pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Return(nil).Times(1) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(3) + pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() }) It("should update status", func() { @@ -711,10 +711,10 @@ var _ = Describe("PostgresReconciler", func() { // Expected method calls // customers schema errors pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(fmt.Errorf("Could not create schema")).Times(1) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(0) + pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() // stores schema pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Return(nil).Times(1) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(3) + pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() }) It("should update status", func() { From f633c5e7900fb2aeffa36b6d545acb0f3a9d4e99 Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 15:31:43 +0300 Subject: [PATCH 28/37] fix: tests --- internal/controller/postgres_controller_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/controller/postgres_controller_test.go b/internal/controller/postgres_controller_test.go index ca2129e1f..2d5173f77 100644 --- a/internal/controller/postgres_controller_test.go +++ b/internal/controller/postgres_controller_test.go @@ -746,10 +746,9 @@ var _ = Describe("PostgresReconciler", func() { It("should not recreate existing schema", func() { // customers schema pg.EXPECT().CreateSchema(name, name+"-group", "customers", gomock.Any()).Return(nil).Times(1) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(3) + pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() // stores schema already exists pg.EXPECT().CreateSchema(name, name+"-group", "stores", gomock.Any()).Times(0) - pg.EXPECT().SetSchemaPrivileges(gomock.Any(), gomock.Any()).Return(nil).Times(0) // Call reconcile err := runReconcile(rp, ctx, req) Expect(err).NotTo(HaveOccurred()) From 8795e098c83f12c5be41e8da13d87335daa5dbaf Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 16:01:07 +0300 Subject: [PATCH 29/37] fix: missing value sin the podmonitor --- charts/ext-postgres-operator/templates/pod-monitor.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/ext-postgres-operator/templates/pod-monitor.yaml b/charts/ext-postgres-operator/templates/pod-monitor.yaml index aa6afa77c..13013a894 100644 --- a/charts/ext-postgres-operator/templates/pod-monitor.yaml +++ b/charts/ext-postgres-operator/templates/pod-monitor.yaml @@ -18,8 +18,8 @@ spec: matchNames: - {{ .Release.Namespace }} podMetricsEndpoints: - - interval: 30s - scrapeTimeout: 10s + - interval: {{ .Values.podMonitor.interval | default "30s" }} + scrapeTimeout: {{ .Values.podMonitor.scrapeTimeout | default "10s" }} path: /metrics port: metrics {{- if .Values.podMonitor.relabelings }} From d4c6e2b9dccac7c181af52c96a6de64e549d0804 Mon Sep 17 00:00:00 2001 From: TK Date: Fri, 19 Sep 2025 17:46:01 +0300 Subject: [PATCH 30/37] fix: tests --- internal/controller/postgres_controller.go | 9 +++------ internal/controller/postgresuser_controller.go | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/controller/postgres_controller.go b/internal/controller/postgres_controller.go index f709927b4..96b0b62eb 100644 --- a/internal/controller/postgres_controller.go +++ b/internal/controller/postgres_controller.go @@ -170,7 +170,7 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } desiredOwner := instance.Spec.MasterRole - // reconcile instance.Spec.MasterRole if it was changed + // If no owner was specified, use default owner name if desiredOwner == "" { desiredOwner = fmt.Sprintf("%s-group", instance.Spec.Database) } @@ -181,15 +181,12 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c if err != nil { return requeue(errors.NewInternalError(err)) } - instance.Status.Roles.Owner = desiredOwner - } - - // reconcile the desired owner of the database - if instance.Status.Roles.Owner != "" { + // Alter database owner if the owner role was changed err = r.pg.AlterDatabaseOwner(instance.Spec.Database, instance.Status.Roles.Owner) if err != nil { return requeue(errors.NewInternalError(err)) } + instance.Status.Roles.Owner = desiredOwner } // create extensions diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index f727ec51f..1b4ce8684 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -225,6 +225,7 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request desiredGroup = database.Status.Roles.Owner } + // Ability user to be reassigned to another group role currentGroup := instance.Status.PostgresGroup if desiredGroup != "" && currentGroup != desiredGroup { From 1a878d8e10aabcdea90a90ab6b9d1b4155696466 Mon Sep 17 00:00:00 2001 From: tk <157844717+tkcontiant@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:20:30 +0300 Subject: [PATCH 31/37] Apply suggestion from @pcallewaert Co-authored-by: Pieter C --- charts/ext-postgres-operator/templates/pod-monitor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/ext-postgres-operator/templates/pod-monitor.yaml b/charts/ext-postgres-operator/templates/pod-monitor.yaml index 13013a894..e4813bb1d 100644 --- a/charts/ext-postgres-operator/templates/pod-monitor.yaml +++ b/charts/ext-postgres-operator/templates/pod-monitor.yaml @@ -1,4 +1,4 @@ -{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") ( .Values.podMonitor ) }} +{{- if and (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") ( .Values.podMonitor.enabled ) }} apiVersion: monitoring.coreos.com/v1 kind: PodMonitor metadata: From 35f852cf4e51d18f3ddfff72c39293d3239b6804 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:35:11 +0300 Subject: [PATCH 32/37] reoslve conflicts after rebase --- .../controller/postgresuser_controller.go | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index 1b4ce8684..ad1525704 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -258,6 +258,34 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request } else { role = instance.Status.PostgresRole login = instance.Status.PostgresLogin + awsConfig := instance.Spec.AWS + awsIamRequested := awsConfig != nil && awsConfig.EnableIamAuth + + if r.cloudProvider == "AWS" { + if awsIamRequested && !instance.Status.EnableIamAuth { + if err := r.pg.GrantRole("rds_iam", role); err != nil { + reqLogger.WithValues("role", role).Error(err, "failed to grant rds_iam role") + } else { + instance.Status.EnableIamAuth = true + if sErr := r.Status().Update(ctx, instance); sErr != nil { + reqLogger.WithValues("role", role).Error(sErr, "failed to update status after IAM grant") + } + } + } + + // Revoke aws_iam role on transition: spec=false, status=true + if !awsIamRequested && instance.Status.EnableIamAuth { + if err := r.pg.RevokeRole("rds_iam", role); err != nil { + reqLogger.WithValues("role", role).Error(err, "failed to revoke rds_iam role") + } else { + instance.Status.EnableIamAuth = false + if sErr := r.Status().Update(ctx, instance); sErr != nil { + reqLogger.WithValues("role", role).Error(sErr, "failed to update status after IAM revoke") + } + } + } + } else if awsIamRequested { + reqLogger.WithValues("role", role).Info("IAM Auth requested while we are not running with AWS cloud provider config") } err = r.addFinalizer(ctx, reqLogger, instance) From 2de42f2538b23939f1ef0c1e835a2cf1080f5751 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:17:35 +0300 Subject: [PATCH 33/37] reoslve conflicts after rebase --- .../controller/postgresuser_controller.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index ad1525704..77b27841d 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -286,6 +286,58 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request } } else if awsIamRequested { reqLogger.WithValues("role", role).Info("IAM Auth requested while we are not running with AWS cloud provider config") + + // Reconcile logic for changes in group membership + // This is only applicable if user role is already created + // and privileges are changed in spec + if instance.Status.PostgresRole != "" { + + // We need to get the Postgres CR to get the group role name + database, err := r.getPostgresCR(ctx, instance) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + // Determine desired group role + var desiredGroup string + switch instance.Spec.Privileges { + case "READ": + desiredGroup = database.Status.Roles.Reader + case "WRITE": + desiredGroup = database.Status.Roles.Writer + default: + desiredGroup = database.Status.Roles.Owner + } + + currentGroup := instance.Status.PostgresGroup + if desiredGroup != "" && currentGroup != desiredGroup { + + // Remove the old group membership if present + if currentGroup != "" { + err = r.pg.RevokeRole(currentGroup, role) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + } + + // Grant the new group role + err = r.pg.GrantRole(desiredGroup, role) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + // Ensure objects created by the user are owned by the new group + err = r.pg.AlterDefaultLoginRole(role, desiredGroup) + if err != nil { + return r.requeue(ctx, instance, errors.NewInternalError(err)) + } + + instance.Status.PostgresGroup = desiredGroup + err = r.Status().Update(ctx, instance) + if err != nil { + return r.requeue(ctx, instance, err) + } + } } err = r.addFinalizer(ctx, reqLogger, instance) From c00cf35966952d6746f0d77f92a8ec13947a3e04 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:18:21 +0300 Subject: [PATCH 34/37] reoslve conflicts after rebase --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 4360d8bfb..87f361d95 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ kubeconfig .gomodcache .gocache + +# helm template output temporary files +/**/out +out From f55d5e2d58f89392619d1492bfb5c9e79bdec788 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:23:53 +0300 Subject: [PATCH 35/37] fix: not needed cluster wide permission --- .../templates/clusterrole.yaml | 22 ------------------- config/rbac/cluster_role.yaml | 15 ------------- 2 files changed, 37 deletions(-) diff --git a/charts/ext-postgres-operator/templates/clusterrole.yaml b/charts/ext-postgres-operator/templates/clusterrole.yaml index eb8a3dcba..3d1d6767f 100644 --- a/charts/ext-postgres-operator/templates/clusterrole.yaml +++ b/charts/ext-postgres-operator/templates/clusterrole.yaml @@ -11,28 +11,6 @@ rules: - secrets verbs: - "*" - - apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - update - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - apps resourceNames: - ext-postgres-operator resources: diff --git a/config/rbac/cluster_role.yaml b/config/rbac/cluster_role.yaml index e2ae7bf28..408e76c70 100644 --- a/config/rbac/cluster_role.yaml +++ b/config/rbac/cluster_role.yaml @@ -7,23 +7,8 @@ rules: - "" resources: - secrets - - events verbs: - "*" - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - apps resourceNames: - ext-postgres-operator resources: From 627f711c330e9cc78cff20a476a8dc2d04a6c5fd Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:26:36 +0300 Subject: [PATCH 36/37] feat: provide podMonitor enable/disable bool --- charts/ext-postgres-operator/values.yaml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/charts/ext-postgres-operator/values.yaml b/charts/ext-postgres-operator/values.yaml index f4b325e60..2d79cc112 100644 --- a/charts/ext-postgres-operator/values.yaml +++ b/charts/ext-postgres-operator/values.yaml @@ -115,17 +115,18 @@ env: {} # POSTGRES_CLOUD_PROVIDER: "AWS" # podMonitor is a custom resource used by the Prometheus-Operator and others -podMonitor: {} - # interval: 30s - # scrapeTimeout: 10s - # relabeling: [] - # # - targetLabel: app - # # replacement: '{{ include "chart.name" . }}' - # additonalLabels: {} - # # e.g. release label of the prometheus operator - # # release: prometheus-operator - # additionalAnnotations: {} - # # e.g. {} +podMonitor: + enabled: false + interval: 30s + scrapeTimeout: 10s + relabeling: [] + # - targetLabel: app + # replacement: '{{ include "chart.name" . }}' + additonalLabels: {} + # e.g. release label of the prometheus operator + # release: prometheus-operator + additionalAnnotations: {} + # e.g. {} nodeSelector: {} From d5905448cd8dfb875568019c5680dc39fb40d3f6 Mon Sep 17 00:00:00 2001 From: TK Date: Thu, 25 Sep 2025 18:28:36 +0300 Subject: [PATCH 37/37] fix: tests after review --- charts/ext-postgres-operator/Chart.yaml | 2 +- .../templates/clusterrole.yaml | 2 + config/rbac/cluster_role.yaml | 2 + .../controller/postgres_controller_test.go | 1 - .../controller/postgresuser_controller.go | 97 ++----------------- pkg/postgres/aws.go | 8 -- pkg/postgres/azure.go | 8 -- pkg/postgres/gcp.go | 8 -- 8 files changed, 11 insertions(+), 117 deletions(-) diff --git a/charts/ext-postgres-operator/Chart.yaml b/charts/ext-postgres-operator/Chart.yaml index 6bdad7f5c..a6c89f94f 100644 --- a/charts/ext-postgres-operator/Chart.yaml +++ b/charts/ext-postgres-operator/Chart.yaml @@ -9,4 +9,4 @@ description: | type: application version: 2.3.0 -appVersion: "2.2.0" +appVersion: "2.4.0" diff --git a/charts/ext-postgres-operator/templates/clusterrole.yaml b/charts/ext-postgres-operator/templates/clusterrole.yaml index 3d1d6767f..a37699feb 100644 --- a/charts/ext-postgres-operator/templates/clusterrole.yaml +++ b/charts/ext-postgres-operator/templates/clusterrole.yaml @@ -11,6 +11,8 @@ rules: - secrets verbs: - "*" + - apiGroups: + - apps resourceNames: - ext-postgres-operator resources: diff --git a/config/rbac/cluster_role.yaml b/config/rbac/cluster_role.yaml index 408e76c70..37104b321 100644 --- a/config/rbac/cluster_role.yaml +++ b/config/rbac/cluster_role.yaml @@ -9,6 +9,8 @@ rules: - secrets verbs: - "*" + - apiGroups: + - apps resourceNames: - ext-postgres-operator resources: diff --git a/internal/controller/postgres_controller_test.go b/internal/controller/postgres_controller_test.go index 2d5173f77..b98167be9 100644 --- a/internal/controller/postgres_controller_test.go +++ b/internal/controller/postgres_controller_test.go @@ -73,7 +73,6 @@ var _ = Describe("PostgresReconciler", func() { pg = mockpg.NewMockPG(mockCtrl) pg.EXPECT().AlterDatabaseOwner(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() pg.EXPECT().ReassignDatabaseOwner(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() - pg.EXPECT().ReassignDatabaseOwner(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() cl = k8sClient // Create runtime scheme sc = scheme.Scheme diff --git a/internal/controller/postgresuser_controller.go b/internal/controller/postgresuser_controller.go index 77b27841d..ad36bf5a7 100644 --- a/internal/controller/postgresuser_controller.go +++ b/internal/controller/postgresuser_controller.go @@ -203,89 +203,7 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request } } else if awsIamRequested { reqLogger.WithValues("role", role).Info("IAM Auth requested while we are not running with AWS cloud provider config") - // Reconcile logic for changes in group membership - // This is only applicable if user role is already created - // and privileges are changed in spec - if instance.Status.PostgresRole != "" { - - // We need to get the Postgres CR to get the group role name - database, err := r.getPostgresCR(ctx, instance) - if err != nil { - return r.requeue(ctx, instance, errors.NewInternalError(err)) - } - - // Determine desired group role - var desiredGroup string - switch instance.Spec.Privileges { - case "READ": - desiredGroup = database.Status.Roles.Reader - case "WRITE": - desiredGroup = database.Status.Roles.Writer - default: - desiredGroup = database.Status.Roles.Owner - } - - // Ability user to be reassigned to another group role - currentGroup := instance.Status.PostgresGroup - if desiredGroup != "" && currentGroup != desiredGroup { - - // Remove the old group membership if present - if currentGroup != "" { - err = r.pg.RevokeRole(currentGroup, role) - if err != nil { - return r.requeue(ctx, instance, errors.NewInternalError(err)) - } - } - - // Grant the new group role - err = r.pg.GrantRole(desiredGroup, role) - if err != nil { - return r.requeue(ctx, instance, errors.NewInternalError(err)) - } - - // Ensure objects created by the user are owned by the new group - err = r.pg.AlterDefaultLoginRole(role, desiredGroup) - if err != nil { - return r.requeue(ctx, instance, errors.NewInternalError(err)) - } - - instance.Status.PostgresGroup = desiredGroup - err = r.Status().Update(ctx, instance) - if err != nil { - return r.requeue(ctx, instance, err) - } - } - } else { - role = instance.Status.PostgresRole - login = instance.Status.PostgresLogin - awsConfig := instance.Spec.AWS - awsIamRequested := awsConfig != nil && awsConfig.EnableIamAuth - - if r.cloudProvider == "AWS" { - if awsIamRequested && !instance.Status.EnableIamAuth { - if err := r.pg.GrantRole("rds_iam", role); err != nil { - reqLogger.WithValues("role", role).Error(err, "failed to grant rds_iam role") - } else { - instance.Status.EnableIamAuth = true - if sErr := r.Status().Update(ctx, instance); sErr != nil { - reqLogger.WithValues("role", role).Error(sErr, "failed to update status after IAM grant") - } - } - } - - // Revoke aws_iam role on transition: spec=false, status=true - if !awsIamRequested && instance.Status.EnableIamAuth { - if err := r.pg.RevokeRole("rds_iam", role); err != nil { - reqLogger.WithValues("role", role).Error(err, "failed to revoke rds_iam role") - } else { - instance.Status.EnableIamAuth = false - if sErr := r.Status().Update(ctx, instance); sErr != nil { - reqLogger.WithValues("role", role).Error(sErr, "failed to update status after IAM revoke") - } - } - } - } else if awsIamRequested { - reqLogger.WithValues("role", role).Info("IAM Auth requested while we are not running with AWS cloud provider config") + } // Reconcile logic for changes in group membership // This is only applicable if user role is already created @@ -309,32 +227,29 @@ func (r *PostgresUserReconciler) Reconcile(ctx context.Context, req ctrl.Request desiredGroup = database.Status.Roles.Owner } + // Ability user to be reassigned to another group role currentGroup := instance.Status.PostgresGroup if desiredGroup != "" && currentGroup != desiredGroup { // Remove the old group membership if present if currentGroup != "" { - err = r.pg.RevokeRole(currentGroup, role) - if err != nil { + if err := r.pg.RevokeRole(currentGroup, role); err != nil { return r.requeue(ctx, instance, errors.NewInternalError(err)) } } // Grant the new group role - err = r.pg.GrantRole(desiredGroup, role) - if err != nil { + if err := r.pg.GrantRole(desiredGroup, role); err != nil { return r.requeue(ctx, instance, errors.NewInternalError(err)) } // Ensure objects created by the user are owned by the new group - err = r.pg.AlterDefaultLoginRole(role, desiredGroup) - if err != nil { + if err := r.pg.AlterDefaultLoginRole(role, desiredGroup); err != nil { return r.requeue(ctx, instance, errors.NewInternalError(err)) } instance.Status.PostgresGroup = desiredGroup - err = r.Status().Update(ctx, instance) - if err != nil { + if err := r.Status().Update(ctx, instance); err != nil { return r.requeue(ctx, instance, err) } } diff --git a/pkg/postgres/aws.go b/pkg/postgres/aws.go index 9a5aa96bc..61e732357 100644 --- a/pkg/postgres/aws.go +++ b/pkg/postgres/aws.go @@ -78,11 +78,3 @@ func (c *awspg) DropRole(role, newOwner, database string, logger logr.Logger) er return c.pg.DropRole(role, newOwner, database, logger) } - -func (c *awspg) AlterDatabaseOwner(dbName, owner string) error { - return c.pg.AlterDatabaseOwner(dbName, owner) -} - -func (c *awspg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { - return c.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) -} diff --git a/pkg/postgres/azure.go b/pkg/postgres/azure.go index 5cdb04b0c..99628bcb7 100644 --- a/pkg/postgres/azure.go +++ b/pkg/postgres/azure.go @@ -48,11 +48,3 @@ func (azpg *azurepg) DropRole(role, newOwner, database string, logger logr.Logge // Delegate to parent implementation to perform the actual drop return azpg.pg.DropRole(role, newOwner, database, logger) } - -func (azpg *azurepg) AlterDatabaseOwner(dbName, owner string) error { - return azpg.pg.AlterDatabaseOwner(dbName, owner) -} - -func (azpg *azurepg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { - return azpg.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) -} diff --git a/pkg/postgres/gcp.go b/pkg/postgres/gcp.go index e88ab461e..1531ffb84 100644 --- a/pkg/postgres/gcp.go +++ b/pkg/postgres/gcp.go @@ -83,11 +83,3 @@ func (c *gcppg) DropRole(role, newOwner, database string, logger logr.Logger) er } return nil } - -func (c *gcppg) AlterDatabaseOwner(dbName, owner string) error { - return c.pg.AlterDatabaseOwner(dbName, owner) -} - -func (c *gcppg) ReassignDatabaseOwner(dbName, currentOwner, newOwner string, logger logr.Logger) error { - return c.pg.ReassignDatabaseOwner(dbName, currentOwner, newOwner, logger) -}