Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ on:
branches:
- main

env:
# Temporary: the chart's default Postgres image (18-spock5-standard)
# currently resolves to a minor version that predates the
# output_plugin_libraries GUC (PLAT-722). Point tests at the same tag
# from pgEdge's internal image repo, which already has a patched
# version, so CI can pass while the public tag catches up. Remove this
# (and its two references below) once pgEdge/postgres-images#28 ships
# and the public tag moves.
TEST_POSTGRES_IMAGE: ghcr.io/pgedge/pgedge-postgres-internal:18-spock5-standard
Comment thread
coderabbitai[bot] marked this conversation as resolved.

jobs:
unit-tests:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -73,7 +83,7 @@ jobs:
run: make -C test kind-load-image

- name: Chart install test
run: ct install --chart-dirs . --charts . --target-branch main --skip-missing-values --github-groups --helm-extra-set-args "--set pgEdge.initSpockImageName=pgedge-helm-utils:dev"
run: ct install --chart-dirs . --charts . --target-branch main --skip-missing-values --github-groups --helm-extra-set-args "--set pgEdge.initSpockImageName=pgedge-helm-utils:dev --set pgEdge.clusterSpec.imageName=${{ env.TEST_POSTGRES_IMAGE }}"

- name: Teardown
if: always()
Expand Down Expand Up @@ -120,7 +130,7 @@ jobs:
run: make -C test kind-load-image

- name: Integration tests
run: make -C test test-integration KUBECONTEXT=kind-pgedge-test INIT_SPOCK_IMAGE=pgedge-helm-utils:dev TIMEOUT=15m
run: make -C test test-integration KUBECONTEXT=kind-pgedge-test INIT_SPOCK_IMAGE=pgedge-helm-utils:dev POSTGRES_IMAGE=${{ env.TEST_POSTGRES_IMAGE }} TIMEOUT=15m

