Skip to content

K8SPG-911: Add pg_tde support#1440

Draft
egegunes wants to merge 3 commits intomainfrom
K8SPG-911
Draft

K8SPG-911: Add pg_tde support#1440
egegunes wants to merge 3 commits intomainfrom
K8SPG-911

Conversation

@egegunes
Copy link
Contributor

CHANGE DESCRIPTION

Problem:
Short explanation of the problem.

Cause:
Short explanation of the root cause of the issue if applicable.

Solution:
Short explanation of the solution we are providing with this PR.

CHECKLIST

Jira

  • Is the Jira ticket created and referenced properly?
  • Does the Jira ticket have the proper statuses for documentation (Needs Doc) and QA (Needs QA)?
  • Does the Jira ticket link to the proper milestone (Fix Version field)?

Tests

  • Is an E2E test/test case added for the new feature/change?
  • Are unit tests added where appropriate?

Config/Logging/Testability

  • Are all needed new/changed options added to default YAML files?
  • Are all needed new/changed options added to the Helm Chart?
  • Did we add proper logging messages for operator actions?
  • Did we ensure compatibility with the previous version or cluster upgrade process?
  • Does the change support oldest and newest supported PG version?
  • Does the change support oldest and newest supported Kubernetes version?

Copilot AI review requested due to automatic review settings February 12, 2026 13:58
@egegunes egegunes added this to the v2.9.0 milestone Feb 12, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds pg_tde (Transparent Data Encryption) support for PostgreSQL clusters, integrating with HashiCorp Vault for key management. The implementation follows the existing extension pattern in the codebase and includes API changes, reconciliation logic, and CRD updates.

Changes:

  • Added pg_tde extension support with Vault integration for managing encryption keys
  • Introduced new API types (PGTDESpec, PGTDEVaultSpec, PGTDESecretObjectReference) for configuring pg_tde
  • Refactored extension configuration to use individual extension structs instead of the deprecated builtin structure
  • Added reconciliation logic for configuring pg_tde providers and managing encryption keys

Reviewed changes

Copilot reviewed 20 out of 22 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go Added PGTDESpec, PGTDEVaultSpec, and PGTDESecretObjectReference types for pg_tde configuration
pkg/apis/postgres-operator.crunchydata.com/v1beta1/zz_generated.deepcopy.go Auto-generated DeepCopy methods for new pg_tde types
pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go Added BuiltInExtensionSpec and pg_tde support; refactored extension defaults handling
pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go Auto-generated DeepCopy methods for BuiltInExtensionSpec
internal/pgtde/postgres.go New package implementing pg_tde enable/disable and Vault provider configuration
internal/postgres/reconcile.go Added pg_tde volume and volume mount configuration for PostgreSQL instance pods
internal/controller/postgrescluster/postgres.go Added reconcilePGTDEProviders function to configure pg_tde Vault providers
internal/controller/postgrescluster/controller.go Integrated pg_tde parameters and provider reconciliation into main reconciliation flow
internal/controller/postgrescluster/instance.go Added TDEInstalledAnnotation to instance pods when pg_tde is enabled
internal/naming/names.go Added constants for pg_tde volume names, mount paths, and provider identifiers
internal/naming/annotations.go Added TDEInstalledAnnotation constant
internal/pgvector/postgres.go Fixed comments to correctly reference pgvector instead of pgAudit
percona/controller/pgbackup/controller.go Added pg.Default() call to ensure extension defaults before backup
deploy/.yaml and config/crd/bases/.yaml Updated CRD definitions with pg_tde specifications
deploy/cr.yaml Added example pg_tde configuration with Vault settings
pkg/apis/pgv2.percona.com/v2/perconapgbackup_types_test.go Replaced custom ptr() function with k8s.io/utils/ptr.To
Comments suppressed due to low confidence (1)

pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go:436

  • Inconsistent usage of extension enabled flags. Line 432 uses cr.Spec.Extensions.PGStatMonitor.Enabled (the new field structure), while lines 433-436 still use cr.Spec.Extensions.BuiltIn.* (the deprecated field structure). All lines should be updated to use the new field structure consistently for maintainability.
	postgresCluster.Spec.Extensions.PGStatMonitor = *cr.Spec.Extensions.PGStatMonitor.Enabled
	postgresCluster.Spec.Extensions.PGStatStatements = *cr.Spec.Extensions.BuiltIn.PGStatStatements
	postgresCluster.Spec.Extensions.PGAudit = *cr.Spec.Extensions.BuiltIn.PGAudit
	postgresCluster.Spec.Extensions.PGVector = *cr.Spec.Extensions.BuiltIn.PGVector
	postgresCluster.Spec.Extensions.PGRepack = *cr.Spec.Extensions.BuiltIn.PGRepack

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 111 to 115
pgTDECASecret := &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: inCluster.Spec.Extensions.PGTDE.Vault.CASecret.Name,
},
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SecretProjection is missing the Items field to specify which key from the secret should be mounted. Without Items, all keys in the secret will be mounted. Based on the pattern used elsewhere in the codebase (e.g., internal/patroni/certificates.go), you should add an Items field to specify the exact key to mount from the secret.

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +108
stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', '%s'
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
naming.PGTDEMountPath+"/"+vault.CASecret.Key,
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function doesn't check if vault.CASecret.Key is empty before using it in the SQL statement. When CASecret.Key is empty, this will result in an invalid file path being passed to the pg_tde function. The function should handle the empty case similar to AddVaultProvider which uses "NULL" when CASecret.Key is empty.

