diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml
index 07120f15b0..d7a95ec095 100644
--- a/.github/workflows/continuous-delivery.yml
+++ b/.github/workflows/continuous-delivery.yml
@@ -37,7 +37,7 @@ env:
GOLANG_VERSION: "1.23.x"
KUBEBUILDER_VERSION: "2.3.1"
KIND_VERSION: "v0.24.0"
- ROOK_VERSION: "v1.15.2"
+ ROOK_VERSION: "v1.15.3"
EXTERNAL_SNAPSHOTTER_VERSION: "v8.1.0"
OPERATOR_IMAGE_NAME: "ghcr.io/${{ github.repository }}-testing"
BUILD_PUSH_PROVENANCE: ""
diff --git a/.wordlist-en-custom.txt b/.wordlist-en-custom.txt
index 26de5d448c..b4188b65aa 100644
--- a/.wordlist-en-custom.txt
+++ b/.wordlist-en-custom.txt
@@ -500,6 +500,7 @@ allowPrivilegeEscalation
allowVolumeExpansion
amd
angus
+anonymization
api
apiGroup
apiGroups
@@ -795,6 +796,7 @@ http
httpGet
https
hugepages
+icu
ident
imageCatalogRef
imageName
diff --git a/api/v1/backup_funcs.go b/api/v1/backup_funcs.go
index 9c56e8503d..c41e09ee12 100644
--- a/api/v1/backup_funcs.go
+++ b/api/v1/backup_funcs.go
@@ -24,6 +24,7 @@ import (
volumesnapshot "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -230,6 +231,17 @@ func (backup *Backup) GetVolumeSnapshotConfiguration(
return config
}
+// EnsureGVKIsPresent ensures that the GroupVersionKind (GVK) metadata is present in the Backup object.
+// This is necessary because informers do not automatically include metadata inside the object.
+// By setting the GVK, we ensure that components such as the plugins have enough metadata to typecheck the object.
+func (backup *Backup) EnsureGVKIsPresent() {
+ backup.SetGroupVersionKind(schema.GroupVersionKind{
+ Group: GroupVersion.Group,
+ Version: GroupVersion.Version,
+ Kind: BackupKind,
+ })
+}
+
// IsEmpty checks if the plugin configuration is empty or not
func (configuration *BackupPluginConfiguration) IsEmpty() bool {
return configuration == nil || len(configuration.Name) == 0
diff --git a/api/v1/cluster_funcs.go b/api/v1/cluster_funcs.go
index 41206f8f21..f3caf8d59c 100644
--- a/api/v1/cluster_funcs.go
+++ b/api/v1/cluster_funcs.go
@@ -28,13 +28,13 @@ import (
"github.com/cloudnative-pg/machinery/pkg/image/reference"
"github.com/cloudnative-pg/machinery/pkg/log"
"github.com/cloudnative-pg/machinery/pkg/postgres/version"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/cloudnative-pg/cloudnative-pg/internal/configuration"
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
"github.com/cloudnative-pg/cloudnative-pg/pkg/system"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
"github.com/cloudnative-pg/cloudnative-pg/pkg/versions"
diff --git a/api/v1/cluster_funcs_test.go b/api/v1/cluster_funcs_test.go
index 7d459faca0..8d6f0950ac 100644
--- a/api/v1/cluster_funcs_test.go
+++ b/api/v1/cluster_funcs_test.go
@@ -22,12 +22,12 @@ import (
barmanCatalog "github.com/cloudnative-pg/barman-cloud/pkg/catalog"
"github.com/cloudnative-pg/machinery/pkg/postgres/version"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
. "github.com/onsi/ginkgo/v2"
diff --git a/api/v1/cluster_webhook.go b/api/v1/cluster_webhook.go
index 16b8c73c52..197358d786 100644
--- a/api/v1/cluster_webhook.go
+++ b/api/v1/cluster_webhook.go
@@ -27,6 +27,7 @@ import (
"github.com/cloudnative-pg/machinery/pkg/image/reference"
"github.com/cloudnative-pg/machinery/pkg/log"
"github.com/cloudnative-pg/machinery/pkg/postgres/version"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
"github.com/cloudnative-pg/machinery/pkg/types"
storagesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/apis/volumesnapshot/v1"
v1 "k8s.io/api/core/v1"
@@ -44,7 +45,6 @@ import (
"github.com/cloudnative-pg/cloudnative-pg/internal/configuration"
"github.com/cloudnative-pg/cloudnative-pg/pkg/postgres"
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
)
diff --git a/api/v1/database_types.go b/api/v1/database_types.go
index 8cb52ad810..dd7bd58cf5 100644
--- a/api/v1/database_types.go
+++ b/api/v1/database_types.go
@@ -42,6 +42,9 @@ type DatabaseSpec struct {
// The name inside PostgreSQL
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="name is immutable"
+ // +kubebuilder:validation:XValidation:rule="self != 'postgres'",message="the name postgres is reserved"
+ // +kubebuilder:validation:XValidation:rule="self != 'template0'",message="the name template0 is reserved"
+ // +kubebuilder:validation:XValidation:rule="self != 'template1'",message="the name template1 is reserved"
Name string `json:"name"`
// The owner
@@ -57,6 +60,36 @@ type DatabaseSpec struct {
// +optional
Encoding string `json:"encoding,omitempty"`
+ // The locale (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="locale is immutable"
+ // +optional
+ Locale string `json:"locale,omitempty"`
+
+ // The locale provider (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="locale_provider is immutable"
+ // +optional
+ LocaleProvider string `json:"locale_provider,omitempty"`
+
+ // The LC_COLLATE (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="lc_collate is immutable"
+ // +optional
+ LcCollate string `json:"lc_collate,omitempty"`
+
+ // The LC_CTYPE (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="lc_ctype is immutable"
+ // +optional
+ LcCtype string `json:"lc_ctype,omitempty"`
+
+ // The ICU_LOCALE (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="icu_locale is immutable"
+ // +optional
+ IcuLocale string `json:"icu_locale,omitempty"`
+
+ // The ICU_RULES (cannot be changed)
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="icu_rules is immutable"
+ // +optional
+ IcuRules string `json:"icu_rules,omitempty"`
+
// True when the database is a template
// +optional
IsTemplate *bool `json:"isTemplate,omitempty"`
diff --git a/api/v1/pooler_webhook.go b/api/v1/pooler_webhook.go
index c82205ed0c..24241a836a 100644
--- a/api/v1/pooler_webhook.go
+++ b/api/v1/pooler_webhook.go
@@ -20,6 +20,7 @@ import (
"fmt"
"github.com/cloudnative-pg/machinery/pkg/log"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -27,8 +28,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
)
var (
diff --git a/config/crd/bases/postgresql.cnpg.io_databases.yaml b/config/crd/bases/postgresql.cnpg.io_databases.yaml
index b348c25dd9..f49202505e 100644
--- a/config/crd/bases/postgresql.cnpg.io_databases.yaml
+++ b/config/crd/bases/postgresql.cnpg.io_databases.yaml
@@ -93,15 +93,57 @@ spec:
x-kubernetes-validations:
- message: encoding is immutable
rule: self == oldSelf
+ icu_locale:
+ description: The ICU_LOCALE (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: icu_locale is immutable
+ rule: self == oldSelf
+ icu_rules:
+ description: The ICU_RULES (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: icu_rules is immutable
+ rule: self == oldSelf
isTemplate:
description: True when the database is a template
type: boolean
+ lc_collate:
+ description: The LC_COLLATE (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: lc_collate is immutable
+ rule: self == oldSelf
+ lc_ctype:
+ description: The LC_CTYPE (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: lc_ctype is immutable
+ rule: self == oldSelf
+ locale:
+ description: The locale (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: locale is immutable
+ rule: self == oldSelf
+ locale_provider:
+ description: The locale provider (cannot be changed)
+ type: string
+ x-kubernetes-validations:
+ - message: locale_provider is immutable
+ rule: self == oldSelf
name:
description: The name inside PostgreSQL
type: string
x-kubernetes-validations:
- message: name is immutable
rule: self == oldSelf
+ - message: the name postgres is reserved
+ rule: self != 'postgres'
+ - message: the name template0 is reserved
+ rule: self != 'template0'
+ - message: the name template1 is reserved
+ rule: self != 'template1'
owner:
description: The owner
type: string
diff --git a/docs/src/bootstrap.md b/docs/src/bootstrap.md
index 3cf3cb41b1..87525b4679 100644
--- a/docs/src/bootstrap.md
+++ b/docs/src/bootstrap.md
@@ -389,44 +389,59 @@ to the ["Recovery" section](recovery.md).
### Bootstrap from a live cluster (`pg_basebackup`)
-The `pg_basebackup` bootstrap mode lets you create a new cluster (*target*) as
-an exact physical copy of an existing and **binary compatible** PostgreSQL
-instance (*source*), through a valid *streaming replication* connection.
-The source instance can be either a primary or a standby PostgreSQL server.
+The `pg_basebackup` bootstrap mode allows you to create a new cluster
+(*target*) as an exact physical copy of an existing and **binary-compatible**
+PostgreSQL instance (*source*) managed by CloudNativePG, using a valid
+*streaming replication* connection. The source instance can either be a primary
+or a standby PostgreSQL server. It’s crucial to thoroughly review the
+requirements section below, as the pros and cons of PostgreSQL physical
+replication fully apply.
+
+The primary use cases for this method include:
+
+- Reporting and business intelligence clusters that need to be regenerated
+ periodically (daily, weekly)
+- Test databases containing live data that require periodic regeneration
+ (daily, weekly, monthly) and anonymization
+- Rapid spin-up of a standalone replica cluster
+- Physical migrations of CloudNativePG clusters to different namespaces or
+ Kubernetes clusters
-The primary use case for this method is represented by **migrations** to CloudNativePG,
-either from outside Kubernetes or within Kubernetes (e.g., from another operator).
+!!! Important
+ Avoid using this method, based on physical replication, to migrate an
+ existing PostgreSQL cluster outside of Kubernetes into CloudNativePG unless you
+ are completely certain that all requirements are met and the operation has been
+ thoroughly tested. The CloudNativePG community does not endorse this approach
+ for such use cases and recommends using logical import instead. It is
+ exceedingly rare that all requirements for physical replication are met in a
+ way that seamlessly works with CloudNativePG.
!!! Warning
- The current implementation creates a *snapshot* of the origin PostgreSQL
- instance when the cloning process terminates and immediately starts
- the created cluster. See ["Current limitations"](#current-limitations) below for details.
-
-Similar to the case of the `recovery` bootstrap method, once the clone operation
-completes, the operator will take ownership of the target cluster, starting from
-the first instance. This includes overriding some configuration parameters, as
-required by CloudNativePG, resetting the superuser password, creating
-the `streaming_replica` user, managing the replicas, and so on. The resulting
-cluster will be completely independent of the source instance.
+ In its current implementation, this method clones the source PostgreSQL
+ instance, thereby creating a *snapshot*. Once the cloning process has finished,
+ the new cluster is immediately started.
+ Refer to ["Current limitations"](#current-limitations) for more details.
+
+Similar to the `recovery` bootstrap method, once the cloning operation is
+complete, the operator takes full ownership of the target cluster, starting
+from the first instance. This includes overriding certain configuration
+parameters as required by CloudNativePG, resetting the superuser password,
+creating the `streaming_replica` user, managing replicas, and more. The
+resulting cluster operates independently from the source instance.
!!! Important
- Configuring the network between the target instance and the source instance
- goes beyond the scope of CloudNativePG documentation, as it depends
- on the actual context and environment.
+ Configuring the network connection between the target and source instances
+ lies outside the scope of CloudNativePG documentation, as it depends heavily on
+ the specific context and environment.
-The streaming replication client on the target instance, which will be
-transparently managed by `pg_basebackup`, can authenticate itself on the source
-instance in any of the following ways:
+The streaming replication client on the target instance, managed transparently
+by `pg_basebackup`, can authenticate on the source instance using one of the
+following methods:
-1. via [username/password](#usernamepassword-authentication)
-2. via [TLS client certificate](#tls-certificate-authentication)
+1. [Username/password](#usernamepassword-authentication)
+2. [TLS client certificate](#tls-certificate-authentication)
-The latter is the recommended one if you connect to a source managed
-by CloudNativePG or configured for TLS authentication.
-The first option is, however, the most common form of authentication to a
-PostgreSQL server in general, and might be the easiest way if the source
-instance is on a traditional environment outside Kubernetes.
-Both cases are explained below.
+Both authentication methods are detailed below.
#### Requirements
@@ -650,7 +665,7 @@ instance using a second connection (see the `--wal-method=stream` option for
Once the backup is completed, the new instance will be started on a new timeline
and diverge from the source.
For this reason, it is advised to stop all write operations to the source database
-before migrating to the target database in Kubernetes.
+before migrating to the target database.
!!! Important
Before you attempt a migration, you must test both the procedure
diff --git a/docs/src/cloudnative-pg.v1.md b/docs/src/cloudnative-pg.v1.md
index 868b609256..22eb5d401e 100644
--- a/docs/src/cloudnative-pg.v1.md
+++ b/docs/src/cloudnative-pg.v1.md
@@ -2314,6 +2314,48 @@ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-
The encoding (cannot be changed)
+locale
+string
+ |
+
+ The locale (cannot be changed)
+ |
+
+locale_provider
+string
+ |
+
+ The locale provider (cannot be changed)
+ |
+
+lc_collate
+string
+ |
+
+ The LC_COLLATE (cannot be changed)
+ |
+
+lc_ctype
+string
+ |
+
+ The LC_CTYPE (cannot be changed)
+ |
+
+icu_locale
+string
+ |
+
+ The ICU_LOCALE (cannot be changed)
+ |
+
+icu_rules
+string
+ |
+
+ The ICU_RULES (cannot be changed)
+ |
+
isTemplate
bool
|
diff --git a/docs/src/samples/database-example-icu.yaml b/docs/src/samples/database-example-icu.yaml
new file mode 100644
index 0000000000..7a6bba7e4d
--- /dev/null
+++ b/docs/src/samples/database-example-icu.yaml
@@ -0,0 +1,16 @@
+# NOTE: this manifest will only work properly if the Postgres version supports
+# ICU locales and rules (version 16 and newer)
+apiVersion: postgresql.cnpg.io/v1
+kind: Database
+metadata:
+ name: db-icu
+spec:
+ name: declarative-icu
+ owner: app
+ encoding: UTF8
+ locale_provider: icu
+ icu_locale: en
+ icu_rules: fr
+ template: template0
+ cluster:
+ name: cluster-example
diff --git a/go.mod b/go.mod
index d430eac802..b3734a9ea5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/cloudnative-pg/cloudnative-pg
-go 1.22.0
+go 1.23
toolchain go1.23.2
@@ -12,7 +12,7 @@ require (
github.com/cheynewallace/tabby v1.1.1
github.com/cloudnative-pg/barman-cloud v0.0.0-20240924124724-92831d48562a
github.com/cloudnative-pg/cnpg-i v0.0.0-20241001103001-7e24b2eccd50
- github.com/cloudnative-pg/machinery v0.0.0-20241001153943-0e5ba4f9a0e1
+ github.com/cloudnative-pg/machinery v0.0.0-20241007084552-267a543ce26f
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/evanphx/json-patch/v5 v5.9.0
github.com/go-logr/logr v1.4.2
@@ -27,7 +27,7 @@ require (
github.com/mitchellh/go-ps v1.0.0
github.com/onsi/ginkgo/v2 v2.20.2
github.com/onsi/gomega v1.34.2
- github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2
+ github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.1
github.com/prometheus/client_golang v1.20.4
github.com/robfig/cron v1.2.0
github.com/sethvargo/go-password v0.3.1
diff --git a/go.sum b/go.sum
index f8365e307d..92f5c0833b 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@ github.com/cloudnative-pg/barman-cloud v0.0.0-20240924124724-92831d48562a h1:0v1
github.com/cloudnative-pg/barman-cloud v0.0.0-20240924124724-92831d48562a/go.mod h1:Jm0tOp5oB7utpt8wz6RfSv31h1mThOtffjfyxVupriE=
github.com/cloudnative-pg/cnpg-i v0.0.0-20241001103001-7e24b2eccd50 h1:Rm/bbC0GNCuWth5fHVMos99RzNczbWRVBdjubh3JMPs=
github.com/cloudnative-pg/cnpg-i v0.0.0-20241001103001-7e24b2eccd50/go.mod h1:lTWPq8pluS0PSnRMwt0zShftbyssoRhTJ5zAip8unl8=
-github.com/cloudnative-pg/machinery v0.0.0-20241001153943-0e5ba4f9a0e1 h1:qrxfp0vR+zqC+L1yTdQTqRHvnLLcVk4CdWB1RwLd8UE=
-github.com/cloudnative-pg/machinery v0.0.0-20241001153943-0e5ba4f9a0e1/go.mod h1:bWp1Es5zlxElg4Z/c5f0RKOkDcyNvDHdYIvNcPQU4WM=
+github.com/cloudnative-pg/machinery v0.0.0-20241007084552-267a543ce26f h1:tdh7vyJBadzToa2pYYC5gERr35kum4N2571VWtXnkPk=
+github.com/cloudnative-pg/machinery v0.0.0-20241007084552-267a543ce26f/go.mod h1:bWp1Es5zlxElg4Z/c5f0RKOkDcyNvDHdYIvNcPQU4WM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -157,8 +157,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2 h1:6UsAv+jAevuGO2yZFU/BukV4o9NKnFMOuoouSA4G0ns=
-github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.75.2/go.mod h1:XYrdZw5dW12Cjkt4ndbeNZZTBp4UCHtW0ccR9+sTtPU=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.1 h1:XGoEXT6WTTihO+MD8MAao+YaQIH905HbK0WK2lyo28k=
+github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.77.1/go.mod h1:D0KY8md81DQKdaR/cXwnhoWB3MYYyc/UjvqE8GFkIvA=
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
diff --git a/internal/cmd/plugin/status/status.go b/internal/cmd/plugin/status/status.go
index 41489ea976..1889975c55 100644
--- a/internal/cmd/plugin/status/status.go
+++ b/internal/cmd/plugin/status/status.go
@@ -27,6 +27,7 @@ import (
"time"
"github.com/cheynewallace/tabby"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
types "github.com/cloudnative-pg/machinery/pkg/types"
"github.com/logrusorgru/aurora/v4"
corev1 "k8s.io/api/core/v1"
@@ -44,7 +45,6 @@ import (
"github.com/cloudnative-pg/cloudnative-pg/pkg/postgres"
"github.com/cloudnative-pg/cloudnative-pg/pkg/reconciler/hibernation"
"github.com/cloudnative-pg/cloudnative-pg/pkg/specs"
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
)
diff --git a/internal/management/controller/database_controller_sql.go b/internal/management/controller/database_controller_sql.go
index 1cf32d83a0..cd01a4f926 100644
--- a/internal/management/controller/database_controller_sql.go
+++ b/internal/management/controller/database_controller_sql.go
@@ -20,7 +20,9 @@ import (
"context"
"database/sql"
"fmt"
+ "strings"
+ "github.com/cloudnative-pg/machinery/pkg/log"
"github.com/jackc/pgx/v5"
apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
@@ -56,27 +58,54 @@ func createDatabase(
db *sql.DB,
obj *apiv1.Database,
) error {
- sqlCreateDatabase := fmt.Sprintf("CREATE DATABASE %s ", pgx.Identifier{obj.Spec.Name}.Sanitize())
+ var sqlCreateDatabase strings.Builder
+ sqlCreateDatabase.WriteString(fmt.Sprintf("CREATE DATABASE %s ", pgx.Identifier{obj.Spec.Name}.Sanitize()))
if len(obj.Spec.Owner) > 0 {
- sqlCreateDatabase += fmt.Sprintf(" OWNER %s", pgx.Identifier{obj.Spec.Owner}.Sanitize())
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" OWNER %s", pgx.Identifier{obj.Spec.Owner}.Sanitize()))
}
if len(obj.Spec.Template) > 0 {
- sqlCreateDatabase += fmt.Sprintf(" TEMPLATE %s", pgx.Identifier{obj.Spec.Template}.Sanitize())
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" TEMPLATE %s", pgx.Identifier{obj.Spec.Template}.Sanitize()))
}
if len(obj.Spec.Tablespace) > 0 {
- sqlCreateDatabase += fmt.Sprintf(" TABLESPACE %s", pgx.Identifier{obj.Spec.Tablespace}.Sanitize())
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" TABLESPACE %s", pgx.Identifier{obj.Spec.Tablespace}.Sanitize()))
}
if obj.Spec.AllowConnections != nil {
- sqlCreateDatabase += fmt.Sprintf(" ALLOW_CONNECTIONS %v", *obj.Spec.AllowConnections)
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" ALLOW_CONNECTIONS %v", *obj.Spec.AllowConnections))
}
if obj.Spec.ConnectionLimit != nil {
- sqlCreateDatabase += fmt.Sprintf(" CONNECTION LIMIT %v", *obj.Spec.ConnectionLimit)
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" CONNECTION LIMIT %v", *obj.Spec.ConnectionLimit))
}
if obj.Spec.IsTemplate != nil {
- sqlCreateDatabase += fmt.Sprintf(" IS_TEMPLATE %v", *obj.Spec.IsTemplate)
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" IS_TEMPLATE %v", *obj.Spec.IsTemplate))
+ }
+ if obj.Spec.Encoding != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" ENCODING %s", pgx.Identifier{obj.Spec.Encoding}.Sanitize()))
+ }
+ if obj.Spec.Locale != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" LOCALE %s", pgx.Identifier{obj.Spec.Locale}.Sanitize()))
+ }
+ if obj.Spec.LocaleProvider != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" LOCALE_PROVIDER %s", pgx.Identifier{obj.Spec.LocaleProvider}.Sanitize()))
+ }
+ if obj.Spec.LcCollate != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" LC_COLLATE %s", pgx.Identifier{obj.Spec.LcCollate}.Sanitize()))
+ }
+ if obj.Spec.LcCtype != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" LC_CTYPE %s", pgx.Identifier{obj.Spec.LcCtype}.Sanitize()))
+ }
+ if obj.Spec.IcuLocale != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" ICU_LOCALE %s", pgx.Identifier{obj.Spec.IcuLocale}.Sanitize()))
+ }
+ if obj.Spec.IcuRules != "" {
+ sqlCreateDatabase.WriteString(fmt.Sprintf(" ICU_RULES %s", pgx.Identifier{obj.Spec.IcuRules}.Sanitize()))
}
- _, err := db.ExecContext(ctx, sqlCreateDatabase)
+ contextLogger, ctx := log.SetupLogger(ctx)
+
+ _, err := db.ExecContext(ctx, sqlCreateDatabase.String())
+ if err != nil {
+ contextLogger.Error(err, "while creating database", "query", sqlCreateDatabase.String())
+ }
return err
}
@@ -86,6 +115,8 @@ func updateDatabase(
db *sql.DB,
obj *apiv1.Database,
) error {
+ contextLogger, ctx := log.SetupLogger(ctx)
+
if obj.Spec.AllowConnections != nil {
changeAllowConnectionsSQL := fmt.Sprintf(
"ALTER DATABASE %s WITH ALLOW_CONNECTIONS %v",
@@ -93,6 +124,7 @@ func updateDatabase(
*obj.Spec.AllowConnections)
if _, err := db.ExecContext(ctx, changeAllowConnectionsSQL); err != nil {
+ contextLogger.Error(err, "while altering database", "query", changeAllowConnectionsSQL)
return fmt.Errorf("while altering database %q with allow_connections %t: %w",
obj.Spec.Name, *obj.Spec.AllowConnections, err)
}
@@ -105,6 +137,7 @@ func updateDatabase(
*obj.Spec.ConnectionLimit)
if _, err := db.ExecContext(ctx, changeConnectionsLimitSQL); err != nil {
+ contextLogger.Error(err, "while altering database", "query", changeConnectionsLimitSQL)
return fmt.Errorf("while altering database %q with connection limit %d: %w",
obj.Spec.Name, *obj.Spec.ConnectionLimit, err)
}
@@ -117,6 +150,7 @@ func updateDatabase(
*obj.Spec.IsTemplate)
if _, err := db.ExecContext(ctx, changeIsTemplateSQL); err != nil {
+ contextLogger.Error(err, "while altering database", "query", changeIsTemplateSQL)
return fmt.Errorf("while altering database %q with is_template %t: %w",
obj.Spec.Name, *obj.Spec.IsTemplate, err)
}
@@ -129,6 +163,7 @@ func updateDatabase(
pgx.Identifier{obj.Spec.Owner}.Sanitize())
if _, err := db.ExecContext(ctx, changeOwnerSQL); err != nil {
+ contextLogger.Error(err, "while altering database", "query", changeOwnerSQL)
return fmt.Errorf("while altering database %q owner %s to: %w",
obj.Spec.Name, obj.Spec.Owner, err)
}
@@ -141,6 +176,7 @@ func updateDatabase(
pgx.Identifier{obj.Spec.Tablespace}.Sanitize())
if _, err := db.ExecContext(ctx, changeTablespaceSQL); err != nil {
+ contextLogger.Error(err, "while altering database", "query", changeTablespaceSQL)
return fmt.Errorf("while altering database %q tablespace %s: %w",
obj.Spec.Name, obj.Spec.Tablespace, err)
}
@@ -154,11 +190,13 @@ func dropDatabase(
db *sql.DB,
obj *apiv1.Database,
) error {
+ contextLogger, ctx := log.SetupLogger(ctx)
+ query := fmt.Sprintf("DROP DATABASE IF EXISTS %s", pgx.Identifier{obj.Spec.Name}.Sanitize())
_, err := db.ExecContext(
ctx,
- fmt.Sprintf("DROP DATABASE IF EXISTS %s", pgx.Identifier{obj.Spec.Name}.Sanitize()),
- )
+ query)
if err != nil {
+ contextLogger.Error(err, "while dropping database", "query", query)
return fmt.Errorf("while dropping database %q: %w", obj.Spec.Name, err)
}
diff --git a/internal/management/controller/database_controller_sql_test.go b/internal/management/controller/database_controller_sql_test.go
index 444267b36e..b95a13e076 100644
--- a/internal/management/controller/database_controller_sql_test.go
+++ b/internal/management/controller/database_controller_sql_test.go
@@ -107,6 +107,32 @@ var _ = Describe("Managed Database SQL", func() {
err = createDatabase(ctx, db, database)
Expect(err).ToNot(HaveOccurred())
})
+
+ It("should create a new Database with locale and encoding kind fields", func(ctx SpecContext) {
+ database.Spec.Locale = "POSIX"
+ database.Spec.LocaleProvider = "icu"
+ database.Spec.LcCtype = "en_US.utf8"
+ database.Spec.LcCollate = "C"
+ database.Spec.Encoding = "LATIN1"
+ database.Spec.IcuLocale = "en"
+ database.Spec.IcuRules = "fr"
+
+ expectedValue := sqlmock.NewResult(0, 1)
+ expectedQuery := fmt.Sprintf(
+ "CREATE DATABASE %s OWNER %s "+
+ "ENCODING %s LOCALE %s LOCALE_PROVIDER %s LC_COLLATE %s LC_CTYPE %s "+
+ "ICU_LOCALE %s ICU_RULES %s",
+ pgx.Identifier{database.Spec.Name}.Sanitize(), pgx.Identifier{database.Spec.Owner}.Sanitize(),
+ pgx.Identifier{database.Spec.Encoding}.Sanitize(), pgx.Identifier{database.Spec.Locale}.Sanitize(),
+ pgx.Identifier{database.Spec.LocaleProvider}.Sanitize(), pgx.Identifier{database.Spec.LcCollate}.Sanitize(),
+ pgx.Identifier{database.Spec.LcCtype}.Sanitize(),
+ pgx.Identifier{database.Spec.IcuLocale}.Sanitize(), pgx.Identifier{database.Spec.IcuRules}.Sanitize(),
+ )
+ dbMock.ExpectExec(expectedQuery).WillReturnResult(expectedValue)
+
+ err = createDatabase(ctx, db, database)
+ Expect(err).ToNot(HaveOccurred())
+ })
})
Context("updateDatabase", func() {
diff --git a/pkg/configfile/configfile.go b/pkg/configfile/configfile.go
index 14ac64bcc1..9b5aa1b584 100644
--- a/pkg/configfile/configfile.go
+++ b/pkg/configfile/configfile.go
@@ -23,9 +23,8 @@ import (
"strings"
"github.com/cloudnative-pg/machinery/pkg/fileutils"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
"github.com/lib/pq"
-
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
)
// UpdatePostgresConfigurationFile search and replace options in a Postgres configuration file.
diff --git a/pkg/management/postgres/webserver/local.go b/pkg/management/postgres/webserver/local.go
index 89579382d6..61e99860e8 100644
--- a/pkg/management/postgres/webserver/local.go
+++ b/pkg/management/postgres/webserver/local.go
@@ -227,7 +227,5 @@ func (ws *localWebserverEndpoints) startPluginBackup(
cluster *apiv1.Cluster,
backup *apiv1.Backup,
) {
- cmd := NewPluginBackupCommand(cluster, backup, ws.typedClient, ws.eventRecorder)
- cmd.Start(ctx)
- cmd.Close()
+ NewPluginBackupCommand(cluster, backup, ws.typedClient, ws.eventRecorder).Start(ctx)
}
diff --git a/pkg/management/postgres/webserver/plugin_backup.go b/pkg/management/postgres/webserver/plugin_backup.go
index bbd1c993bd..0c0d18acdf 100644
--- a/pkg/management/postgres/webserver/plugin_backup.go
+++ b/pkg/management/postgres/webserver/plugin_backup.go
@@ -43,8 +43,6 @@ type PluginBackupCommand struct {
Backup *apiv1.Backup
Client client.Client
Recorder record.EventRecorder
- Log log.Logger
- Plugins repository.Interface
}
// NewPluginBackupCommand initializes a BackupCommand object, taking a physical
@@ -55,23 +53,13 @@ func NewPluginBackupCommand(
client client.Client,
recorder record.EventRecorder,
) *PluginBackupCommand {
- logger := log.WithValues(
- "pluginConfiguration", backup.Spec.PluginConfiguration,
- "backupName", backup.Name,
- "backupNamespace", backup.Name)
-
- plugins := repository.New()
- if err := plugins.RegisterUnixSocketPluginsInPath(configuration.Current.PluginSocketDir); err != nil {
- logger.Error(err, "Error while discovering plugins")
- }
+ backup.EnsureGVKIsPresent()
return &PluginBackupCommand{
Cluster: cluster,
Backup: backup,
Client: client,
Recorder: recorder,
- Log: logger,
- Plugins: plugins,
}
}
@@ -80,31 +68,33 @@ func (b *PluginBackupCommand) Start(ctx context.Context) {
go b.invokeStart(ctx)
}
-// Close closes all the connections to the plugins
-func (b *PluginBackupCommand) Close() {
- b.Plugins.Close()
-}
-
func (b *PluginBackupCommand) invokeStart(ctx context.Context) {
- backupLog := b.Log.WithValues(
+ contextLogger := log.FromContext(ctx).WithValues(
+ "pluginConfiguration", b.Backup.Spec.PluginConfiguration,
"backupName", b.Backup.Name,
"backupNamespace", b.Backup.Name)
- cli, err := pluginClient.WithPlugins(ctx, b.Plugins, b.Cluster.Spec.Plugins.GetEnabledPluginNames()...)
+ plugins := repository.New()
+ if err := plugins.RegisterUnixSocketPluginsInPath(configuration.Current.PluginSocketDir); err != nil {
+ contextLogger.Error(err, "Error while discovering plugins")
+ }
+ defer plugins.Close()
+
+ cli, err := pluginClient.WithPlugins(ctx, plugins, b.Cluster.Spec.Plugins.GetEnabledPluginNames()...)
if err != nil {
b.markBackupAsFailed(ctx, err)
return
}
// record the backup beginning
- backupLog.Info("Plugin backup started")
+ contextLogger.Info("Plugin backup started")
b.Recorder.Event(b.Backup, "Normal", "Starting", "Backup started")
// Update backup status in cluster conditions on startup
if err := b.retryWithRefreshedCluster(ctx, func() error {
return conditions.Patch(ctx, b.Client, b.Cluster, apiv1.BackupStartingCondition)
}); err != nil {
- backupLog.Error(err, "Error changing backup condition (backup started)")
+ contextLogger.Error(err, "Error changing backup condition (backup started)")
// We do not terminate here because we could still have a good backup
// even if we are unable to communicate with the Kubernetes API server
}
@@ -120,7 +110,7 @@ func (b *PluginBackupCommand) invokeStart(ctx context.Context) {
return
}
- backupLog.Info("Backup completed")
+ contextLogger.Info("Backup completed")
b.Recorder.Event(b.Backup, "Normal", "Completed", "Backup completed")
// Set the status to completed
@@ -146,28 +136,30 @@ func (b *PluginBackupCommand) invokeStart(ctx context.Context) {
}
if err := postgres.PatchBackupStatusAndRetry(ctx, b.Client, b.Backup); err != nil {
- backupLog.Error(err, "Can't set backup status as completed")
+ contextLogger.Error(err, "Can't set backup status as completed")
}
// Update backup status in cluster conditions on backup completion
if err := b.retryWithRefreshedCluster(ctx, func() error {
return conditions.Patch(ctx, b.Client, b.Cluster, apiv1.BackupSucceededCondition)
}); err != nil {
- b.Log.Error(err, "Can't update the cluster with the completed backup data")
+ contextLogger.Error(err, "Can't update the cluster with the completed backup data")
}
}
func (b *PluginBackupCommand) markBackupAsFailed(ctx context.Context, failure error) {
+ contextLogger := log.FromContext(ctx)
+
backupStatus := b.Backup.GetStatus()
// record the failure
- b.Log.Error(failure, "Backup failed")
+ contextLogger.Error(failure, "Backup failed")
b.Recorder.Event(b.Backup, "Normal", "Failed", "Backup failed")
// update backup status as failed
backupStatus.SetAsFailed(failure)
if err := postgres.PatchBackupStatusAndRetry(ctx, b.Client, b.Backup); err != nil {
- b.Log.Error(err, "Can't mark backup as failed")
+ contextLogger.Error(err, "Can't mark backup as failed")
// We do not terminate here because we still want to set the condition on the cluster.
}
@@ -180,7 +172,7 @@ func (b *PluginBackupCommand) markBackupAsFailed(ctx context.Context, failure er
b.Cluster.Status.LastFailedBackup = utils.GetCurrentTimestampWithFormat(time.RFC3339)
return b.Client.Status().Patch(ctx, b.Cluster, client.MergeFrom(origCluster))
}); failErr != nil {
- b.Log.Error(failErr, "while setting cluster condition for failed backup")
+ contextLogger.Error(failErr, "while setting cluster condition for failed backup")
}
}
diff --git a/pkg/multicache/multinamespaced_cache.go b/pkg/multicache/multinamespaced_cache.go
index 80f69eecfb..a3222085db 100644
--- a/pkg/multicache/multinamespaced_cache.go
+++ b/pkg/multicache/multinamespaced_cache.go
@@ -24,12 +24,11 @@ import (
"fmt"
"github.com/cloudnative-pg/machinery/pkg/log"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
)
type multiNamespaceCache struct {
diff --git a/pkg/specs/roles.go b/pkg/specs/roles.go
index e48a3b6ad4..f0d9bf4cb1 100644
--- a/pkg/specs/roles.go
+++ b/pkg/specs/roles.go
@@ -19,11 +19,11 @@ package specs
import (
"slices"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
)
// CreateRole create a role with the permissions needed by the instance manager
diff --git a/pkg/stringset/stringset.go b/pkg/stringset/stringset.go
deleted file mode 100644
index f5678ec4a0..0000000000
--- a/pkg/stringset/stringset.go
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-Copyright The CloudNativePG Contributors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package stringset implements a basic set of strings
-package stringset
-
-import (
- "slices"
-)
-
-// Data represent a set of strings
-type Data struct {
- innerMap map[string]struct{}
-}
-
-// New create a new empty set of strings
-func New() *Data {
- return &Data{
- innerMap: make(map[string]struct{}),
- }
-}
-
-// From create a empty set of strings given
-// a slice of strings
-func From(strings []string) *Data {
- result := New()
- for _, value := range strings {
- result.Put(value)
- }
- return result
-}
-
-// FromKeys create a string set from the
-// keys of a map
-func FromKeys[T any](v map[string]T) *Data {
- result := New()
- for key := range v {
- result.Put(key)
- }
- return result
-}
-
-// Put a string in the set
-func (set *Data) Put(key string) {
- set.innerMap[key] = struct{}{}
-}
-
-// Delete deletes a string from the set. If the string doesn't exist
-// this is a no-op
-func (set *Data) Delete(key string) {
- delete(set.innerMap, key)
-}
-
-// Has checks if a string is in the set or not
-func (set *Data) Has(key string) bool {
- _, ok := set.innerMap[key]
- return ok
-}
-
-// Len returns the map of the set
-func (set *Data) Len() int {
- return len(set.innerMap)
-}
-
-// ToList returns the strings contained in this set as
-// a string slice
-func (set *Data) ToList() (result []string) {
- result = make([]string, 0, len(set.innerMap))
- for key := range set.innerMap {
- result = append(result, key)
- }
- return
-}
-
-// ToSortedList returns the string container in this set
-// as a sorted string slice
-func (set *Data) ToSortedList() []string {
- result := set.ToList()
- slices.Sort(result)
- return result
-}
-
-// Eq compares two string sets for equality
-func (set *Data) Eq(other *Data) bool {
- if set == nil || other == nil {
- return false
- }
-
- if set.Len() != other.Len() {
- return false
- }
-
- for key := range set.innerMap {
- if !other.Has(key) {
- return false
- }
- }
-
- return true
-}
diff --git a/pkg/stringset/stringset_test.go b/pkg/stringset/stringset_test.go
deleted file mode 100644
index abf6e5548b..0000000000
--- a/pkg/stringset/stringset_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
-Copyright The CloudNativePG Contributors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package stringset
-
-import (
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-var _ = Describe("String set", func() {
- It("starts as an empty set", func() {
- Expect(New().Len()).To(Equal(0))
- })
-
- It("starts with a list of strings", func() {
- Expect(From([]string{"one", "two"}).Len()).To(Equal(2))
- Expect(From([]string{"one", "two", "two"}).Len()).To(Equal(2))
- })
-
- It("store string keys", func() {
- set := New()
- Expect(set.Has("test")).To(BeFalse())
- Expect(set.Has("test2")).To(BeFalse())
-
- set.Put("test")
- Expect(set.Has("test")).To(BeTrue())
- Expect(set.Has("test2")).To(BeFalse())
- })
-
- It("removes string keys", func() {
- set := From([]string{"one", "two"})
- set.Delete("one")
- Expect(set.ToList()).To(Equal([]string{"two"}))
- })
-
- It("constructs a string slice given a set", func() {
- Expect(From([]string{"one", "two"}).ToList()).To(ContainElements("one", "two"))
- })
-
- It("compares two string set for equality", func() {
- Expect(From([]string{"one", "two"}).Eq(From([]string{"one", "two"}))).To(BeTrue())
- Expect(From([]string{"one", "two"}).Eq(From([]string{"two", "three"}))).To(BeFalse())
- Expect(From([]string{"one", "two"}).Eq(From([]string{"one", "two", "three"}))).To(BeFalse())
- Expect(From([]string{"one", "two", "three"}).Eq(From([]string{"one", "two"}))).To(BeFalse())
- })
-
- It("constructs a sorted string slice given a set", func() {
- Expect(From([]string{"one", "two", "three", "four"}).ToSortedList()).To(
- HaveExactElements("four", "one", "three", "two"))
- Expect(New().ToList()).To(BeEmpty())
- })
-
- It("constructs a string set from a map having string as keys", func() {
- Expect(FromKeys(map[string]int{
- "one": 1,
- "two": 2,
- "three": 3,
- }).ToSortedList()).To(
- HaveExactElements("one", "three", "two"),
- )
- })
-})
diff --git a/pkg/stringset/suite_test.go b/pkg/stringset/suite_test.go
deleted file mode 100644
index bb29e64601..0000000000
--- a/pkg/stringset/suite_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-Copyright The CloudNativePG Contributors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package stringset
-
-import (
- "testing"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
-)
-
-// These tests use Ginkgo (BDD-style Go testing framework). Refer to
-// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
-
-func TestConfigFile(t *testing.T) {
- RegisterFailHandler(Fail)
-
- RunSpecs(t, "Configuration File Parsing Suite")
-}
diff --git a/pkg/utils/fencing.go b/pkg/utils/fencing.go
index 7cedb1cbf5..c7ec6c37aa 100644
--- a/pkg/utils/fencing.go
+++ b/pkg/utils/fencing.go
@@ -24,12 +24,11 @@ import (
"slices"
"sort"
+ "github.com/cloudnative-pg/machinery/pkg/stringset"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/cloudnative-pg/cloudnative-pg/pkg/stringset"
)
var (
diff --git a/tests/e2e/declarative_database_management_test.go b/tests/e2e/declarative_database_management_test.go
index aee596b3cc..ec702a6084 100644
--- a/tests/e2e/declarative_database_management_test.go
+++ b/tests/e2e/declarative_database_management_test.go
@@ -17,6 +17,7 @@ limitations under the License.
package e2e
import (
+ "fmt"
"time"
"k8s.io/apimachinery/pkg/types"
@@ -50,19 +51,17 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
Context("plain vanilla cluster", Ordered, func() {
const (
- namespacePrefix = "declarative-db"
- databaseCrdName = "db-declarative"
- databaseWithDeleteRetainPolicyCrdName = "db-declarative-delete"
- dbname = "declarative"
+ namespacePrefix = "declarative-db"
+ dbname = "declarative"
)
var (
- clusterName, namespace string
- database *apiv1.Database
- databaseWithDeleteRetainPolicy *apiv1.Database
+ clusterName, namespace, databaseObjectName string
+ database *apiv1.Database
+ databaseWithDeleteRetainPolicy *apiv1.Database
+ err error
)
BeforeAll(func() {
- var err error
// Create a cluster in a namespace we'll delete after the test
namespace, err = env.CreateUniqueTestNamespace(namespacePrefix)
Expect(err).ToNot(HaveOccurred())
@@ -93,11 +92,28 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
}, 300).Should(Succeed())
}
+ assertDatabaseHasExpectedFields := func(namespace, primaryPod string, db apiv1.Database) {
+ query := fmt.Sprintf("select count(*) from pg_database where datname = '%s' "+
+ "and encoding = %s and datctype = '%s' and datcollate = '%s'",
+ db.Spec.Name, db.Spec.Encoding, db.Spec.LcCtype, db.Spec.LcCollate)
+ Eventually(func(g Gomega) {
+ stdout, _, err := env.ExecQueryInInstancePod(
+ utils.PodLocator{
+ Namespace: namespace,
+ PodName: primaryPod,
+ },
+ "postgres",
+ query)
+ g.Expect(err).ToNot(HaveOccurred())
+ g.Expect(stdout).Should(ContainSubstring("1"))
+ }, 30).Should(Succeed())
+ }
+
When("Database CRD reclaim policy is set to retain (default) inside spec", func() {
It("can add a declarative database", func() {
By("applying Database CRD manifest", func() {
CreateResourceFromFile(namespace, databaseManifest)
- _, err := env.GetResourceNameFromYAML(databaseManifest)
+ databaseObjectName, err = env.GetResourceNameFromYAML(databaseManifest)
Expect(err).NotTo(HaveOccurred())
})
By("ensuring the Database CRD succeeded reconciliation", func() {
@@ -105,7 +121,7 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
database = &apiv1.Database{}
databaseNamespacedName := types.NamespacedName{
Namespace: namespace,
- Name: databaseCrdName,
+ Name: databaseObjectName,
}
Eventually(func(g Gomega) {
@@ -115,11 +131,25 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
}, 300).WithPolling(10 * time.Second).Should(Succeed())
})
- By("verifying the db exists", func() {
+ By("verifying new database has been created with the expected fields", func() {
primaryPodInfo, err := env.GetClusterPrimary(namespace, clusterName)
Expect(err).ToNot(HaveOccurred())
assertDatabaseExists(namespace, primaryPodInfo.Name, dbname, true)
+
+ // NOTE: the `pg_database` table in Postgres does not contain fields
+ // for the owner nor the template.
+ // Its fields are dependent on the version of Postgres, so we pick
+ // a subset that is available to check even on PG v12
+ expectedDatabaseFields := apiv1.Database{
+ Spec: apiv1.DatabaseSpec{
+ Name: "declarative",
+ LcCtype: "en_US.utf8",
+ LcCollate: "C", // this is the default value
+ Encoding: "0", // corresponds to SQL_ASCII
+ },
+ }
+ assertDatabaseHasExpectedFields(namespace, primaryPodInfo.Name, expectedDatabaseFields)
})
})
@@ -140,7 +170,7 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
It("can add a declarative database", func() {
By("applying Database CRD manifest", func() {
CreateResourceFromFile(namespace, databaseManifestWithDeleteReclaimPolicy)
- _, err := env.GetResourceNameFromYAML(databaseManifestWithDeleteReclaimPolicy)
+ databaseObjectName, err = env.GetResourceNameFromYAML(databaseManifestWithDeleteReclaimPolicy)
Expect(err).NotTo(HaveOccurred())
})
By("ensuring the Database CRD succeeded reconciliation", func() {
@@ -148,7 +178,7 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
databaseWithDeleteRetainPolicy = &apiv1.Database{}
databaseNamespacedName := types.NamespacedName{
Namespace: namespace,
- Name: databaseWithDeleteRetainPolicyCrdName,
+ Name: databaseObjectName,
}
Eventually(func(g Gomega) {
@@ -183,7 +213,7 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
"will be deleted", func() {
By("applying Database CRD manifest", func() {
CreateResourceFromFile(namespace, databaseManifestWithDeleteReclaimPolicy)
- _, err := env.GetResourceNameFromYAML(databaseManifestWithDeleteReclaimPolicy)
+ databaseObjectName, err = env.GetResourceNameFromYAML(databaseManifestWithDeleteReclaimPolicy)
Expect(err).NotTo(HaveOccurred())
})
By("ensuring the Database CRD succeeded reconciliation", func() {
@@ -191,7 +221,7 @@ var _ = Describe("Declarative databases management test", Label(tests.LabelSmoke
databaseWithDeleteRetainPolicy = &apiv1.Database{}
databaseNamespacedName := types.NamespacedName{
Namespace: namespace,
- Name: databaseWithDeleteRetainPolicyCrdName,
+ Name: databaseObjectName,
}
Eventually(func(g Gomega) {
diff --git a/tests/e2e/fixtures/declarative_databases/database.yaml.template b/tests/e2e/fixtures/declarative_databases/database.yaml.template
index afa83d0ccd..3ded03c50a 100644
--- a/tests/e2e/fixtures/declarative_databases/database.yaml.template
+++ b/tests/e2e/fixtures/declarative_databases/database.yaml.template
@@ -5,5 +5,8 @@ metadata:
spec:
name: declarative
owner: app
+ lc_ctype: "en_US.utf8"
+ encoding: SQL_ASCII
+ template: template0
cluster:
name: cluster-with-declarative-databases