- name: Collect debug artifacts on failure
if: failure()
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ You can run `make gen-docs` after updating the templates to generate the associa
|-----|------|---------|-------------|
| pgEdge.adminUser | string | `"admin"` | The name of the admin role used for database management and init-spock connections. |
| pgEdge.appName | string | `"pgedge"` | Determines the name of resources in the pgEdge cluster. Many other values are derived from this name, so it must be less than or equal to 26 characters in length. |
| pgEdge.clusterSpec | object | `{"bootstrap":{"initdb":{"database":"app","encoding":"UTF8","owner":"app","postInitApplicationSQL":["CREATE EXTENSION spock;"],"postInitSQL":[],"postInitTemplateSQL":[]}},"certificates":{"clientCASecret":"client-ca-key-pair","replicationTLSSecret":"streaming-replica-client-cert"},"imageName":"ghcr.io/pgedge/pgedge-postgres:18-spock5-standard","imagePullPolicy":"Always","instances":1,"managed":{"roles":[{"comment":"Admin role","ensure":"present","login":true,"name":"admin","superuser":true}]},"postgresql":{"parameters":{"checkpoint_completion_target":"0.9","checkpoint_timeout":"15min","dynamic_shared_memory_type":"posix","hot_standby_feedback":"on","spock.allow_ddl_from_functions":"on","spock.conflict_log_level":"DEBUG","spock.conflict_resolution":"last_update_wins","spock.enable_ddl_replication":"on","spock.include_ddl_repset":"on","spock.save_resolutions":"on","track_commit_timestamp":"on","track_io_timing":"on","wal_level":"logical","wal_sender_timeout":"5s"},"pg_hba":["hostssl app pgedge 0.0.0.0/0 cert","hostssl app admin 0.0.0.0/0 cert","hostssl app app 0.0.0.0/0 cert","hostssl all streaming_replica all cert map=cnpg_streaming_replica"],"pg_ident":["local postgres admin","local postgres app"],"shared_preload_libraries":["pg_stat_statements","snowflake","spock"]},"projectedVolumeTemplate":{"sources":[{"secret":{"items":[{"key":"tls.crt","mode":384,"path":"pgedge/certificates/tls.crt"},{"key":"tls.key","mode":384,"path":"pgedge/certificates/tls.key"},{"key":"ca.crt","mode":384,"path":"pgedge/certificates/ca.crt"}],"name":"pgedge-client-cert"}}]}}` | Default CloudNativePG Cluster specification applied to all nodes, which can be overridden on a per-node basis using the `clusterSpec` field in each node definition. |
| pgEdge.clusterSpec | object | `{"bootstrap":{"initdb":{"database":"app","encoding":"UTF8","owner":"app","postInitApplicationSQL":["CREATE EXTENSION spock;"],"postInitSQL":[],"postInitTemplateSQL":[]}},"certificates":{"clientCASecret":"client-ca-key-pair","replicationTLSSecret":"streaming-replica-client-cert"},"imageName":"ghcr.io/pgedge/pgedge-postgres:18-spock5-standard","imagePullPolicy":"Always","instances":1,"managed":{"roles":[{"comment":"Admin role","ensure":"present","login":true,"name":"admin","superuser":true}]},"postgresql":{"parameters":{"checkpoint_completion_target":"0.9","checkpoint_timeout":"15min","dynamic_shared_memory_type":"posix","hot_standby_feedback":"on","output_plugin_libraries":"pgoutput, test_decoding, spock_output","spock.allow_ddl_from_functions":"on","spock.conflict_log_level":"DEBUG","spock.conflict_resolution":"last_update_wins","spock.enable_ddl_replication":"on","spock.include_ddl_repset":"on","spock.save_resolutions":"on","track_commit_timestamp":"on","track_io_timing":"on","wal_level":"logical","wal_sender_timeout":"5s"},"pg_hba":["hostssl app pgedge 0.0.0.0/0 cert","hostssl app admin 0.0.0.0/0 cert","hostssl app app 0.0.0.0/0 cert","hostssl all streaming_replica all cert map=cnpg_streaming_replica"],"pg_ident":["local postgres admin","local postgres app"],"shared_preload_libraries":["pg_stat_statements","snowflake","spock"]},"projectedVolumeTemplate":{"sources":[{"secret":{"items":[{"key":"tls.crt","mode":384,"path":"pgedge/certificates/tls.crt"},{"key":"tls.key","mode":384,"path":"pgedge/certificates/tls.key"},{"key":"ca.crt","mode":384,"path":"pgedge/certificates/ca.crt"}],"name":"pgedge-client-cert"}}]}}` | Default CloudNativePG Cluster specification applied to all nodes, which can be overridden on a per-node basis using the `clusterSpec` field in each node definition. |
| pgEdge.externalNodes | list | `[]` | Configuration for nodes that are part of the pgEdge cluster, but managed externally to this Helm chart. This can be leveraged for multi-cluster deployments or to wire up existing CloudNativePG Clusters to a pgEdge cluster. |
| pgEdge.extraResources | list | `[]` | Array of extra Kubernetes resources to deploy alongside pgEdge (evaluated as templates). Useful for deploying NetworkPolicies, PodMonitors, ConfigMaps, etc. |
| pgEdge.initSpock | bool | `true` | Whether or not to run the init-spock job to initialize the pgEdge nodes and subscriptions In multi-cluster deployments, this should only be set to true on the last cluster to be deployed. |
Expand Down
3 changes: 3 additions & 0 deletions changes/unreleased/Added-20260812-212552.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Added a default for `postgresql.parameters.output_plugin_libraries` (`pgoutput, test_decoding, spock_output`) so Spock keeps replicating once a node is running PostgreSQL 16.15, 17.11, 18.6, or newer, which otherwise reject `spock_output` by default. Documented overriding it to `null` at the chart-wide level for clusters pinned to an older minor (#64)
time: 2026-08-12T21:25:52.152871+05:00
28 changes: 27 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,32 @@ By default, `shared_preload_libraries` contains `pg_stat_statements`, `snowflake

Always include `spock` in `shared_preload_libraries`, as it is required for core functionality provided by this chart. This chart will call `CREATE EXTENSION` for spock when initializing each CloudNativePG Cluster.

### output_plugin_libraries and older PostgreSQL minor versions

PostgreSQL 16.15, 17.11, 18.6, and newer minor versions add a new `output_plugin_libraries` allow-list that gates which logical decoding output plugins a server accepts. Its built-in default is `pgoutput, test_decoding`, which does not include `spock_output`. Without it on the list, Spock cannot create its replication slot and replication stops on the provider node.

This chart sets `postgresql.parameters.output_plugin_libraries` to `pgoutput, test_decoding, spock_output` by default, so that Spock keeps working once a node is running one of the patched minor versions. Since the chart defaults to a mutable image tag, a fresh install picks up a patched version automatically.

!!! warning

`output_plugin_libraries` is a core PostgreSQL setting, not an extension GUC. A minor version older than 16.15 / 17.11 / 18.6 does not recognize it at all, and refuses to start with `unrecognized configuration parameter "output_plugin_libraries"` if it is set to any value, including an empty one. Setting this parameter is only safe once every node in your cluster is on a patched minor version.

If you pin `clusterSpec.imageName` to a minor version older than the patched ones, override the parameter to `null` at the chart-wide level in your values file rather than removing the line:

```yaml
pgEdge:
clusterSpec:
postgresql:
parameters:
output_plugin_libraries: null
```

A YAML value of `null` here deletes the corresponding key before Helm ever renders the manifest, so the parameter is omitted entirely rather than sent through as an empty string. If you need additional output plugins beyond `spock_output`, override the parameter with the full list you need instead of unsetting it.

!!! warning

This `null` override only works at the chart-wide `pgEdge.clusterSpec` level. Setting it to `null` on a single node's `clusterSpec` does not omit the parameter: that override is merged onto the chart-wide default with Sprig's `mergeOverwrite` rather than through Helm's own values merging, and Kubernetes stores a `null` value for a `map[string]string` entry as an empty string rather than dropping the key. An empty value is still rejected exactly like any other value on a PostgreSQL version that predates this GUC. There is currently no values-only way to exempt a single node from this default while keeping it for the rest of the cluster — pin every node's image consistently instead.

## Values reference

You can customize this Helm chart by specifying configuration parameters in your `values.yaml` file.
Expand All @@ -229,7 +255,7 @@ The following table lists all available options and their descriptions.
|-----|------|---------|-------------|
| pgEdge.adminUser | string | `"admin"` | The name of the admin role used for database management and init-spock connections. |
| pgEdge.appName | string | `"pgedge"` | Determines the name of resources in the pgEdge cluster. Many other values are derived from this name, so it must be less than or equal to 26 characters in length. |
| pgEdge.clusterSpec | object | `{"bootstrap":{"initdb":{"database":"app","encoding":"UTF8","owner":"app","postInitApplicationSQL":["CREATE EXTENSION spock;"],"postInitSQL":[],"postInitTemplateSQL":[]}},"certificates":{"clientCASecret":"client-ca-key-pair","replicationTLSSecret":"streaming-replica-client-cert"},"imageName":"ghcr.io/pgedge/pgedge-postgres:18-spock5-standard","imagePullPolicy":"Always","instances":1,"managed":{"roles":[{"comment":"Admin role","ensure":"present","login":true,"name":"admin","superuser":true}]},"postgresql":{"parameters":{"checkpoint_completion_target":"0.9","checkpoint_timeout":"15min","dynamic_shared_memory_type":"posix","hot_standby_feedback":"on","spock.allow_ddl_from_functions":"on","spock.conflict_log_level":"DEBUG","spock.conflict_resolution":"last_update_wins","spock.enable_ddl_replication":"on","spock.include_ddl_repset":"on","spock.save_resolutions":"on","track_commit_timestamp":"on","track_io_timing":"on","wal_level":"logical","wal_sender_timeout":"5s"},"pg_hba":["hostssl app pgedge 0.0.0.0/0 cert","hostssl app admin 0.0.0.0/0 cert","hostssl app app 0.0.0.0/0 cert","hostssl all streaming_replica all cert map=cnpg_streaming_replica"],"pg_ident":["local postgres admin","local postgres app"],"shared_preload_libraries":["pg_stat_statements","snowflake","spock"]},"projectedVolumeTemplate":{"sources":[{"secret":{"items":[{"key":"tls.crt","mode":384,"path":"pgedge/certificates/tls.crt"},{"key":"tls.key","mode":384,"path":"pgedge/certificates/tls.key"},{"key":"ca.crt","mode":384,"path":"pgedge/certificates/ca.crt"}],"name":"pgedge-client-cert"}}]}}` | Default CloudNativePG Cluster specification applied to all nodes, which can be overridden on a per-node basis using the `clusterSpec` field in each node definition. |
| pgEdge.clusterSpec | object | `{"bootstrap":{"initdb":{"database":"app","encoding":"UTF8","owner":"app","postInitApplicationSQL":["CREATE EXTENSION spock;"],"postInitSQL":[],"postInitTemplateSQL":[]}},"certificates":{"clientCASecret":"client-ca-key-pair","replicationTLSSecret":"streaming-replica-client-cert"},"imageName":"ghcr.io/pgedge/pgedge-postgres:18-spock5-standard","imagePullPolicy":"Always","instances":1,"managed":{"roles":[{"comment":"Admin role","ensure":"present","login":true,"name":"admin","superuser":true}]},"postgresql":{"parameters":{"checkpoint_completion_target":"0.9","checkpoint_timeout":"15min","dynamic_shared_memory_type":"posix","hot_standby_feedback":"on","output_plugin_libraries":"pgoutput, test_decoding, spock_output","spock.allow_ddl_from_functions":"on","spock.conflict_log_level":"DEBUG","spock.conflict_resolution":"last_update_wins","spock.enable_ddl_replication":"on","spock.include_ddl_repset":"on","spock.save_resolutions":"on","track_commit_timestamp":"on","track_io_timing":"on","wal_level":"logical","wal_sender_timeout":"5s"},"pg_hba":["hostssl app pgedge 0.0.0.0/0 cert","hostssl app admin 0.0.0.0/0 cert","hostssl app app 0.0.0.0/0 cert","hostssl all streaming_replica all cert map=cnpg_streaming_replica"],"pg_ident":["local postgres admin","local postgres app"],"shared_preload_libraries":["pg_stat_statements","snowflake","spock"]},"projectedVolumeTemplate":{"sources":[{"secret":{"items":[{"key":"tls.crt","mode":384,"path":"pgedge/certificates/tls.crt"},{"key":"tls.key","mode":384,"path":"pgedge/certificates/tls.key"},{"key":"ca.crt","mode":384,"path":"pgedge/certificates/ca.crt"}],"name":"pgedge-client-cert"}}]}}` | Default CloudNativePG Cluster specification applied to all nodes, which can be overridden on a per-node basis using the `clusterSpec` field in each node definition. |
| pgEdge.externalNodes | list | `[]` | Configuration for nodes that are part of the pgEdge cluster, but managed externally to this Helm chart. This can be leveraged for multi-cluster deployments or to wire up existing CloudNativePG Clusters to a pgEdge cluster. |
| pgEdge.extraResources | list | `[]` | Array of extra Kubernetes resources to deploy alongside pgEdge (evaluated as templates). Useful for deploying NetworkPolicies, PodMonitors, ConfigMaps, etc. |
| pgEdge.initSpock | bool | `true` | Whether or not to run the init-spock job to initialize the pgEdge nodes and subscriptions In multi-cluster deployments, this should only be set to true on the last cluster to be deployed. |
Expand Down
26 changes: 26 additions & 0 deletions docs/configuration.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,32 @@ By default, `shared_preload_libraries` contains `pg_stat_statements`, `snowflake

Always include `spock` in `shared_preload_libraries`, as it is required for core functionality provided by this chart. This chart will call `CREATE EXTENSION` for spock when initializing each CloudNativePG Cluster.

### output_plugin_libraries and older PostgreSQL minor versions

PostgreSQL 16.15, 17.11, 18.6, and newer minor versions add a new `output_plugin_libraries` allow-list that gates which logical decoding output plugins a server accepts. Its built-in default is `pgoutput, test_decoding`, which does not include `spock_output`. Without it on the list, Spock cannot create its replication slot and replication stops on the provider node.

This chart sets `postgresql.parameters.output_plugin_libraries` to `pgoutput, test_decoding, spock_output` by default, so that Spock keeps working once a node is running one of the patched minor versions. Since the chart defaults to a mutable image tag, a fresh install picks up a patched version automatically.

!!! warning

`output_plugin_libraries` is a core PostgreSQL setting, not an extension GUC. A minor version older than 16.15 / 17.11 / 18.6 does not recognize it at all, and refuses to start with `unrecognized configuration parameter "output_plugin_libraries"` if it is set to any value, including an empty one. Setting this parameter is only safe once every node in your cluster is on a patched minor version.

If you pin `clusterSpec.imageName` to a minor version older than the patched ones, override the parameter to `null` at the chart-wide level in your values file rather than removing the line:

```yaml
pgEdge:
clusterSpec:
postgresql:
parameters:
output_plugin_libraries: null
```

A YAML value of `null` here deletes the corresponding key before Helm ever renders the manifest, so the parameter is omitted entirely rather than sent through as an empty string. If you need additional output plugins beyond `spock_output`, override the parameter with the full list you need instead of unsetting it.

!!! warning

This `null` override only works at the chart-wide `pgEdge.clusterSpec` level. Setting it to `null` on a single node's `clusterSpec` does not omit the parameter: that override is merged onto the chart-wide default with Sprig's `mergeOverwrite` rather than through Helm's own values merging, and Kubernetes stores a `null` value for a `map[string]string` entry as an empty string rather than dropping the key. An empty value is still rejected exactly like any other value on a PostgreSQL version that predates this GUC. There is currently no values-only way to exempt a single node from this default while keeping it for the rest of the cluster — pin every node's image consistently instead.

## Values reference

You can customize this Helm chart by specifying configuration parameters in your `values.yaml` file.
Expand Down
10 changes: 8 additions & 2 deletions test/integration/multicluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ func TestMultiClusterInstall(t *testing.T) {
CreateNamespace: true,
}
if initSpockImg != "" {
opts.SetValues = []string{fmt.Sprintf("pgEdge.initSpockImageName=%s", initSpockImg)}
opts.SetValues = append(opts.SetValues, fmt.Sprintf("pgEdge.initSpockImageName=%s", initSpockImg))
}
if postgresImg != "" {
opts.SetValues = append(opts.SetValues, fmt.Sprintf("pgEdge.clusterSpec.imageName=%s", postgresImg))
}
if err := helmA.Install(helmRelease, opts); err != nil {
t.Fatalf("helm install cluster-a failed: %v", err)
Expand Down Expand Up @@ -141,7 +144,10 @@ func TestMultiClusterInstall(t *testing.T) {
Timeout: timeout.String(),
}
if initSpockImg != "" {
optsB.SetValues = []string{fmt.Sprintf("pgEdge.initSpockImageName=%s", initSpockImg)}
optsB.SetValues = append(optsB.SetValues, fmt.Sprintf("pgEdge.initSpockImageName=%s", initSpockImg))
}
if postgresImg != "" {
optsB.SetValues = append(optsB.SetValues, fmt.Sprintf("pgEdge.clusterSpec.imageName=%s", postgresImg))
}
if err := helmB.Install(helmRelease, optsB); err != nil {
t.Fatalf("helm install cluster-b failed: %v", err)
Expand Down
Loading
Loading