Suggested change
stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', '%s'
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
naming.PGTDEMountPath+"/"+vault.CASecret.Key,
// Handle optional CA secret key similarly to AddVaultProvider: use NULL when empty.
caPath := "NULL"
if vault.CASecret.Key != "" {
caPath = fmt.Sprintf("'%s'", naming.PGTDEMountPath+"/"+vault.CASecret.Key)
}
stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', %s
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
caPath,

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +104
'vault-provider', '%s', '%s', '%s', '%s'
);`,
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provider name is hard-coded as 'vault-provider' instead of using the naming.PGTDEVaultProvider constant. This inconsistency could lead to configuration issues since AddVaultProvider uses the constant. Both functions should use the same constant for consistency.

Suggested change
'vault-provider', '%s', '%s', '%s', '%s'
);`,
'%s', '%s', '%s', '%s', '%s'
);`,
naming.PGTDEVaultProvider,

Copilot uses AI. Check for mistakes.
Comment on lines +452 to +457
configure := func(ctx context.Context, exec postgres.Executor) error {
if cluster.Status.PGTDERevision == "" {
return pgtde.AddVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
}
return pgtde.ChangeVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no validation to ensure cluster.Spec.Extensions.PGTDE.Vault is not nil before calling AddVaultProvider or ChangeVaultProvider. If pg_tde is enabled but the Vault configuration is not provided, this will result in a nil pointer dereference. Add a nil check for Vault configuration before using it.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +109
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', '%s'
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
naming.PGTDEMountPath+"/"+vault.CASecret.Key,
),
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL statement construction is vulnerable to SQL injection as user-provided values from vault.Host, vault.MountPath, vault.TokenSecret.Key, and vault.CASecret.Key are directly interpolated into the SQL string using fmt.Sprintf without any sanitization or parameterization. Although these values come from the cluster spec, they should still be properly escaped or parameterized to prevent SQL injection attacks and handle special characters correctly. Consider using proper SQL parameterization or at minimum sanitizing these inputs.

Copilot uses AI. Check for mistakes.
Comment on lines 94 to 98
pgTDETokenSecret := &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: inCluster.Spec.Extensions.PGTDE.Vault.TokenSecret.Name,
},
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SecretProjection is missing the Items field to specify which key from the secret should be mounted. Without Items, all keys in the secret will be mounted, which may not be the intended behavior. Based on the pattern used elsewhere in the codebase (e.g., internal/patroni/certificates.go), you should add an Items field to specify the exact key to mount from the secret.

Copilot uses AI. Check for mistakes.
crunchyv1beta1 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

// EnableInPostgreSQL installs pgvector triggers into every database.
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment incorrectly says "installs pgvector triggers" but this function installs pg_tde extension, not pgvector. This should be updated to accurately describe the function's purpose.

Suggested change
// EnableInPostgreSQL installs pgvector triggers into every database.
// EnableInPostgreSQL installs and updates the pg_tde extension in every database.

Copilot uses AI. Check for mistakes.
Comment on lines 89 to 120
pgTDEVolumeMount := corev1.VolumeMount{
Name: naming.PGTDEVolume,
MountPath: naming.PGTDEMountPath,
ReadOnly: true,
}
pgTDETokenSecret := &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: inCluster.Spec.Extensions.PGTDE.Vault.TokenSecret.Name,
},
}
pgTDEVolume := corev1.Volume{
Name: pgTDEVolumeMount.Name,
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
DefaultMode: initialize.Int32(0o600),
Sources: []corev1.VolumeProjection{
{Secret: pgTDETokenSecret},
},
},
},
}
if inCluster.Spec.Extensions.PGTDE.Vault.CASecret.Name != "" {
pgTDECASecret := &corev1.SecretProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: inCluster.Spec.Extensions.PGTDE.Vault.CASecret.Name,
},
}
pgTDEVolume.VolumeSource.Projected.Sources = append(
pgTDEVolume.VolumeSource.Projected.Sources, corev1.VolumeProjection{
Secret: pgTDECASecret,
})
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pg_tde volume and volume mount are created unconditionally at the beginning of the function, even when PGTDE is not enabled. This creates unused volume objects that are only conditionally added to the pod later. Consider moving this logic inside the conditional check at lines 193-195 and 275-277 to avoid creating unnecessary objects.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +70
fmt.Sprintf(`SELECT pg_tde_add_global_key_provider_vault_v2(
'%s', '%s', '%s', '%s', %s
);`,
naming.PGTDEVaultProvider,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
caSecretPath,
),
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL statement construction is vulnerable to SQL injection as user-provided values from vault.Host, vault.MountPath, vault.TokenSecret.Key, and vault.CASecret.Key are directly interpolated into the SQL string using fmt.Sprintf without any sanitization or parameterization. Although these values come from the cluster spec, they should still be properly escaped or parameterized to prevent SQL injection attacks and handle special characters correctly. Consider using proper SQL parameterization or at minimum sanitizing these inputs.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +123
package pgtde

import (
"context"
"fmt"
"strings"

"github.com/percona/percona-postgresql-operator/v2/internal/logging"
"github.com/percona/percona-postgresql-operator/v2/internal/naming"
"github.com/percona/percona-postgresql-operator/v2/internal/postgres"
crunchyv1beta1 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
)

// EnableInPostgreSQL installs pgvector triggers into every database.
func EnableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {
log := logging.FromContext(ctx)

stdout, stderr, err := exec.ExecInAllDatabases(ctx,
`SET client_min_messages = WARNING; CREATE EXTENSION IF NOT EXISTS pg_tde; ALTER EXTENSION pg_tde UPDATE;`,
map[string]string{
"ON_ERROR_STOP": "on", // Abort when any one command fails.
"QUIET": "on", // Do not print successful commands to stdout.
})

log.V(1).Info("enabled pg_tde", "stdout", stdout, "stderr", stderr)

return err
}

func DisableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {
log := logging.FromContext(ctx)

stdout, stderr, err := exec.ExecInAllDatabases(ctx,
`SET client_min_messages = WARNING; DROP EXTENSION IF EXISTS pg_tde;`,
map[string]string{
"ON_ERROR_STOP": "on", // Abort when any one command fails.
"QUIET": "on", // Do not print successful commands to stdout.
})

log.V(1).Info("disabled pg_tde", "stdout", stdout, "stderr", stderr)

return err
}

func PostgreSQLParameters(outParameters *postgres.Parameters) {
outParameters.Mandatory.AppendToList("shared_preload_libraries", "pg_tde")
}

func AddVaultProvider(ctx context.Context, exec postgres.Executor, vault *crunchyv1beta1.PGTDEVaultSpec) error {
log := logging.FromContext(ctx)

caSecretPath := "NULL"
if vault.CASecret.Key != "" {
caSecretPath = fmt.Sprintf("'%s'", naming.PGTDEMountPath+"/"+vault.CASecret.Key)
}

stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_add_global_key_provider_vault_v2(
'%s', '%s', '%s', '%s', %s
);`,
naming.PGTDEVaultProvider,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
caSecretPath,
),
fmt.Sprintf("SELECT pg_tde_create_key_using_global_key_provider('%s', '%s');",
naming.PGTDEGlobalKey,
naming.PGTDEVaultProvider,
),
fmt.Sprintf("SELECT pg_tde_set_default_key_using_global_key_provider('%s', '%s');",
naming.PGTDEGlobalKey,
naming.PGTDEVaultProvider,
),
}, "\n")),
map[string]string{
"ON_ERROR_STOP": "on", // Abort when any one statement fails.
"QUIET": "on", // Do not print successful statements to stdout.
}, nil)

