diff --git a/.github/workflows/tests-cluster-chainsaw.yaml b/.github/workflows/tests-cluster-chainsaw.yaml index 5286413c2..555b3fe07 100644 --- a/.github/workflows/tests-cluster-chainsaw.yaml +++ b/.github/workflows/tests-cluster-chainsaw.yaml @@ -26,7 +26,7 @@ jobs: helm install prometheus-crds prometheus-community/prometheus-operator-crds - name: Install Chainsaw - uses: kyverno/action-install-chainsaw@82d8e747037f840e0ef9bdd97ecdc617f5535bdc # v0.2.8 + uses: kyverno/action-install-chainsaw@b2f61a8d0459a65c476ac802514d88e1612b3396 # v0.2.9 - name: Setup MinIO run: | diff --git a/charts/cluster/README.md b/charts/cluster/README.md index bf30859ce..476746d7d 100644 --- a/charts/cluster/README.md +++ b/charts/cluster/README.md @@ -153,6 +153,7 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.annotations | object | `{}` | | | cluster.certificates | object | `{}` | The configuration for the CA and related certificates. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-CertificatesConfiguration | | cluster.enableSuperuserAccess | bool | `true` | When this option is enabled, the operator will use the SuperuserSecret to update the postgres user password. If the secret is not present, the operator will automatically create one. When this option is disabled, the operator will ignore the SuperuserSecret content, delete it when automatically created, and then blank the password of the postgres user by setting it to NULL. | +| cluster.imageCatalogRef | object | `{}` | Reference to `ImageCatalog` of `ClusterImageCatalog`, if specified takes precedence over `cluster.imageName` | | cluster.imageName | string | `""` | Name of the container image, supporting both tags (:) and digests for deterministic and repeatable deployments: :@sha256: | | cluster.imagePullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never or IfNotPresent. If not defined, it defaults to IfNotPresent. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | | cluster.imagePullSecrets | list | `[]` | The list of pull secrets to be used to pull the images. See: https://cloudnative-pg.io/documentation/current/cloudnative-pg.v1/#postgresql-cnpg-io-v1-LocalObjectReference | @@ -164,10 +165,12 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.monitoring.podMonitor.enabled | bool | `true` | Whether to enable the PodMonitor | | cluster.monitoring.prometheusRule.enabled | bool | `true` | Whether to enable the PrometheusRule automated alerts | | cluster.monitoring.prometheusRule.excludeRules | list | `[]` | Exclude specified rules | -| cluster.postgresGID | int | `26` | The GID of the postgres user inside the image, defaults to 26 | -| cluster.postgresUID | int | `26` | The UID of the postgres user inside the image, defaults to 26 | +| cluster.postgresGID | int | `-1` | The GID of the postgres user inside the image, defaults to 26 | +| cluster.postgresUID | int | `-1` | The UID of the postgres user inside the image, defaults to 26 | | cluster.postgresql.parameters | object | `{}` | PostgreSQL configuration options (postgresql.conf) | | cluster.postgresql.pg_hba | list | `[]` | PostgreSQL Host Based Authentication rules (lines to be appended to the pg_hba.conf file) | +| cluster.postgresql.pg_ident | list | `[]` | PostgreSQL User Name Maps rules (lines to be appended to the pg_ident.conf file) | +| cluster.postgresql.shared_preload_libraries | list | `[]` | Lists of shared preload libraries to add to the default ones | | cluster.primaryUpdateMethod | string | `"switchover"` | Method to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated. It can be switchover (default) or restart. | | cluster.primaryUpdateStrategy | string | `"unsupervised"` | Strategy to follow to upgrade the primary server during a rolling update procedure, after all replicas have been successfully updated: it can be automated (unsupervised - default) or manual (supervised) | | cluster.priorityClassName | string | `""` | | @@ -180,6 +183,8 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | cluster.walStorage.size | string | `"1Gi"` | | | cluster.walStorage.storageClass | string | `""` | | | fullnameOverride | string | `""` | Override the full name of the chart | +| imageCatalog.create | bool | `true` | Whether to provision an image catalog. If imageCatalog.images is empty this option will be ignored. | +| imageCatalog.images | list | `[]` | List of images to be provisioned in an image catalog. | | mode | string | `"standalone"` | Cluster mode of operation. Available modes: * `standalone` - default mode. Creates new or updates an existing CNPG cluster. * `replica` - Creates a replica cluster from an existing CNPG cluster. # TODO * `recovery` - Same as standalone but creates a cluster from a backup, object store or via pg_basebackup. | | nameOverride | string | `""` | Override the name of the chart | | pooler.enabled | bool | `false` | Whether to enable PgBouncer | @@ -236,7 +241,11 @@ refer to the [CloudNativePG Documentation](https://cloudnative-pg.io/documentat | recovery.s3.secretKey | string | `""` | | | recovery.secret.create | bool | `true` | Whether to create a secret for the backup credentials | | recovery.secret.name | string | `""` | Name of the backup credentials secret | -| type | string | `"postgresql"` | Type of the CNPG database. Available types: * `postgresql` * `postgis` | +| type | string | `"postgresql"` | Type of the CNPG database. Available types: * `postgresql` * `postgis` * `timescaledb` * `paradedb` | +| version.paradedb | string | `"0.9.3"` | If using ParadeDB, specify the version | +| version.postgis | string | `"3.4"` | If using PostGIS, specify the version | +| version.postgresql | string | `"16"` | PostgreSQL major version to use | +| version.timescaledb | string | `"2.15"` | If using TimescaleDB, specify the version | ## Maintainers diff --git a/charts/cluster/README.md.gotmpl b/charts/cluster/README.md.gotmpl index e1a4d2f05..2a7fd1023 100644 --- a/charts/cluster/README.md.gotmpl +++ b/charts/cluster/README.md.gotmpl @@ -69,6 +69,8 @@ Cluster Configuration Currently the chart supports two database types. These are configured via the `type` parameter. These are: * `postgresql` - A standard PostgreSQL database. * `postgis` - A PostgreSQL database with the PostGIS extension installed. +* `timescaledb` - A PostgreSQL database with the TimescaleDB extension installed. +* `paradedb` - A PostgreSQL database with the ParadeDB extensions (`pg_search` and `pg_analytics`) installed. Depending on the type the chart will use a different Docker image and fill in some initial setup, like extension installation. diff --git a/charts/cluster/examples/basic.yaml b/charts/cluster/examples/basic.yaml index 5b608c267..730612c2f 100644 --- a/charts/cluster/examples/basic.yaml +++ b/charts/cluster/examples/basic.yaml @@ -1,4 +1,6 @@ mode: standalone +version: + postgresql: "16" cluster: instances: 1 backups: diff --git a/charts/cluster/examples/image-catalog-ref.yaml b/charts/cluster/examples/image-catalog-ref.yaml new file mode 100644 index 000000000..e4833a3b6 --- /dev/null +++ b/charts/cluster/examples/image-catalog-ref.yaml @@ -0,0 +1,12 @@ +type: postgresql +mode: standalone +version: + major: "16" + timescaledb: "2.15" +cluster: + instances: 1 + imageCatalogRef: + kind: ImageCatalog + name: my-image-catalog +backups: + enabled: false diff --git a/charts/cluster/examples/image-catalog.yaml b/charts/cluster/examples/image-catalog.yaml new file mode 100644 index 000000000..c610229b0 --- /dev/null +++ b/charts/cluster/examples/image-catalog.yaml @@ -0,0 +1,14 @@ +type: postgresql +mode: standalone +version: + major: "16" + timescaledb: "2.15" +cluster: + instances: 1 +backups: + enabled: false +imageCatalog: + create: true + images: + - major: 16 + image: my-custom-postgres-image:mytag diff --git a/charts/cluster/examples/postgis.yaml b/charts/cluster/examples/postgis.yaml index 6c686dc62..168ac9fbf 100644 --- a/charts/cluster/examples/postgis.yaml +++ b/charts/cluster/examples/postgis.yaml @@ -1,6 +1,9 @@ type: postgis mode: standalone +version: + postgresql: "16" + postgis: "3.4" cluster: instances: 1 backups: - enabled: false \ No newline at end of file + enabled: false diff --git a/charts/cluster/examples/timescaledb.yaml b/charts/cluster/examples/timescaledb.yaml new file mode 100644 index 000000000..328b6c1eb --- /dev/null +++ b/charts/cluster/examples/timescaledb.yaml @@ -0,0 +1,9 @@ +type: timescaledb +mode: standalone +version: + postgresql: "15.7" + timescaledb: "2.15" +cluster: + instances: 1 +backups: + enabled: false diff --git a/charts/cluster/templates/NOTES.txt b/charts/cluster/templates/NOTES.txt index ba44e95df..6a28fa592 100644 --- a/charts/cluster/templates/NOTES.txt +++ b/charts/cluster/templates/NOTES.txt @@ -37,7 +37,7 @@ Configuration {{- $redundancyColor = "ok" -}} {{- end }} -{{ $scheduledBackups := (first .Values.backups.scheduledBackups).name }} +{{- $scheduledBackups := (first .Values.backups.scheduledBackups).name -}} {{- range (rest .Values.backups.scheduledBackups) -}} {{ $scheduledBackups = printf "%s, %s" $scheduledBackups .name }} {{- end -}} @@ -54,12 +54,19 @@ Configuration {{- end -}} {{- end -}} +{{- $image := (include "cluster.image" .) | fromYaml -}} +{{- if $image.imageCatalogRef -}} + {{- $image = printf "%s: %s(%s)" $image.imageCatalogRef.kind $image.imageCatalogRef.name (include "cluster.postgresqlMajor" .) -}} +{{- else if $image.imageName -}} + {{- $image = $image.imageName -}} +{{- end }} + ╭───────────────────┬──────────────────────────────────────────────────────────╮ │ Configuration │ Value │ ┝━━━━━━━━━━━━━━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┥ │ Cluster mode │ {{ printf "%-56s" $mode }} │ │ Type │ {{ printf "%-56s" .Values.type }} │ -│ Image │ {{ include "cluster.color-info" (printf "%-56s" (include "cluster.imageName" .)) }} │ +│ Image │ {{ include "cluster.color-info" (printf "%-56s" $image) }} │ {{- if eq .Values.mode "recovery" }} │ Source │ {{ printf "%-56s" $source }} │ {{- end }} diff --git a/charts/cluster/templates/_bootstrap.tpl b/charts/cluster/templates/_bootstrap.tpl index 81665d2e2..87f27303f 100644 --- a/charts/cluster/templates/_bootstrap.tpl +++ b/charts/cluster/templates/_bootstrap.tpl @@ -3,7 +3,7 @@ bootstrap: initdb: {{- with .Values.cluster.initdb }} - {{- with (omit . "postInitApplicationSQL") }} + {{- with (omit . "postInitApplicationSQL" "postInitTemplateSQL") }} {{- . | toYaml | nindent 4 }} {{- end }} {{- end }} @@ -15,11 +15,32 @@ bootstrap: - CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder; {{- else if eq .Values.type "timescaledb" }} - CREATE EXTENSION IF NOT EXISTS timescaledb; + {{- else if eq .Values.type "paradedb" }} + - CREATE EXTENSION IF NOT EXISTS pg_search; + - CREATE EXTENSION IF NOT EXISTS pg_analytics; + - CREATE EXTENSION IF NOT EXISTS pg_ivm; + - CREATE EXTENSION IF NOT EXISTS vector; + - CREATE EXTENSION IF NOT EXISTS vectorscale; + - ALTER DATABASE "{{ default "app" .Values.cluster.initdb.database }}" SET search_path TO public,paradedb; {{- end }} {{- with .Values.cluster.initdb }} - {{- range .postInitApplicationSQL }} - {{- printf "- %s" . | nindent 6 }} - {{- end -}} + {{- range .postInitApplicationSQL }} + {{- printf "- %s" . | nindent 6 }} + {{- end -}} + {{- end }} + postInitTemplateSQL: + {{- if eq .Values.type "paradedb" }} + - CREATE EXTENSION IF NOT EXISTS pg_search; + - CREATE EXTENSION IF NOT EXISTS pg_analytics; + - CREATE EXTENSION IF NOT EXISTS pg_ivm; + - CREATE EXTENSION IF NOT EXISTS vector; + - CREATE EXTENSION IF NOT EXISTS vectorscale; + - ALTER DATABASE template1 SET search_path TO public,paradedb; + {{- end }} + {{- with .Values.cluster.initdb }} + {{- range .postInitTemplateSQL }} + {{- printf "- %s" . | nindent 6 }} + {{- end -}} {{- end -}} {{- else if eq .Values.mode "recovery" -}} bootstrap: diff --git a/charts/cluster/templates/_helpers.tpl b/charts/cluster/templates/_helpers.tpl index db3c253e5..b6f8a8f83 100644 --- a/charts/cluster/templates/_helpers.tpl +++ b/charts/cluster/templates/_helpers.tpl @@ -51,6 +51,20 @@ app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/part-of: cloudnative-pg {{- end }} +{{/* +Whether we need to use TimescaleDB defaults +*/}} +{{- define "cluster.useTimescaleDBDefaults" -}} +{{ and (eq .Values.type "timescaledb") .Values.imageCatalog.create (empty .Values.cluster.imageCatalogRef.name) (empty .Values.imageCatalog.images) (empty .Values.cluster.imageName) }} +{{- end -}} + +{{/* +Get the PostgreSQL major version from .Values.version.postgresql +*/}} +{{- define "cluster.postgresqlMajor" -}} +{{ index (regexSplit "\\." (toString .Values.version.postgresql) 2) 0 }} +{{- end -}} + {{/* Cluster Image Name If a custom imageName is available, use it, otherwise use the defaults based on the .Values.type @@ -59,12 +73,65 @@ If a custom imageName is available, use it, otherwise use the defaults based on {{- if .Values.cluster.imageName -}} {{- .Values.cluster.imageName -}} {{- else if eq .Values.type "postgresql" -}} - {{- "ghcr.io/cloudnative-pg/postgresql:15.2" -}} + {{- printf "ghcr.io/cloudnative-pg/postgresql:%s" .Values.version.postgresql -}} {{- else if eq .Values.type "postgis" -}} - {{- "ghcr.io/cloudnative-pg/postgis:14" -}} - {{- else if eq .Values.type "timescaledb" -}} - {{ fail "You need to provide your own cluster.imageName as an official timescaledb image doesn't exist yet." }} + {{- printf "ghcr.io/cloudnative-pg/postgis:%s-%s" .Values.version.postgresql .Values.version.postgis -}} + {{- else if eq .Values.type "paradedb" -}} + {{- printf "paradedb/paradedb:%s-v%s" .Values.version.postgresql .Values.version.paradedb -}} {{- else -}} {{ fail "Invalid cluster type!" }} {{- end }} {{- end -}} + +{{/* +Cluster Image +If imageCatalogRef defined, use it, otherwice calculate ordinary imageName. +*/}} +{{- define "cluster.image" }} +{{- if .Values.cluster.imageCatalogRef.name }} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + {{- toYaml .Values.cluster.imageCatalogRef | nindent 2 }} + major: {{ include "cluster.postgresqlMajor" . }} +{{- else if and .Values.imageCatalog.create (not (empty .Values.imageCatalog.images )) }} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + kind: ImageCatalog + name: {{ include "cluster.fullname" . }} + major: {{ include "cluster.postgresqlMajor" . }} +{{- else if eq (include "cluster.useTimescaleDBDefaults" .) "true" -}} +imageCatalogRef: + apiGroup: postgresql.cnpg.io + kind: ImageCatalog + name: {{ include "cluster.fullname" . }}-timescaledb-ha + major: {{ include "cluster.postgresqlMajor" . }} +{{- else }} +imageName: {{ include "cluster.imageName" . }} +{{- end }} +{{- end }} + +{{/* +Postgres UID +*/}} +{{- define "cluster.postgresUID" -}} + {{- if ge (int .Values.cluster.postgresUID) 0 -}} + {{- .Values.cluster.postgresUID }} + {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} + {{- 1000 -}} + {{- else -}} + {{- 26 -}} + {{- end -}} +{{- end -}} + +{{/* +Postgres GID +*/}} +{{- define "cluster.postgresGID" -}} + {{- if ge (int .Values.cluster.postgresGID) 0 -}} + {{- .Values.cluster.postgresGID }} + {{- else if and (eq (include "cluster.useTimescaleDBDefaults" .) "true") (eq .Values.type "timescaledb") -}} + {{- 1000 -}} + {{- else -}} + {{- 26 -}} + {{- end -}} +{{- end -}} diff --git a/charts/cluster/templates/cluster.yaml b/charts/cluster/templates/cluster.yaml index ba2b87643..fd78ce305 100644 --- a/charts/cluster/templates/cluster.yaml +++ b/charts/cluster/templates/cluster.yaml @@ -13,14 +13,14 @@ metadata: {{- end }} spec: instances: {{ .Values.cluster.instances }} - imageName: {{ include "cluster.imageName" . }} + {{- include "cluster.image" . | nindent 2 }} imagePullPolicy: {{ .Values.cluster.imagePullPolicy }} - {{- with .Values.cluster.imagePullSecrets}} + {{- with .Values.cluster.imagePullSecrets }} imagePullSecrets: {{- . | toYaml | nindent 4 }} {{- end }} - postgresUID: {{ .Values.cluster.postgresUID }} - postgresGID: {{ .Values.cluster.postgresGID }} + postgresUID: {{ include "cluster.postgresUID" . }} + postgresGID: {{ include "cluster.postgresGID" . }} storage: size: {{ .Values.cluster.storage.size }} storageClass: {{ .Values.cluster.storage.storageClass }} @@ -55,12 +55,21 @@ spec: shared_preload_libraries: {{- if eq .Values.type "timescaledb" }} - timescaledb + {{- else if eq .Values.type "paradedb" }} + - pg_search + - pg_analytics + - pg_cron + {{- end }} + {{- with .Values.cluster.postgresql.shared_preload_libraries }} + {{- toYaml . | nindent 6 }} {{- end }} {{- with .Values.cluster.postgresql }} parameters: {{- toYaml .parameters | nindent 6 }} pg_hba: {{- toYaml .pg_hba | nindent 6 }} + pg_ident: + {{- toYaml .pg_ident | nindent 6 }} {{ end }} managed: diff --git a/charts/cluster/templates/image-catalog-timescaledb-ha.yaml b/charts/cluster/templates/image-catalog-timescaledb-ha.yaml new file mode 100644 index 000000000..d611171e1 --- /dev/null +++ b/charts/cluster/templates/image-catalog-timescaledb-ha.yaml @@ -0,0 +1,18 @@ +{{- if eq (include "cluster.useTimescaleDBDefaults" .) "true" -}} +apiVersion: postgresql.cnpg.io/v1 +kind: ImageCatalog +metadata: + name: {{ include "cluster.fullname" . }}-timescaledb-ha +spec: + images: + - major: 12 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 13 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 14 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 15 + image: timescale/timescaledb-ha:pg15-ts{{ .Values.version.timescaledb }} + - major: 16 + image: timescale/timescaledb-ha:pg16-ts{{ .Values.version.timescaledb }} +{{ end }} diff --git a/charts/cluster/templates/image-catalog.yaml b/charts/cluster/templates/image-catalog.yaml new file mode 100644 index 000000000..6dc707222 --- /dev/null +++ b/charts/cluster/templates/image-catalog.yaml @@ -0,0 +1,12 @@ +{{ if and .Values.imageCatalog.create (not (empty .Values.imageCatalog.images )) }} +apiVersion: postgresql.cnpg.io/v1 +kind: ImageCatalog +metadata: + name: {{ include "cluster.fullname" . }} +spec: + images: + {{- range $image := .Values.imageCatalog.images }} + - image: {{ $image.image }} + major: {{ $image.major }} + {{- end }} +{{- end }} diff --git a/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup-assert.yaml new file mode 100644 index 000000000..9c0f3eb48 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup.yaml new file mode 100644 index 000000000..19d550162 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/00-minio_cleanup.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: minio-cleanup + image: minio/mc + command: ['sh', '-c'] + args: + - | + mc alias set myminio https://minio.minio.svc.cluster.local minio minio123 + mc rm --recursive --force myminio/mybucket/paradedb diff --git a/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster-assert.yaml new file mode 100644 index 000000000..8b243d630 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: paradedb-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster.yaml b/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster.yaml new file mode 100644 index 000000000..ed0c19c8c --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/01-paradedb_cluster.yaml @@ -0,0 +1,26 @@ +type: paradedb +mode: standalone +cluster: + instances: 2 + storage: + size: 256Mi + +backups: + enabled: true + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/paradedb/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write-assert.yaml new file mode 100644 index 000000000..3fac848be --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: paradedb-write +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write.yaml b/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write.yaml new file mode 100644 index 000000000..a2b669e5c --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/02-paradedb_write.yaml @@ -0,0 +1,33 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: paradedb-write +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: paradedb-cluster-app + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + psql "$DB_URI" <<-EOSQL + CALL paradedb.create_bm25_test_table( schema_name => 'public', table_name => 'mock_items' ); + CALL paradedb.create_bm25( + index_name => 'search_idx', + schema_name => 'public', + table_name => 'mock_items', + key_field => 'id', + text_fields => paradedb.field('description', tokenizer => paradedb.tokenizer('en_stem')) || + paradedb.field('category'), + numeric_fields => paradedb.field('rating') + ); + EOSQL diff --git a/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test-assert.yaml new file mode 100644 index 000000000..678c11c9b --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: paradedb-test +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test.yaml b/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test.yaml new file mode 100644 index 000000000..52a489df5 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/03-paradedb_test.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: paradedb-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: paradedb-cluster-app + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + RESULT=$(psql "$DB_URI" -t) <<-EOSQL + SELECT description + FROM search_idx.search('description:"bluetooth speaker"~1'); + EOSQL + echo -$RESULT- + test "$RESULT" = " Bluetooth-enabled speaker" diff --git a/charts/cluster/test/paradedb-minio-backup-restore/04-data_write-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/04-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/04-data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/04-data_write.yaml b/charts/cluster/test/paradedb-minio-backup-restore/04-data_write.yaml new file mode 100644 index 000000000..646c7e6de --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/04-data_write.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: configmap-creator-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: configmap-creator +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: configmap-creator-binding +subjects: +- kind: ServiceAccount + name: configmap-creator-sa +roleRef: + kind: Role + name: configmap-creator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + serviceAccountName: configmap-creator-sa + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: paradedb-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client kubectl coreutils + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" + sleep 5 + DATE_NO_BAD_TABLE=$(date --rfc-3339=ns) + kubectl create configmap date-no-bad-table --from-literal=date="$DATE_NO_BAD_TABLE" + sleep 5 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/05-backup.yaml b/charts/cluster/test/paradedb-minio-backup-restore/05-backup.yaml new file mode 100644 index 000000000..543c9c110 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/05-backup.yaml @@ -0,0 +1,8 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + method: barmanObjectStore + cluster: + name: paradedb-cluster diff --git a/charts/cluster/test/paradedb-minio-backup-restore/05-backup_completed-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/05-backup_completed-assert.yaml new file mode 100644 index 000000000..013b0aad5 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/05-backup_completed-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: paradedb-cluster + method: barmanObjectStore +status: + phase: completed diff --git a/charts/cluster/test/paradedb-minio-backup-restore/05-backup_running-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/05-backup_running-assert.yaml new file mode 100644 index 000000000..6610deb8f --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/05-backup_running-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: paradedb-cluster + method: barmanObjectStore +status: + phase: running diff --git a/charts/cluster/test/paradedb-minio-backup-restore/05-checkpoint.yaml b/charts/cluster/test/paradedb-minio-backup-restore/05-checkpoint.yaml new file mode 100644 index 000000000..bf886cc7b --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/05-checkpoint.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: backup-checkpoint +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: create-checkpoint + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: paradedb-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + END_TIME=$(( $(date +%s) + 30 )) + while [ $(date +%s) -lt $END_TIME ]; do + psql "$DB_URI" -c "SELECT pg_switch_wal();CHECKPOINT;" + sleep 5 + done diff --git a/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write-assert.yaml new file mode 100644 index 000000000..ad9be77a7 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write.yaml b/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write.yaml new file mode 100644 index 000000000..8490de2c9 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/06-post_backup_data_write.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: paradedb-cluster-superuser + key: uri + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mybadtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml new file mode 100644 index 000000000..2b6b9651f --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: recovery-backup-pitr-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml new file mode 100644 index 000000000..8031e3c2b --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml @@ -0,0 +1,48 @@ +type: paradedb +mode: recovery + +cluster: + instances: 2 + storage: + size: 256Mi + +recovery: + method: backup + backupName: "post-init-backup" + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/paradedb/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" + +backups: + enabled: true + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/paradedb/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/paradedb-minio-backup-restore/08-data_test-assert.yaml b/charts/cluster/test/paradedb-minio-backup-restore/08-data_test-assert.yaml new file mode 100644 index 000000000..6f14d5f23 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/08-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +status: + succeeded: 1 diff --git a/charts/cluster/test/paradedb-minio-backup-restore/08-data_test.yaml b/charts/cluster/test/paradedb-minio-backup-restore/08-data_test.yaml new file mode 100644 index 000000000..5fb4faf39 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/08-data_test.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: recovery-backup-pitr-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + set -e + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" + echo "Good table exists" + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mybadtable$$)' --csv -q 2>/dev/null)" = "f" + echo "Bad table does not exist" diff --git a/charts/cluster/test/paradedb-minio-backup-restore/chainsaw-test.yaml b/charts/cluster/test/paradedb-minio-backup-restore/chainsaw-test.yaml new file mode 100644 index 000000000..7af6da771 --- /dev/null +++ b/charts/cluster/test/paradedb-minio-backup-restore/chainsaw-test.yaml @@ -0,0 +1,144 @@ +## +# This test sets up a ParadeDB cluster with MinIO backups and ensures that ParadeDB extensions are installed and +# PITR recovery is enabled and working. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: paradedb +spec: + timeouts: + apply: 1s + assert: 5m + cleanup: 1m + steps: + - name: Clear the MinIO bucket + try: + - apply: + file: ./00-minio_cleanup.yaml + - assert: + file: ./00-minio_cleanup-assert.yaml + - name: Install a standalone ParadeDB cluster + try: + - script: + content: | + kubectl -n $NAMESPACE create secret generic kube-root-ca.crt --from-literal=ca.crt="$(kubectl -n kube-system get configmaps kube-root-ca.crt -o jsonpath='{.data.ca\.crt}')" --dry-run=client -o yaml | kubectl apply -f - + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-paradedb_cluster.yaml \ + --wait \ + paradedb ../../ + - assert: + file: ./01-paradedb_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=paradedb-cluster + - name: Initialize with ParadeDB sample data + timeouts: + apply: 1s + assert: 10s + try: + - apply: + file: ./02-paradedb_write.yaml + - assert: + file: ./02-paradedb_write-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-write + - name: Verify ParadeDB extensions are installed + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 03-paradedb_test.yaml + - assert: + file: 03-paradedb_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Write some data to the cluster + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 04-data_write.yaml + - assert: + file: 04-data_write-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Create a backup + try: + - apply: + file: ./05-backup.yaml + - assert: + file: ./05-backup_running-assert.yaml + - apply: + file: ./05-checkpoint.yaml + - assert: + file: ./05-backup_completed-assert.yaml + - name: Write more data to the database after the backup + try: + - apply: + file: ./06-post_backup_data_write.yaml + - assert: + file: ./06-post_backup_data_write-assert.yaml + timeouts: + apply: 1s + assert: 10m + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Backup + - name: Create a recovery cluster from backup with a PITR target + try: + - script: + content: | + DATE_NO_BAD_TABLE=$(kubectl -n $NAMESPACE get configmap date-no-bad-table -o 'jsonpath={.data.date}') + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./07-recovery_backup_pitr_cluster.yaml \ + --set recovery.pitrTarget.time="$DATE_NO_BAD_TABLE" \ + --wait \ + recovery-backup-pitr ../../ + - assert: + file: ./07-recovery_backup_pitr_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=paradedb-cluster + - name: Verify the pre-backup data on the recovery cluster exists but not the post-backup data + try: + - apply: + file: 08-data_test.yaml + - assert: + file: 08-data_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - podLogs: + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE paradedb diff --git a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml index c26a44faf..5f5c62a68 100644 --- a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml +++ b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster-assert.yaml @@ -17,6 +17,10 @@ spec: max_connections: "42" pg_hba: - host all 1.2.3.4/32 trust + pg_ident: + - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: + - pgaudit bootstrap: initdb: database: mydb diff --git a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml index 96a3d429c..c86a5dceb 100644 --- a/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml +++ b/charts/cluster/test/postgresql-cluster-configuration/01-non_default_configuration_cluster.yaml @@ -56,6 +56,10 @@ cluster: max_connections: "42" pg_hba: - host all 1.2.3.4/32 trust + pg_ident: + - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: + - pgaudit initdb: database: mydb owner: dante diff --git a/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup.yaml index 3c06f4159..97cfc7389 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/00-minio_cleanup.yaml @@ -13,4 +13,4 @@ spec: args: - | mc alias set myminio https://minio.minio.svc.cluster.local minio minio123 - mc rm --recursive --force myminio/mybucket + mc rm --recursive --force myminio/mybucket/postgresql-minio-backup-restore diff --git a/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml index efebd75ec..d55832170 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/01-standalone_cluster.yaml @@ -18,7 +18,7 @@ backups: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v1" accessKey: "minio" secretKey: "minio123" region: "local" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml b/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml index 8e231df66..e674d8b53 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/02-data_write.yaml @@ -18,6 +18,6 @@ spec: command: ['sh', '-c'] args: - | - apk --no-cache add postgresql-client kubectl + apk --no-cache add postgresql-client DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) psql "$DB_URI" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml b/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml index 0571dbdd6..2e56595de 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/04-post_backup_data_write.yaml @@ -49,7 +49,7 @@ spec: command: ['sh', '-c'] args: - | - apk --no-cache add postgresql-client kubectl + apk --no-cache add postgresql-client kubectl coreutils DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) DATE_NO_BAD_TABLE=$(date --rfc-3339=ns) sleep 30 diff --git a/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml index 7279e1e81..449e524f8 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/05-recovery_backup_cluster.yaml @@ -19,7 +19,7 @@ recovery: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v1" accessKey: "minio" secretKey: "minio123" region: "local" @@ -39,7 +39,7 @@ backups: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v2" accessKey: "minio" secretKey: "minio123" region: "local" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml index d6dafdc64..230d5ba1d 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/07-recovery_object_store_cluster.yaml @@ -19,7 +19,7 @@ recovery: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v1" accessKey: "minio" secretKey: "minio123" region: "local" @@ -39,7 +39,7 @@ backups: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v2" accessKey: "minio" secretKey: "minio123" region: "local" diff --git a/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml index 3fa2bbd03..449e524f8 100644 --- a/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml +++ b/charts/cluster/test/postgresql-minio-backup-restore/09-recovery_backup_pitr_cluster.yaml @@ -19,7 +19,7 @@ recovery: encryption: "" s3: bucket: "mybucket" - path: "/v1" + path: "/postgresql-minio-backup-restore/v1" accessKey: "minio" secretKey: "minio123" region: "local" @@ -39,7 +39,7 @@ backups: encryption: "" s3: bucket: "mybucket" - path: "/v2" + path: "/postgresql-minio-backup-restore/v2" accessKey: "minio" secretKey: "minio123" region: "local" diff --git a/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml index fc5f0c8c9..cc5a743ad 100644 --- a/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml +++ b/charts/cluster/test/postgresql-pg_basebackup/01-data_write.yaml @@ -25,6 +25,6 @@ spec: command: ['sh', '-c'] args: - | - apk --no-cache add postgresql-client kubectl + apk --no-cache add postgresql-client psql "$DB_URI" -c "CREATE DATABASE mygooddb;" psql "$DB_URI/mygooddb" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup-assert.yaml new file mode 100644 index 000000000..9c0f3eb48 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup.yaml b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup.yaml new file mode 100644 index 000000000..ce71b1ef7 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/00-minio_cleanup.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: minio-cleanup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: minio-cleanup + image: minio/mc + command: ['sh', '-c'] + args: + - | + mc alias set myminio https://minio.minio.svc.cluster.local minio minio123 + mc rm --recursive --force myminio/mybucket/timescale diff --git a/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml new file mode 100644 index 000000000..3bbd2f8fe --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: timescale-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml new file mode 100644 index 000000000..f84117fe0 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/01-timescale_cluster.yaml @@ -0,0 +1,28 @@ +type: timescaledb +mode: standalone + +cluster: + instances: 2 + storage: + size: 256Mi + +backups: + enabled: true + + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/timescale/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml new file mode 100644 index 000000000..aa63a21c9 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: timescale-test +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml new file mode 100644 index 000000000..9b7581f96 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/03-timescale_test.yaml @@ -0,0 +1,22 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: timescale-test +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: timescale-cluster-app + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM pg_extension WHERE extname = '\''timescaledb'\'')' --csv -q 2>/dev/null)" = "t" \ No newline at end of file diff --git a/charts/cluster/test/timescale-minio-backup-restore/04-data_write-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/04-data_write-assert.yaml new file mode 100644 index 000000000..831f963d9 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/04-data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/04-data_write.yaml b/charts/cluster/test/timescale-minio-backup-restore/04-data_write.yaml new file mode 100644 index 000000000..b827de143 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/04-data_write.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: configmap-creator-sa +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: configmap-creator +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: configmap-creator-binding +subjects: +- kind: ServiceAccount + name: configmap-creator-sa +roleRef: + kind: Role + name: configmap-creator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write +spec: + template: + spec: + serviceAccountName: configmap-creator-sa + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: timescale-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client kubectl coreutils + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mygoodtable (id serial PRIMARY KEY);" + sleep 5 + DATE_NO_BAD_TABLE=$(date --rfc-3339=ns) + kubectl create configmap date-no-bad-table --from-literal=date="$DATE_NO_BAD_TABLE" + sleep 5 diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-backup.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup.yaml new file mode 100644 index 000000000..be5e4b181 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/05-backup.yaml @@ -0,0 +1,8 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + method: barmanObjectStore + cluster: + name: timescale-cluster diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-backup_completed-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup_completed-assert.yaml new file mode 100644 index 000000000..040b1a49e --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/05-backup_completed-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: timescale-cluster + method: barmanObjectStore +status: + phase: completed diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-backup_running-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-backup_running-assert.yaml new file mode 100644 index 000000000..dc35727a0 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/05-backup_running-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Backup +metadata: + name: post-init-backup +spec: + cluster: + name: timescale-cluster + method: barmanObjectStore +status: + phase: running diff --git a/charts/cluster/test/timescale-minio-backup-restore/05-checkpoint.yaml b/charts/cluster/test/timescale-minio-backup-restore/05-checkpoint.yaml new file mode 100644 index 000000000..3ba7fc727 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/05-checkpoint.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: backup-checkpoint +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: create-checkpoint + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: timescale-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + END_TIME=$(( $(date +%s) + 30 )) + while [ $(date +%s) -lt $END_TIME ]; do + psql "$DB_URI" -c "SELECT pg_switch_wal();CHECKPOINT;" + sleep 5 + done diff --git a/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write-assert.yaml new file mode 100644 index 000000000..ad9be77a7 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write.yaml b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write.yaml new file mode 100644 index 000000000..8585b247d --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/06-post_backup_data_write.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-write-post-backup +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-write + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: timescale-cluster-superuser + key: uri + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + psql "$DB_URI" -c "CREATE TABLE mybadtable (id serial PRIMARY KEY);" diff --git a/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml new file mode 100644 index 000000000..2b6b9651f --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: recovery-backup-pitr-cluster +status: + readyInstances: 2 diff --git a/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml new file mode 100644 index 000000000..7e9c38f55 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/07-recovery_backup_pitr_cluster.yaml @@ -0,0 +1,48 @@ +type: timescaledb +mode: recovery + +cluster: + instances: 2 + storage: + size: 256Mi + +recovery: + method: backup + backupName: "post-init-backup" + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/timescale/v1" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" + +backups: + enabled: true + provider: s3 + endpointURL: "https://minio.minio.svc.cluster.local" + endpointCA: + name: kube-root-ca.crt + key: ca.crt + wal: + encryption: "" + data: + encryption: "" + s3: + bucket: "mybucket" + path: "/timescale/v2" + accessKey: "minio" + secretKey: "minio123" + region: "local" + scheduledBackups: [] + retentionPolicy: "30d" diff --git a/charts/cluster/test/timescale-minio-backup-restore/08-data_test-assert.yaml b/charts/cluster/test/timescale-minio-backup-restore/08-data_test-assert.yaml new file mode 100644 index 000000000..6f14d5f23 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/08-data_test-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +status: + succeeded: 1 diff --git a/charts/cluster/test/timescale-minio-backup-restore/08-data_test.yaml b/charts/cluster/test/timescale-minio-backup-restore/08-data_test.yaml new file mode 100644 index 000000000..5fb4faf39 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/08-data_test.yaml @@ -0,0 +1,27 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: data-test-backup-pitr +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: data-test + env: + - name: DB_URI + valueFrom: + secretKeyRef: + name: recovery-backup-pitr-cluster-superuser + key: uri + image: alpine:3.19 + command: ['sh', '-c'] + args: + - | + apk --no-cache add postgresql-client + DB_URI=$(echo $DB_URI | sed "s|/\*|/|" ) + set -e + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mygoodtable$$)' --csv -q 2>/dev/null)" = "t" + echo "Good table exists" + test "$(psql $DB_URI -t -c 'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $$mybadtable$$)' --csv -q 2>/dev/null)" = "f" + echo "Bad table does not exist" diff --git a/charts/cluster/test/timescale-minio-backup-restore/chainsaw-test.yaml b/charts/cluster/test/timescale-minio-backup-restore/chainsaw-test.yaml new file mode 100644 index 000000000..496153398 --- /dev/null +++ b/charts/cluster/test/timescale-minio-backup-restore/chainsaw-test.yaml @@ -0,0 +1,129 @@ +## +# This test sets up a timescale cluster with MinIO backups and ensured that timescale extensions are installed and +# PITR recovery is enabled and working. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: timescale +spec: + timeouts: + apply: 1s + assert: 5m + cleanup: 1m + steps: + - name: Clear the MinIO bucket + try: + - apply: + file: ./00-minio_cleanup.yaml + - assert: + file: ./00-minio_cleanup-assert.yaml + - name: Install a standalone timescale cluster + try: + - script: + content: | + kubectl -n $NAMESPACE create secret generic kube-root-ca.crt --from-literal=ca.crt="$(kubectl -n kube-system get configmaps kube-root-ca.crt -o jsonpath='{.data.ca\.crt}')" --dry-run=client -o yaml | kubectl apply -f - + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./01-timescale_cluster.yaml \ + --wait \ + timescale ../../ + - assert: + file: ./01-timescale_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=timescale-cluster + - name: Verify timescale extensions are installed + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 03-timescale_test.yaml + - assert: + file: 03-timescale_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Write some data to the cluster + timeouts: + apply: 1s + assert: 30s + try: + - apply: + file: 04-data_write.yaml + - assert: + file: 04-data_write-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + - podLogs: + selector: batch.kubernetes.io/job-name=data-test + - name: Create a backup + try: + - apply: + file: ./05-backup.yaml + - assert: + file: ./05-backup_running-assert.yaml + - apply: + file: ./05-checkpoint.yaml + - assert: + file: ./05-backup_completed-assert.yaml + - name: Write more data to the database after the backup + try: + - apply: + file: ./06-post_backup_data_write.yaml + - assert: + file: ./06-post_backup_data_write-assert.yaml + timeouts: + apply: 1s + assert: 10m + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Backup + - name: Create a recovery cluster from backup with a PITR target + try: + - script: + content: | + DATE_NO_BAD_TABLE=$(kubectl -n $NAMESPACE get configmap date-no-bad-table -o 'jsonpath={.data.date}') + helm upgrade \ + --install \ + --namespace $NAMESPACE \ + --values ./07-recovery_backup_pitr_cluster.yaml \ + --set recovery.pitrTarget.time="$DATE_NO_BAD_TABLE" \ + --wait \ + recovery-backup-pitr ../../ + - assert: + file: ./07-recovery_backup_pitr_cluster-assert.yaml + catch: + - describe: + apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + - podLogs: + selector: cnpg.io/cluster=recovery-backup-pitr-cluster + - name: Verify the pre-backup data on the recovery cluster exists but not the post-backup data + try: + - apply: + file: 08-data_test.yaml + - assert: + file: 08-data_test-assert.yaml + catch: + - describe: + apiVersion: batch/v1 + kind: Job + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - podLogs: + selector: batch.kubernetes.io/job-name=data-test-backup-pitr + - name: Cleanup + try: + - script: + content: | + helm uninstall --namespace $NAMESPACE timescale diff --git a/charts/cluster/values.schema.json b/charts/cluster/values.schema.json index 38ca30a04..6b81ab0ac 100644 --- a/charts/cluster/values.schema.json +++ b/charts/cluster/values.schema.json @@ -187,6 +187,9 @@ "enableSuperuserAccess": { "type": "boolean" }, + "imageCatalogRef": { + "type": "object" + }, "imageName": { "type": "string" }, @@ -249,6 +252,12 @@ }, "pg_hba": { "type": "array" + }, + "pg_ident": { + "type": "array" + }, + "shared_preload_libraries": { + "type": "array" } } }, @@ -300,6 +309,17 @@ "fullnameOverride": { "type": "string" }, + "imageCatalog": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "images": { + "type": "array" + } + } + }, "mode": { "type": "string" }, @@ -564,6 +584,23 @@ }, "type": { "type": "string" + }, + "version": { + "type": "object", + "properties": { + "paradedb": { + "type": "string" + }, + "postgis": { + "type": "string" + }, + "postgresql": { + "type": "string" + }, + "timescaledb": { + "type": "string" + } + } } } } diff --git a/charts/cluster/values.yaml b/charts/cluster/values.yaml index d8bb3b22d..1a68b953f 100644 --- a/charts/cluster/values.yaml +++ b/charts/cluster/values.yaml @@ -7,8 +7,20 @@ fullnameOverride: "" # -- Type of the CNPG database. Available types: # * `postgresql` # * `postgis` +# * `timescaledb` +# * `paradedb` type: postgresql +version: + # -- PostgreSQL major version to use + postgresql: "16" + # -- If using TimescaleDB, specify the version + timescaledb: "2.15" + # -- If using PostGIS, specify the version + postgis: "3.4" + # -- If using ParadeDB, specify the version + paradedb: "0.9.3" + ### # -- Cluster mode of operation. Available modes: # * `standalone` - default mode. Creates new or updates an existing CNPG cluster. @@ -123,6 +135,11 @@ cluster: # :@sha256: imageName: "" # Default value depends on type (postgresql/postgis/timescaledb) + # -- Reference to `ImageCatalog` of `ClusterImageCatalog`, if specified takes precedence over `cluster.imageName` + imageCatalogRef: {} + # kind: ImageCatalog + # name: postgresql + # -- Image pull policy. One of Always, Never or IfNotPresent. If not defined, it defaults to IfNotPresent. Cannot be updated. # More info: https://kubernetes.io/docs/concepts/containers/images#updating-images imagePullPolicy: IfNotPresent @@ -141,10 +158,10 @@ cluster: storageClass: "" # -- The UID of the postgres user inside the image, defaults to 26 - postgresUID: 26 + postgresUID: -1 # -- The GID of the postgres user inside the image, defaults to 26 - postgresGID: 26 + postgresGID: -1 # -- Resources requirements of every generated Pod. # Please refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more information. @@ -231,6 +248,10 @@ cluster: # -- PostgreSQL Host Based Authentication rules (lines to be appended to the pg_hba.conf file) pg_hba: [] # - host all all 10.244.0.0/16 md5 + pg_ident: [] + # - mymap /^(.*)@mydomain\.com$ \1 + shared_preload_libraries: [] + # - pgaudit # -- BootstrapInitDB is the configuration of the bootstrap process when initdb is used. # See: https://cloudnative-pg.io/documentation/current/bootstrap/ @@ -328,6 +349,13 @@ backups: # -- Retention policy for backups retentionPolicy: "30d" +imageCatalog: + # -- Whether to provision an image catalog. If imageCatalog.images is empty this option will be ignored. + create: true + # -- List of images to be provisioned in an image catalog. + images: [] + # - image: ghcr.io/your_repo/your_image:your_tag + # major: 16 pooler: # -- Whether to enable PgBouncer