From d5bfb6601783b1b9bd9dbe621ab58ab7a59b75b4 Mon Sep 17 00:00:00 2001 From: moizpgedge Date: Wed, 12 Aug 2026 17:04:10 +0500 Subject: [PATCH 1/4] feat(values): default output_plugin_libraries for Spock on patched PG minors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostgreSQL 16.15/17.11/18.5+ gate logical decoding output plugins behind a new output_plugin_libraries allow-list that excludes spock_output by default, breaking replication once a node picks up a patched minor via the chart's mutable image tag. Set it to include spock_output by default, and document overriding it to null at the chart-wide level for clusters pinned to an older minor — a per-node override does not work, since it round-trips through the Kubernetes API as an empty string rather than an omitted key, which pre-patch Postgres still rejects. --- README.md | 2 +- docs/configuration.md | 28 ++++++++++- docs/configuration.md.gotmpl | 26 +++++++++++ test/unit/cluster_test.go | 46 +++++++++++++++++++ ...t-plugin-libraries-global-null-values.yaml | 11 +++++ .../output-plugin-libraries-null-values.yaml | 12 +++++ values.yaml | 28 +++++++++++ 7 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 test/unit/testdata/output-plugin-libraries-global-null-values.yaml create mode 100644 test/unit/testdata/output-plugin-libraries-null-values.yaml diff --git a/README.md b/README.md index c23cee5..c1ed77d 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/docs/configuration.md b/docs/configuration.md index 490b9d1..96b69b4 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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.5, 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.5 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. @@ -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. | diff --git a/docs/configuration.md.gotmpl b/docs/configuration.md.gotmpl index b996d50..04d649b 100644 --- a/docs/configuration.md.gotmpl +++ b/docs/configuration.md.gotmpl @@ -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.5, 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.5 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. diff --git a/test/unit/cluster_test.go b/test/unit/cluster_test.go index 57604b5..458fdfa 100644 --- a/test/unit/cluster_test.go +++ b/test/unit/cluster_test.go @@ -125,6 +125,7 @@ func TestClusterDefaultPostgreSQLParameters(t *testing.T) { "spock.enable_ddl_replication": "on", "spock.include_ddl_repset": "on", "spock.allow_ddl_from_functions": "on", + "output_plugin_libraries": "pgoutput, test_decoding, spock_output", } for key, want := range params { got := getNestedString(cluster, "spec", "postgresql", "parameters", key) @@ -134,6 +135,51 @@ func TestClusterDefaultPostgreSQLParameters(t *testing.T) { } } +func TestOutputPluginLibrariesGlobalNullOverride(t *testing.T) { + objects := renderTemplate(t, "output-plugin-libraries-global-null-values.yaml") + cluster := findByKindAndName(objects, "Cluster", "pgedge-n1") + if cluster == nil { + t.Fatal("pgedge-n1 Cluster not found") + } + + params, found, _ := unstructured.NestedMap(cluster.Object, "spec", "postgresql", "parameters") + if !found { + t.Fatal("expected postgresql.parameters to be set") + } + if _, present := params["output_plugin_libraries"]; present { + t.Errorf("expected output_plugin_libraries to be absent, got %v", params["output_plugin_libraries"]) + } +} + +// Unlike the global override, a per-node clusterSpec merges onto the +// default via Sprig's mergeOverwrite, which copies a nil value into the +// map instead of deleting the key — so the key is present but nil here, +// not absent as in TestOutputPluginLibrariesGlobalNullOverride. This is +// NOT a safe substitute for the global override: confirmed live, the +// Kubernetes API stores that nil as an empty string rather than dropping +// the key, and a pre-patch PostgreSQL still rejects the parameter with an +// empty value exactly as it would any other value. A template-only test +// can't see that far, so this only pins the divergence at this layer. +func TestOutputPluginLibrariesPerNodeNullOverride(t *testing.T) { + objects := renderTemplate(t, "output-plugin-libraries-null-values.yaml") + cluster := findByKindAndName(objects, "Cluster", "pgedge-n1") + if cluster == nil { + t.Fatal("pgedge-n1 Cluster not found") + } + + params, found, _ := unstructured.NestedMap(cluster.Object, "spec", "postgresql", "parameters") + if !found { + t.Fatal("expected postgresql.parameters to be set") + } + value, present := params["output_plugin_libraries"] + if !present { + t.Fatal("expected output_plugin_libraries key to be present (as nil) in the rendered manifest, but it was absent") + } + if value != nil { + t.Errorf("expected output_plugin_libraries to be nil, got %v", value) + } +} + func TestClusterDefaultBootstrapConfig(t *testing.T) { objects := renderTemplate(t, "single-node-minimal-values.yaml") clusters := filterByKind(objects, "Cluster") diff --git a/test/unit/testdata/output-plugin-libraries-global-null-values.yaml b/test/unit/testdata/output-plugin-libraries-global-null-values.yaml new file mode 100644 index 0000000..14bf8eb --- /dev/null +++ b/test/unit/testdata/output-plugin-libraries-global-null-values.yaml @@ -0,0 +1,11 @@ +pgEdge: + appName: pgedge + nodes: + - name: n1 + hostname: pgedge-n1-rw + clusterSpec: + storage: + size: 1Gi + postgresql: + parameters: + output_plugin_libraries: null diff --git a/test/unit/testdata/output-plugin-libraries-null-values.yaml b/test/unit/testdata/output-plugin-libraries-null-values.yaml new file mode 100644 index 0000000..751b424 --- /dev/null +++ b/test/unit/testdata/output-plugin-libraries-null-values.yaml @@ -0,0 +1,12 @@ +pgEdge: + appName: pgedge + nodes: + - name: n1 + hostname: pgedge-n1-rw + clusterSpec: + postgresql: + parameters: + output_plugin_libraries: null + clusterSpec: + storage: + size: 1Gi diff --git a/values.yaml b/values.yaml index 7cc502c..b4bb4cb 100644 --- a/values.yaml +++ b/values.yaml @@ -78,6 +78,34 @@ pgEdge: track_commit_timestamp: "on" hot_standby_feedback: "on" track_io_timing: "on" + # PostgreSQL 16.15, 17.11, 18.5, and newer minor versions gate which + # logical decoding output plugins may be used via the new + # output_plugin_libraries allow-list (default "pgoutput, + # test_decoding"). Without spock_output on that list, Spock cannot + # create its replication slot and replication stops on the provider. + # This chart defaults to a mutable PG image tag, so it will pick up + # a patched minor version, and this default keeps that working out + # of the box rather than failing on the version users try first. + # + # Unlike Spock's own GUCs, this is a core PostgreSQL setting with no + # extension namespace, so a version that predates it rejects it + # outright at startup ("unrecognized configuration parameter") + # rather than ignoring it — setting it unconditionally is only safe + # once every node is on a patched minor version. + # + # If you need to add further output plugins, override this value + # rather than unsetting it. To run against a PG version that + # predates this GUC, override this same key with `null` at THIS + # (chart-wide) level — Helm's own values merging deletes a key + # whose value is explicitly null before rendering, so the + # parameter is omitted entirely rather than sent as an empty + # value. The same null override on a single node's clusterSpec + # does NOT work: that merge goes through Sprig's mergeOverwrite + # and then the Kubernetes API's map[string]string handling, both + # of which keep the key with an empty string rather than dropping + # it, which a pre-patch PG version rejects exactly like any other + # value. + output_plugin_libraries: "pgoutput, test_decoding, spock_output" spock.enable_ddl_replication: "on" spock.include_ddl_repset: "on" spock.allow_ddl_from_functions: "on" From 2af03d2f93549142ba3c5f93d5a1fec71970f671 Mon Sep 17 00:00:00 2001 From: moizpgedge Date: Wed, 12 Aug 2026 20:25:57 +0500 Subject: [PATCH 2/4] docs: list 18.6 instead of the recalled 18.5 18.5 was recalled and replaced by 18.6, so it never shipped as a patched minor. Point at the version that actually did. --- docs/configuration.md | 4 ++-- docs/configuration.md.gotmpl | 4 ++-- values.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 96b69b4..b019742 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -221,13 +221,13 @@ By default, `shared_preload_libraries` contains `pg_stat_statements`, `snowflake ### output_plugin_libraries and older PostgreSQL minor versions -PostgreSQL 16.15, 17.11, 18.5, 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. +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.5 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. + `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: diff --git a/docs/configuration.md.gotmpl b/docs/configuration.md.gotmpl index 04d649b..c702cbf 100644 --- a/docs/configuration.md.gotmpl +++ b/docs/configuration.md.gotmpl @@ -221,13 +221,13 @@ By default, `shared_preload_libraries` contains `pg_stat_statements`, `snowflake ### output_plugin_libraries and older PostgreSQL minor versions -PostgreSQL 16.15, 17.11, 18.5, 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. +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.5 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. + `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: diff --git a/values.yaml b/values.yaml index b4bb4cb..fb16fe6 100644 --- a/values.yaml +++ b/values.yaml @@ -78,7 +78,7 @@ pgEdge: track_commit_timestamp: "on" hot_standby_feedback: "on" track_io_timing: "on" - # PostgreSQL 16.15, 17.11, 18.5, and newer minor versions gate which + # PostgreSQL 16.15, 17.11, 18.6, and newer minor versions gate which # logical decoding output plugins may be used via the new # output_plugin_libraries allow-list (default "pgoutput, # test_decoding"). Without spock_output on that list, Spock cannot From f88b4db425e3ca3c811ce79cd44a2d776af97277 Mon Sep 17 00:00:00 2001 From: moizpgedge Date: Wed, 12 Aug 2026 21:27:54 +0500 Subject: [PATCH 3/4] ci: point tests at the internal repo's patched image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart's default Postgres image (18-spock5-standard) still resolves to a minor version that predates output_plugin_libraries, so chart-testing and integration-tests fail against it. Point both at the same tag in pgEdge's internal image repo instead, which already has a patched version from postgres-images#28's approved build — one env var, no changes to any test values file, easy to remove once the public tag catches up. Also adds the changie entry for this feature. --- .github/workflows/test.yaml | 14 ++++++++++++-- changes/unreleased/Added-20260812-212552.yaml | 3 +++ 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/Added-20260812-212552.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 61eab22..927f512 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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 + jobs: unit-tests: runs-on: ubuntu-latest @@ -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() @@ -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() diff --git a/changes/unreleased/Added-20260812-212552.yaml b/changes/unreleased/Added-20260812-212552.yaml new file mode 100644 index 0000000..9d20267 --- /dev/null +++ b/changes/unreleased/Added-20260812-212552.yaml @@ -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 From e0e171463c2bc829b67a2ecf0a0883168062e47a Mon Sep 17 00:00:00 2001 From: moizpgedge Date: Wed, 12 Aug 2026 22:09:20 +0500 Subject: [PATCH 4/4] fix(test): apply the Postgres image override in the multi-cluster test too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestMultiClusterInstall builds its own install options independently of the shared helper the other integration tests use, and never had the pgEdge.clusterSpec.imageName override wired in at all — only initSpockImageName. Confirmed live: this is why integration-tests kept failing after pointing CI at the internal repo's patched image; every other test already picked it up through the shared helper. Also trims the output_plugin_libraries comment in values.yaml per review: the full explanation lives in the docs already. --- test/integration/multicluster_test.go | 10 +++++++-- values.yaml | 29 ++------------------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/test/integration/multicluster_test.go b/test/integration/multicluster_test.go index 61ed1e4..1525223 100644 --- a/test/integration/multicluster_test.go +++ b/test/integration/multicluster_test.go @@ -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) @@ -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) diff --git a/values.yaml b/values.yaml index fb16fe6..2660e78 100644 --- a/values.yaml +++ b/values.yaml @@ -78,33 +78,8 @@ pgEdge: track_commit_timestamp: "on" hot_standby_feedback: "on" track_io_timing: "on" - # PostgreSQL 16.15, 17.11, 18.6, and newer minor versions gate which - # logical decoding output plugins may be used via the new - # output_plugin_libraries allow-list (default "pgoutput, - # test_decoding"). Without spock_output on that list, Spock cannot - # create its replication slot and replication stops on the provider. - # This chart defaults to a mutable PG image tag, so it will pick up - # a patched minor version, and this default keeps that working out - # of the box rather than failing on the version users try first. - # - # Unlike Spock's own GUCs, this is a core PostgreSQL setting with no - # extension namespace, so a version that predates it rejects it - # outright at startup ("unrecognized configuration parameter") - # rather than ignoring it — setting it unconditionally is only safe - # once every node is on a patched minor version. - # - # If you need to add further output plugins, override this value - # rather than unsetting it. To run against a PG version that - # predates this GUC, override this same key with `null` at THIS - # (chart-wide) level — Helm's own values merging deletes a key - # whose value is explicitly null before rendering, so the - # parameter is omitted entirely rather than sent as an empty - # value. The same null override on a single node's clusterSpec - # does NOT work: that merge goes through Sprig's mergeOverwrite - # and then the Kubernetes API's map[string]string handling, both - # of which keep the key with an empty string rather than dropping - # it, which a pre-patch PG version rejects exactly like any other - # value. + # See "output_plugin_libraries and older PostgreSQL minor versions" + # in docs/configuration.md for what this is and how to override it. output_plugin_libraries: "pgoutput, test_decoding, spock_output" spock.enable_ddl_replication: "on" spock.include_ddl_repset: "on"