if err != nil {
log.Info("failed to add pg_tde vault provider", "stdout", stdout, "stderr", stderr)
} else {
log.Info("added pg_tde vault provider", "stdout", stdout, "stderr", stderr)
}

return err
}

func ChangeVaultProvider(ctx context.Context, exec postgres.Executor, vault *crunchyv1beta1.PGTDEVaultSpec) error {
log := logging.FromContext(ctx)

stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', '%s'
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
naming.PGTDEMountPath+"/"+vault.CASecret.Key,
),
}, "\n")),
map[string]string{
"ON_ERROR_STOP": "on", // Abort when any one statement fails.
"QUIET": "on", // Do not print successful statements to stdout.
}, nil)

if err != nil {
log.Info("failed to change pg_tde vault provider", "stdout", stdout, "stderr", stderr)
} else {
log.Info("changed pg_tde vault provider", "stdout", stdout, "stderr", stderr)
}

return err
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal/pgtde package lacks test coverage. Similar extension packages in the codebase (e.g., internal/pgvector/postgres_test.go, internal/pgaudit/postgres_test.go) have corresponding test files. Consider adding tests for the pgtde package functions, particularly for EnableInPostgreSQL, DisableInPostgreSQL, AddVaultProvider, and ChangeVaultProvider.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings February 16, 2026 13:05
local command=${1}
local uri=${2}
local driver=${3:-postgres}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

if [ "${hugepages_used}" -gt 0 ]; then
echo "PostgreSQL is using hugepages"
return 0
else
echo "Hugepages available but NOT being used by PostgreSQL"
return 1
fi

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 39 changed files in this pull request and generated 7 comments.

