From ad7e5909166d396cd41e6f842e9a7e86cadeebeb Mon Sep 17 00:00:00 2001 From: Morten Lied Johansen Date: Wed, 17 Sep 2025 15:57:36 +0200 Subject: [PATCH 1/7] Skip creation of OwnerReference if user is in a different namespace (#2912) Instead of doing a string compare on the username, check the actual namespace of the user to determine if an owner reference can be created. --- pkg/cluster/k8sres.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 4db23976c..65ca76357 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -1928,7 +1928,7 @@ func (c *Cluster) generateSingleUserSecret(pgUser spec.PgUser) *v1.Secret { // if secret lives in another namespace we cannot set ownerReferences var ownerReferences []metav1.OwnerReference - if c.Config.OpConfig.EnableCrossNamespaceSecret && strings.Contains(username, ".") { + if c.Config.OpConfig.EnableCrossNamespaceSecret && c.Postgresql.ObjectMeta.Namespace != pgUser.Namespace { ownerReferences = nil } else { ownerReferences = c.ownerReferences() From cce263319262e25167b1cdc56b092b849803e0fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:01:02 +0200 Subject: [PATCH 2/7] Bump requests from 2.32.2 to 2.32.4 in /ui (#2922) Bumps [requests](https://github.com/psf/requests) from 2.32.2 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.2...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ui/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/requirements.txt b/ui/requirements.txt index 783c0aac3..75bcc1952 100644 --- a/ui/requirements.txt +++ b/ui/requirements.txt @@ -9,6 +9,6 @@ jq==1.7.0 json_delta>=2.0.2 kubernetes==11.0.0 python-json-logger==2.0.7 -requests==2.32.2 +requests==2.32.4 stups-tokens>=1.1.19 werkzeug==3.0.6 From d98fc2753aacab063e3b6fae79fe9a1a55c0dfb9 Mon Sep 17 00:00:00 2001 From: Alexander Gramovich <34745780+ggramal@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:01:28 +0300 Subject: [PATCH 3/7] logical-backup:gcs_upload: try to use gcp metadata if LOGICAL_GOOGLE_APPLICATION_CREDENTIALS is not set (#2837) Co-authored-by: Felix Kunde --- logical-backup/dump.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/logical-backup/dump.sh b/logical-backup/dump.sh index 25641c3b5..a250670a6 100755 --- a/logical-backup/dump.sh +++ b/logical-backup/dump.sh @@ -122,7 +122,21 @@ function aws_upload { function gcs_upload { PATH_TO_BACKUP=gs://$LOGICAL_BACKUP_S3_BUCKET"/"$LOGICAL_BACKUP_S3_BUCKET_PREFIX"/"$SCOPE$LOGICAL_BACKUP_S3_BUCKET_SCOPE_SUFFIX"/logical_backups/"$(date +%s).sql.gz - gsutil -o Credentials:gs_service_key_file=$LOGICAL_BACKUP_GOOGLE_APPLICATION_CREDENTIALS cp - "$PATH_TO_BACKUP" + #Set local LOGICAL_GOOGLE_APPLICATION_CREDENTIALS to nothing or + #value of LOGICAL_GOOGLE_APPLICATION_CREDENTIALS env var. Needed + #because `set -o nounset` is globally set + local LOGICAL_BACKUP_GOOGLE_APPLICATION_CREDENTIALS=${LOGICAL_BACKUP_GOOGLE_APPLICATION_CREDENTIALS:-} + + GSUTIL_OPTIONS=("-o" "Credentials:gs_service_key_file=$LOGICAL_BACKUP_GOOGLE_APPLICATION_CREDENTIALS") + + #If GOOGLE_APPLICATION_CREDENTIALS is not set try to get + #creds from metadata + if [[ -z $LOGICAL_BACKUP_GOOGLE_APPLICATION_CREDENTIALS ]] + then + GSUTIL_OPTIONS[1]="GoogleCompute:service_account=default" + fi + + gsutil ${GSUTIL_OPTIONS[@]} cp - "$PATH_TO_BACKUP" } function upload { From bcd729b2ccbac7abdea923f77b29ae4c32df4fcd Mon Sep 17 00:00:00 2001 From: Polina Bungina <27892524+hughcapet@users.noreply.github.com> Date: Fri, 19 Sep 2025 14:44:17 +0200 Subject: [PATCH 4/7] Add selector to master service when switching to CM (#2955) Add service selector comparison to compareServices This is necessary for the proper switch of `kubernetes_use_configmaps` configuration value, as master service should have different label selector setup for those. --- pkg/cluster/cluster.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index e9a691faa..f1d9ce164 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -841,6 +841,10 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) { return false, "new service's owner references do not match the current ones" } + if !reflect.DeepEqual(old.Spec.Selector, new.Spec.Selector) { + return false, "new service's selector does not match the current one" + } + return true, "" } From dc294259692319a229e7677c16f88d3248124e08 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Tue, 23 Sep 2025 14:30:06 +0200 Subject: [PATCH 5/7] include external traffic policy comparison into service diffing (#2956) --- pkg/cluster/cluster.go | 4 ++ pkg/cluster/cluster_test.go | 77 ++++++++++++++++++++++++++++++------- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index f1d9ce164..b06386f39 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -845,6 +845,10 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) { return false, "new service's selector does not match the current one" } + if old.Spec.ExternalTrafficPolicy != new.Spec.ExternalTrafficPolicy { + return false, "new service's ExternalTrafficPolicy does not match the current one" + } + return true, "" } diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index 09d9df972..25f61db98 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -1341,14 +1341,21 @@ func TestCompareEnv(t *testing.T) { } } -func newService(ann map[string]string, svcT v1.ServiceType, lbSr []string) *v1.Service { +func newService( + annotations map[string]string, + svcType v1.ServiceType, + sourceRanges []string, + selector map[string]string, + policy v1.ServiceExternalTrafficPolicyType) *v1.Service { svc := &v1.Service{ Spec: v1.ServiceSpec{ - Type: svcT, - LoadBalancerSourceRanges: lbSr, + Selector: selector, + Type: svcType, + LoadBalancerSourceRanges: sourceRanges, + ExternalTrafficPolicy: policy, }, } - svc.Annotations = ann + svc.Annotations = annotations return svc } @@ -1365,13 +1372,18 @@ func TestCompareServices(t *testing.T) { }, } + defaultPolicy := v1.ServiceExternalTrafficPolicyTypeCluster + serviceWithOwnerReference := newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeClusterIP, - []string{"128.141.0.0/16", "137.138.0.0/16"}) + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, + defaultPolicy, + ) ownerRef := metav1.OwnerReference{ APIVersion: "acid.zalan.do/v1", @@ -1397,14 +1409,16 @@ func TestCompareServices(t *testing.T) { constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeClusterIP, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeClusterIP, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), match: true, }, { @@ -1415,14 +1429,16 @@ func TestCompareServices(t *testing.T) { constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeClusterIP, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeLoadBalancer, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), match: false, reason: `new service's type "LoadBalancer" does not match the current one "ClusterIP"`, }, @@ -1434,14 +1450,16 @@ func TestCompareServices(t *testing.T) { constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeLoadBalancer, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeLoadBalancer, - []string{"185.249.56.0/22"}), + []string{"185.249.56.0/22"}, + nil, defaultPolicy), match: false, reason: `new service's LoadBalancerSourceRange does not match the current one`, }, @@ -1453,14 +1471,16 @@ func TestCompareServices(t *testing.T) { constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeLoadBalancer, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeLoadBalancer, - []string{}), + []string{}, + nil, defaultPolicy), match: false, reason: `new service's LoadBalancerSourceRange does not match the current one`, }, @@ -1472,10 +1492,39 @@ func TestCompareServices(t *testing.T) { constants.ElbTimeoutAnnotationName: constants.ElbTimeoutAnnotationValue, }, v1.ServiceTypeClusterIP, - []string{"128.141.0.0/16", "137.138.0.0/16"}), + []string{"128.141.0.0/16", "137.138.0.0/16"}, + nil, defaultPolicy), new: serviceWithOwnerReference, match: false, }, + { + about: "new service has a label selector", + current: newService( + map[string]string{}, + v1.ServiceTypeClusterIP, + []string{}, + nil, defaultPolicy), + new: newService( + map[string]string{}, + v1.ServiceTypeClusterIP, + []string{}, + map[string]string{"cluster-name": "clstr", "spilo-role": "master"}, defaultPolicy), + match: false, + }, + { + about: "services differ on external traffic policy", + current: newService( + map[string]string{}, + v1.ServiceTypeClusterIP, + []string{}, + nil, defaultPolicy), + new: newService( + map[string]string{}, + v1.ServiceTypeClusterIP, + []string{}, + nil, v1.ServiceExternalTrafficPolicyTypeLocal), + match: false, + }, } for _, tt := range tests { From 8ba57b28f5775895a6d710ada3931cf3defd7bc8 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Tue, 14 Oct 2025 10:59:43 +0200 Subject: [PATCH 6/7] extend RBAC in prepatation to switch to configmap-based cluster management (#2961) --- docs/reference/operator_parameters.md | 9 +++++-- manifests/operator-service-account-rbac.yaml | 27 +++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 95bfb4cf3..7e7cbeaf0 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -107,8 +107,13 @@ Those are top-level keys, containing both leaf keys and groups. * **kubernetes_use_configmaps** Select if setup uses endpoints (default), or configmaps to manage leader when DCS is kubernetes (not etcd or similar). In OpenShift it is not possible to - use endpoints option, and configmaps is required. By default, - `kubernetes_use_configmaps: false`, meaning endpoints will be used. + use endpoints option, and configmaps is required. Starting with K8s 1.33, + endpoints are marked as deprecated. It's recommended to switch to config maps + instead. But, to do so make sure you scale the Postgres cluster down to just + one primary pod (e.g. using `max_instances` option). Otherwise, you risk + running into a split-brain scenario. + By default, `kubernetes_use_configmaps: false`, meaning endpoints will be used. + Starting from v1.16.0 the default will be changed to `true`. * **docker_image** Spilo Docker image for Postgres instances. For production, don't rely on the diff --git a/manifests/operator-service-account-rbac.yaml b/manifests/operator-service-account-rbac.yaml index bf27f99f1..2cc1edcd1 100644 --- a/manifests/operator-service-account-rbac.yaml +++ b/manifests/operator-service-account-rbac.yaml @@ -59,13 +59,20 @@ rules: - get - patch - update -# to read configuration from ConfigMaps +# to read configuration from ConfigMaps and help Patroni manage the cluster if endpoints are not used - apiGroups: - "" resources: - configmaps verbs: + - create + - delete + - deletecollection - get + - list + - patch + - update + - watch # to send events to the CRs - apiGroups: - "" @@ -78,7 +85,7 @@ rules: - patch - update - watch -# to manage endpoints which are also used by Patroni +# to manage endpoints which are also used by Patroni (if it is using config maps) - apiGroups: - "" resources: @@ -249,7 +256,21 @@ kind: ClusterRole metadata: name: postgres-pod rules: -# Patroni needs to watch and manage endpoints +# Patroni needs to watch and manage config maps (or endpoints) +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +# Patroni needs to watch and manage endpoints (or config maps) - apiGroups: - "" resources: From eddf521227ffbb7a0661a4bebfad4bf7fe25423f Mon Sep 17 00:00:00 2001 From: Eng Zer Jun Date: Tue, 14 Oct 2025 17:59:48 +0800 Subject: [PATCH 7/7] Replace `golang.org/x/exp` with stdlib (#2857) * Replace `golang.org/x/exp` with stdlib These experimental packages are now available in the Go standard library since Go 1.21. 1. golang.org/x/exp/slices -> slices [1] 2. golang.org/x/exp/maps -> maps [2] [1]: https://go.dev/doc/go1.21#slices [2]: https://go.dev/doc/go1.21#maps Signed-off-by: Eng Zer Jun * Run go mod tidy Signed-off-by: Eng Zer Jun --------- Signed-off-by: Eng Zer Jun Co-authored-by: Felix Kunde --- go.mod | 6 ++---- go.sum | 2 -- pkg/cluster/k8sres.go | 9 ++++----- pkg/cluster/pod.go | 3 +-- pkg/cluster/sync.go | 4 ++-- pkg/cluster/sync_test.go | 5 ++--- 6 files changed, 11 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index d047b2453..150074cf8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/zalando/postgres-operator go 1.25.0 require ( + github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.53.8 github.com/golang/mock v1.6.0 github.com/lib/pq v1.10.9 @@ -12,7 +13,6 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.41.0 - golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.30.4 k8s.io/apiextensions-apiserver v0.25.9 @@ -21,10 +21,7 @@ require ( k8s.io/code-generator v0.25.9 ) -require golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - require ( - github.com/Masterminds/semver v1.5.0 github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect @@ -62,6 +59,7 @@ require ( golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.36.0 // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 2d66aef46..cab88ce55 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= -golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 65ca76357..93d992c4b 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "maps" "path" + "slices" "sort" "strings" @@ -12,19 +14,16 @@ import ( "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" - batchv1 "k8s.io/api/batch/v1" - "k8s.io/apimachinery/pkg/labels" - acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" "github.com/zalando/postgres-operator/pkg/spec" "github.com/zalando/postgres-operator/pkg/util" diff --git a/pkg/cluster/pod.go b/pkg/cluster/pod.go index 7fc95090e..b28e03078 100644 --- a/pkg/cluster/pod.go +++ b/pkg/cluster/pod.go @@ -3,12 +3,11 @@ package cluster import ( "context" "fmt" + "slices" "sort" "strconv" "time" - "golang.org/x/exp/slices" - appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 9067c8c46..c632e598c 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "fmt" + "maps" "reflect" "regexp" + "slices" "strconv" "strings" "time" @@ -15,8 +17,6 @@ import ( "github.com/zalando/postgres-operator/pkg/util" "github.com/zalando/postgres-operator/pkg/util/constants" "github.com/zalando/postgres-operator/pkg/util/k8sutil" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" diff --git a/pkg/cluster/sync_test.go b/pkg/cluster/sync_test.go index f9d1d7873..e670daa70 100644 --- a/pkg/cluster/sync_test.go +++ b/pkg/cluster/sync_test.go @@ -2,15 +2,14 @@ package cluster import ( "bytes" + "context" "fmt" "io" "net/http" + "slices" "testing" "time" - "context" - - "golang.org/x/exp/slices" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types"