Comment on lines +346 to +351
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeDisabled",
"Unable to install pg_tde")
}
} else {
if pgTdeOK = pgtde.DisableInPostgreSQL(ctx, exec) == nil; !pgTdeOK {
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeEnabled",
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: The event reason strings for pg_tde failures are reversed (install failure emits pgTdeDisabled, disable failure emits pgTdeEnabled).
  2. Why it matters: Alerting and troubleshooting based on event reasons will be misleading.
  3. Fix: Swap the reason strings so the "install" failure uses an "Enabled"-style reason and the "disable" failure uses a "Disabled"-style reason (consistent with the other extension events in this function).
Suggested change
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeDisabled",
"Unable to install pg_tde")
}
} else {
if pgTdeOK = pgtde.DisableInPostgreSQL(ctx, exec) == nil; !pgTdeOK {
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeEnabled",
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeEnabled",
"Unable to install pg_tde")
}
} else {
if pgTdeOK = pgtde.DisableInPostgreSQL(ctx, exec) == nil; !pgTdeOK {
r.Recorder.Event(cluster, corev1.EventTypeWarning, "pgTdeDisabled",

Copilot uses AI. Check for mistakes.
Comment on lines +453 to +456
if cluster.Status.PGTDERevision == "" {
return pgtde.AddVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
}
return pgtde.ChangeVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: reconcilePGTDEProviders can panic when spec.extensions.pg_tde.enabled is true but spec.extensions.pg_tde.vault is nil; configure() passes a nil vault into pgtde.AddVaultProvider/ChangeVaultProvider.
  2. Why it matters: A panic will crash the operator and disrupt reconciliation for all clusters.
  3. Fix: Validate cluster.Spec.Extensions.PGTDE.Vault != nil (and required fields) before calling into pgtde.*, returning a clear error/event when pg_tde is enabled without a provider configuration.
Suggested change
if cluster.Status.PGTDERevision == "" {
return pgtde.AddVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
}
return pgtde.ChangeVaultProvider(ctx, exec, cluster.Spec.Extensions.PGTDE.Vault)
pgtdeSpec := cluster.Spec.Extensions.PGTDE
if pgtdeSpec == nil || pgtdeSpec.Vault == nil {
err := errors.New("pg_tde is enabled but spec.extensions.pg_tde.vault is not configured")
log.Error(err, "pg_tde vault provider configuration is missing")
return err
}
if cluster.Status.PGTDERevision == "" {
return pgtde.AddVaultProvider(ctx, exec, pgtdeSpec.Vault)
}
return pgtde.ChangeVaultProvider(ctx, exec, pgtdeSpec.Vault)

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +16
// EnableInPostgreSQL installs pgvector triggers into every database.
func EnableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {
log := logging.FromContext(ctx)
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: The file-level comment for EnableInPostgreSQL mentions pgvector triggers, but the function enables pg_tde.
  2. Why it matters: Incorrect comments slow down reviews/debugging and can mislead future changes.
  3. Fix: Update the comment to describe pg_tde (e.g., installing/updating the pg_tde extension in all databases).

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +83
caSecretPath := "NULL"
if vault.CASecret.Key != "" {
caSecretPath = fmt.Sprintf("'%s'", naming.PGTDEMountPath+"/"+vault.CASecret.Key)
}

stdout, stderr, err := exec.Exec(ctx,
strings.NewReader(strings.Join([]string{
// Quiet NOTICE messages from IF NOT EXISTS statements.
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_add_global_key_provider_vault_v2(
'%s', '%s', '%s', '%s', %s
);`,
naming.PGTDEVaultProvider,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
caSecretPath,
),
fmt.Sprintf("SELECT pg_tde_create_key_using_global_key_provider('%s', '%s');",
naming.PGTDEGlobalKey,
naming.PGTDEVaultProvider,
),
fmt.Sprintf("SELECT pg_tde_set_default_key_using_global_key_provider('%s', '%s');",
naming.PGTDEGlobalKey,
naming.PGTDEVaultProvider,
),
}, "\n")),
map[string]string{
"ON_ERROR_STOP": "on", // Abort when any one statement fails.
"QUIET": "on", // Do not print successful statements to stdout.
}, nil)
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: SQL for configuring Vault is built via fmt.Sprintf with unescaped user-provided values (host, mountPath, secret key paths), which can break SQL quoting or allow SQL injection via CR input.
  2. Why it matters: The operator executes this SQL as a privileged user; malformed/hostile input could lead to unexpected SQL execution.
  3. Fix: Use the existing internal/postgres.QuoteLiteral helper (or equivalent) for every SQL string literal, and avoid embedding raw values directly into SQL.

Copilot uses AI. Check for mistakes.
Comment on lines +101 to +110
`SET client_min_messages = WARNING;`,
fmt.Sprintf(`SELECT pg_tde_change_global_key_provider_vault_v2(
'vault-provider', '%s', '%s', '%s', '%s'
);`,
vault.Host,
vault.MountPath,
naming.PGTDEMountPath+"/"+vault.TokenSecret.Key,
naming.PGTDEMountPath+"/"+vault.CASecret.Key,
),
}, "\n")),
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: ChangeVaultProvider hardcodes the provider name ('vault-provider') and always passes a CA path even when CASecret is unset/optional.
  2. Why it matters: This makes the provider name harder to change consistently and can cause reconciliation to fail indefinitely for configurations that omit caSecret.
  3. Fix: Use naming.PGTDEVaultProvider instead of a string literal, and mirror AddVaultProvider by passing NULL (or equivalent) when no CA secret is configured.

Copilot uses AI. Check for mistakes.
Comment on lines 169 to 173
// The specification of extensions.
// +operator-sdk:csv:customresourcedefinitions:type=spec
// +optional
Extensions ExtensionsSpec `json:"extensions,omitempty"`
Extensions ExtensionsSpec `json:"extensions"`

Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: Extensions was changed from json:"extensions,omitempty" to json:"extensions", which will make .spec.extensions required in the generated CRD schema.
  2. Why it matters: This is a breaking API change; existing clusters created without spec.extensions may fail validation on update after the CRD is upgraded.
  3. Fix: Keep omitempty (or otherwise ensure the CRD does not require this field) and rely on defaulting/webhook logic to populate subfields as needed.

Copilot uses AI. Check for mistakes.
Comment on lines +399 to +406
run_psql_command() {
local command=${1}
local uri=${2}
local driver=${3:-postgres}

kubectl -n ${NAMESPACE} exec $(get_client_pod) -- \
psql -v ON_ERROR_STOP=1 -t -q "${uri}" -c "${command}"
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Problem: run_psql_command defines a driver variable but never uses it.
  2. Why it matters: Dead code in test helpers makes scripts harder to maintain and can confuse future edits.
  3. Fix: Remove the unused driver parameter/variable, or incorporate it into the connection invocation (consistent with run_psql_local).

Copilot uses AI. Check for mistakes.
@JNKPercona
Copy link
Collaborator

Test Name Result Time
backup-enable-disable passed 00:05:52
builtin-extensions passed 00:05:13
custom-envs passed 00:18:38
custom-extensions failure 00:05:37
custom-tls passed 00:05:07
database-init-sql passed 00:02:02
demand-backup passed 00:23:24
demand-backup-offline-snapshot passed 00:12:38
finalizers passed 00:05:56
init-deploy passed 00:03:21
huge-pages passed 00:03:10
monitoring passed 00:07:14
monitoring-pmm3 passed 00:11:14
one-pod passed 00:05:34
operator-self-healing passed 00:07:58
pg-tde failure 00:07:45
pitr passed 00:11:32
scaling passed 00:07:41
scheduled-backup passed 00:27:19
self-healing passed 00:09:11
sidecars passed 00:02:52
standby-pgbackrest failure 00:11:32
standby-streaming passed 00:10:14
start-from-backup passed 00:11:06
tablespaces failure 00:08:51
telemetry-transfer passed 00:03:42
upgrade-consistency passed 00:06:33
upgrade-minor passed 00:05:17
users passed 00:04:42
Summary Value
Tests Run 29/29
Job Duration 01:41:19
Total Test Time 04:11:27

commit: 0de3e00
image: perconalab/percona-postgresql-operator:PR-1440-0de3e009a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants