From f0b5d3725c04d7a9286200642f2f92955fcf138c Mon Sep 17 00:00:00 2001 From: Jorge Solorzano Date: Wed, 10 Jun 2026 11:32:13 +0200 Subject: [PATCH 1/8] Bump github.com/lib/pq from v1.11.2 to v1.12.3 (#3042) Co-authored-by: Felix Kunde --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e0e0b1956..9efa24150 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/aws/aws-sdk-go v1.55.8 github.com/golang/mock v1.6.0 - github.com/lib/pq v1.11.2 + github.com/lib/pq v1.12.3 github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d github.com/pkg/errors v0.9.1 github.com/r3labs/diff v1.1.0 diff --git a/go.sum b/go.sum index a1fa39389..0d0ebb7d2 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= -github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= From 873dd548ffcd1956b709e75ebe4e1eb555dcce2f Mon Sep 17 00:00:00 2001 From: Raphael Torquato <89878688+raphaeltorquat0@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:18:54 -0300 Subject: [PATCH 2/8] Add cluster_labels and annotations to logical backup CronJob and Jobs (#3085) * Add cluster_labels and annotations to logical backup CronJob and Jobs When using the logical backup feature, the CronJob and its created Jobs were missing the cluster_labels and annotations that are applied to other cluster resources. This made it difficult to filter or identify backup jobs using the same labels as other cluster components. Changes: - Added ObjectMeta with labels and annotations to JobTemplateSpec - Updated CronJob ObjectMeta to use the merged labels (including 'application: spilo-logical-backup') - Updated tests to expect the new labels --- e2e/tests/k8s_api.py | 2 +- pkg/cluster/cluster.go | 10 ++++++++++ pkg/cluster/k8sres.go | 8 ++++++-- pkg/cluster/k8sres_test.go | 12 ++++++------ pkg/cluster/sync.go | 10 ++++++++++ pkg/cluster/util.go | 8 ++++++++ 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 1f42ad4bc..0ef3d6315 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -240,7 +240,7 @@ class K8s: time.sleep(self.RETRY_TIMEOUT_SEC) def get_logical_backup_job(self, namespace='default'): - return self.api.batch_v1.list_namespaced_cron_job(namespace, label_selector="application=spilo") + return self.api.batch_v1.list_namespaced_cron_job(namespace, label_selector="application=spilo-logical-backup") def wait_for_logical_backup_job(self, expected_num_of_jobs): while (len(self.get_logical_backup_job().items) != expected_num_of_jobs): diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index b7a7b8e56..95f16d922 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -893,6 +893,16 @@ func (c *Cluster) compareLogicalBackupJob(cur, new *batchv1.CronJob) *compareLog reasons = append(reasons, fmt.Sprintf("new job's env PG_VERSION %q does not match the current one %q", newPgVersion, curPgVersion)) } + if !reflect.DeepEqual(cur.Labels, new.Labels) { + match = false + reasons = append(reasons, "new job's labels do not match the current ones") + } + + if !reflect.DeepEqual(cur.Spec.JobTemplate.Labels, new.Spec.JobTemplate.Labels) { + match = false + reasons = append(reasons, "new job's template labels do not match the current ones") + } + needsReplace := false contReasons := make([]string, 0) needsReplace, contReasons = c.compareContainers("cronjob container", cur.Spec.JobTemplate.Spec.Template.Spec.Containers, new.Spec.JobTemplate.Spec.Template.Spec.Containers, needsReplace, contReasons) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 866eeb752..8d3a40d9a 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -2414,6 +2414,10 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { // configure a cron job jobTemplateSpec := batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: c.annotationsSet(annotations), + }, Spec: jobSpec, } @@ -2426,8 +2430,8 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1.CronJob, error) { ObjectMeta: metav1.ObjectMeta{ Name: c.getLogicalBackupJobName(), Namespace: c.Namespace, - Labels: c.labelsSet(true), - Annotations: c.annotationsSet(nil), + Labels: labels, + Annotations: c.annotationsSet(annotations), OwnerReferences: c.ownerReferences(), }, Spec: batchv1.CronJobSpec{ diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 2010de067..ef408da4d 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -3875,7 +3875,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("100m"), Memory: k8sutil.StringToPointer("100Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("500Mi")}, }, - expectedLabel: map[string]string{configResources.ClusterNameLabel: clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", configResources.ClusterNameLabel: clusterName, "team": teamId}, expectedAnnotation: nil, }, { @@ -3900,7 +3900,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("10m"), Memory: k8sutil.StringToPointer("50Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("300m"), Memory: k8sutil.StringToPointer("300Mi")}, }, - expectedLabel: map[string]string{configResources.ClusterNameLabel: clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", configResources.ClusterNameLabel: clusterName, "team": teamId}, expectedAnnotation: nil, }, { @@ -3923,7 +3923,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("50m"), Memory: k8sutil.StringToPointer("100Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("250m"), Memory: k8sutil.StringToPointer("500Mi")}, }, - expectedLabel: map[string]string{configResources.ClusterNameLabel: clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", configResources.ClusterNameLabel: clusterName, "team": teamId}, expectedAnnotation: nil, }, { @@ -3946,7 +3946,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("100m"), Memory: k8sutil.StringToPointer("200Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("200Mi")}, }, - expectedLabel: map[string]string{configResources.ClusterNameLabel: clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", configResources.ClusterNameLabel: clusterName, "team": teamId}, expectedAnnotation: nil, }, { @@ -3968,7 +3968,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("100m"), Memory: k8sutil.StringToPointer("100Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("500Mi")}, }, - expectedLabel: map[string]string{"labelKey": "labelValue", "cluster-name": clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", "labelKey": "labelValue", "cluster-name": clusterName, "team": teamId}, expectedAnnotation: nil, }, { @@ -3990,7 +3990,7 @@ func TestGenerateLogicalBackupJob(t *testing.T) { ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("100m"), Memory: k8sutil.StringToPointer("100Mi")}, ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("500Mi")}, }, - expectedLabel: map[string]string{configResources.ClusterNameLabel: clusterName, "team": teamId}, + expectedLabel: map[string]string{"application": "spilo-logical-backup", configResources.ClusterNameLabel: clusterName, "team": teamId}, expectedAnnotation: map[string]string{"annotationKey": "annotationValue"}, }, } diff --git a/pkg/cluster/sync.go b/pkg/cluster/sync.go index 7c478477a..e15b5fedc 100644 --- a/pkg/cluster/sync.go +++ b/pkg/cluster/sync.go @@ -1769,6 +1769,16 @@ func (c *Cluster) syncLogicalBackupJob() error { } c.logger.Info("the logical backup job is synced") } + if !reflect.DeepEqual(job.Labels, desiredJob.Labels) { + patchData, err := metaLabelsPatch(desiredJob.Labels) + if err != nil { + return fmt.Errorf("could not form patch for the logical backup job %q labels: %v", jobName, err) + } + _, err = c.KubeClient.CronJobs(c.Namespace).Patch(context.TODO(), jobName, types.MergePatchType, []byte(patchData), metav1.PatchOptions{}) + if err != nil { + return fmt.Errorf("could not patch labels of the logical backup job %q: %v", jobName, err) + } + } if changed, _ := c.compareAnnotations(job.Annotations, desiredJob.Annotations, nil); changed { patchData, err := metaAnnotationsPatch(desiredJob.Annotations) if err != nil { diff --git a/pkg/cluster/util.go b/pkg/cluster/util.go index 9c830129d..cbcccd16e 100644 --- a/pkg/cluster/util.go +++ b/pkg/cluster/util.go @@ -167,6 +167,14 @@ func metaAnnotationsPatch(annotations map[string]string) ([]byte, error) { }{&meta}) } +func metaLabelsPatch(labels map[string]string) ([]byte, error) { + var meta metav1.ObjectMeta + meta.Labels = labels + return json.Marshal(struct { + ObjMeta interface{} `json:"metadata"` + }{&meta}) +} + func (c *Cluster) logPDBChanges(old, new *policyv1.PodDisruptionBudget, isUpdate bool, reason string) { if isUpdate { c.logger.Infof("pod disruption budget %q has been changed", util.NameFromMeta(old.ObjectMeta)) From ab740cf5e5cc9db973c93480345f262794bfad60 Mon Sep 17 00:00:00 2001 From: Allen Conlon Date: Thu, 11 Jun 2026 03:55:31 -0400 Subject: [PATCH 3/8] feat: add publish of helm chart to ghcr.io (#2853) Signed-off-by: Allen Conlon Co-authored-by: Felix Kunde --- .github/workflows/publish_ghcr_image.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/publish_ghcr_image.yaml b/.github/workflows/publish_ghcr_image.yaml index 2425e39b3..5a0c3b045 100644 --- a/.github/workflows/publish_ghcr_image.yaml +++ b/.github/workflows/publish_ghcr_image.yaml @@ -101,3 +101,15 @@ jobs: build-args: BASE_IMAGE=ubuntu:22.04 tags: "${{ steps.image_lb.outputs.BACKUP_IMAGE }}" platforms: linux/amd64,linux/arm64 + + - name: Build and push postgres-operator chart to ghcr + run: | + helm package charts/postgres-operator + helm push postgres-operator-*.tgz oci://${{ env.REGISTRY }}/zalando/charts + rm -rf postgres-operator-*.tgz + + - name: Build and push postgres-operator-ui chart to ghcr + run: | + helm package charts/postgres-operator-ui + helm push postgres-operator-ui-*.tgz oci://${{ env.REGISTRY }}/zalando/charts + rm -rf postgres-operator-ui-*.tgz From a71e6bdf7fba12769e213790e7103eeb7599eb8f Mon Sep 17 00:00:00 2001 From: Lucas Nikola Pape Date: Fri, 12 Jun 2026 08:02:26 +0000 Subject: [PATCH 4/8] feat: implement service type NodePort (#2986) feat: implement service type NodePort fix: handle LoadBalancer to NodePort service type transition move NodePort check before LoadBalancer and remove redundant nodePor add LB-specific DNS annotations again --- .../crds/operatorconfigurations.yaml | 12 ++ .../postgres-operator/crds/postgresqls.yaml | 20 ++ docs/administrator.md | 35 +++ docs/reference/cluster_manifest.md | 42 ++++ pkg/apis/acid.zalan.do/v1/crds.go | 12 ++ .../v1/operator_configuration_type.go | 29 ++- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 12 ++ .../acid.zalan.do/v1/zz_generated.deepcopy.go | 40 ++++ pkg/cluster/cluster.go | 8 + pkg/cluster/cluster_test.go | 71 ++++-- pkg/cluster/connection_pooler.go | 39 +++- pkg/cluster/connection_pooler_test.go | 178 +++++++++++++++ pkg/cluster/k8sres.go | 48 ++++- pkg/cluster/k8sres_test.go | 203 +++++++++++++++++- pkg/cluster/resources.go | 9 +- pkg/controller/operator_config.go | 4 + pkg/util/config/config.go | 4 + 17 files changed, 728 insertions(+), 38 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index 961b5b655..09356c476 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -610,6 +610,18 @@ spec: enable_replica_pooler_load_balancer: type: boolean default: false + enable_master_node_port: + type: boolean + default: false + enable_master_pooler_node_port: + type: boolean + default: false + enable_replica_node_port: + type: boolean + default: false + enable_replica_pooler_node_port: + type: boolean + default: false external_traffic_policy: type: string enum: diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 37bea3985..a52259a85 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -281,6 +281,26 @@ spec: type: boolean enableReplicaPoolerLoadBalancer: type: boolean + enableMasterNodePort: + type: boolean + masterNodePort: + type: integer + minimum: 0 + enableMasterPoolerNodePort: + type: boolean + masterPoolerNodePort: + type: integer + minimum: 0 + enableReplicaNodePort: + type: boolean + replicaNodePort: + type: integer + minimum: 0 + enableReplicaPoolerNodePort: + type: boolean + replicaPoolerNodePort: + type: integer + minimum: 0 enableShmVolume: type: boolean env: diff --git a/docs/administrator.md b/docs/administrator.md index e854775ce..ec0bc28dc 100644 --- a/docs/administrator.md +++ b/docs/administrator.md @@ -928,6 +928,41 @@ For the `external-dns.alpha.kubernetes.io/hostname` annotation the `-pooler` suffix will be appended to the cluster name used in the template which is defined in `master|replica_dns_name_format`. +## Node Ports + +Alternatively to Load Balancers Node Ports can be used. Kubernetes services with type +`NodePort` redirect traffic from a specified port on your kubernetes nodes to your service. +To expose your services to an external network with NodePorts you can set `enableMasterNodePort` and/or `enableReplicaNodePort` to `true` +in your cluster manifest. In the case any of these variables are omitted from the manifest, the operator configuration settings `enable_master_node_port` and `enable_replica_node_port` apply. +Note that the operator settings affect all Postgresql services running in all namespaces watched +by the operator. + +**Enabling a NodePort configuration will override the corresponding LoadBalancer configuration.** + +There are multiple options to specify service annotations that will be merged +with each other and override in the following order (where latter take +precedence): + +1. Globally configured `custom_service_annotations` +2. `serviceAnnotations` specified in the cluster manifest +3. `masterServiceAnnotations` and `replicaServiceAnnotations` specified in the cluster manifest + +Load-Balancer specific annotations are not applied. + +Node port services can also be configured for the [connection pooler](user.md#connection-pooler) pods +with the manifest flags `enableMasterPoolerNodePort` and/or `enableReplicaPoolerNodePort` or in the operator configuration with `enable_master_pooler_node_port` +and/or `enable_replica_pooler_node_port`. + +To configure which ports Kubernetes should use for your NodePort service you can configure ports in your cluster manifest +for each type: + +- masterNodePort +- masterPoolerNodePort +- replicaNodePort +- replicaPoolerNodePort + +When not defined or set to 0 kubernetes will choose a port for you from [your kubernetes cluster's configured range](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport). + ## Running periodic 'autorepair' scans of K8s objects The Postgres Operator periodically scans all K8s objects belonging to each diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index a45350f38..b216c1fb2 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -118,6 +118,48 @@ These parameters are grouped directly under the `spec` key in the manifest. this parameter. Optional, when empty the load balancer service becomes inaccessible from outside of the Kubernetes cluster. +* **enableMasterNodePort** + boolean flag to override the operator defaults (set by the + `enable_master_node_port` parameter) to define whether to enable the node + port pointing to the Postgres primary. Optional. Overrides `enableMasterLoadBalancer`. + +* **enableMasterPoolerNodePort** + boolean flag to override the operator defaults (set by the + `enable_master_pooler_node_port` parameter) to define whether to enable + the node port for master pooler pods pointing to the Postgres primary. + Optional. Overrides `enableMasterPoolerLoadBalancer`. + +* **enableReplicaNodePort** + boolean flag to override the operator defaults (set by the + `enable_replica_node_port` parameter) to define whether to enable the node + port pointing to the Postgres standby instances. Optional. Overrides `enableReplicaLoadBalancer`. + +* **enableReplicaPoolerNodePort** + boolean flag to override the operator defaults (set by the + `enable_replica_pooler_node_port` parameter) to define whether to enable + the node port for replica pooler pods pointing to the Postgres standby + instances. Optional. Overrides `enableReplicaPoolerLoadBalancer`. + +* **masterNodePort** + integer flag to specify a port number for the node port to the Postgres primary. + Only used when `enableMasterNodePort` or `enable_master_node_port` are enabled. + Optional. Kubernetes will provide a port number for you if not specified. + +* **masterPoolerNodePort** + integer flag to specify a port number for the node port for the master pooler pods pointing to the Postgres primary. + Only used when `enableMasterPoolerNodePort` or `enable_master_pooler_node_port` are enabled. + Optional. Kubernetes will provide a port number for you if not specified. + +* **replicaNodePort** + integer flag to specify a port number for the node port pointing to the Postgres standby instances. + Only used when `enableReplicaNodePort` or `enable_replica_node_port` are enabled. + Optional. Kubernetes will provide a port number for you if not specified. + +* **replicaPoolerNodePort** + integer flag to specify a port number for the node port for the replica pooler pods pointing to the Postgres standby instances + Only used when `enableReplicaPoolerNodePort` or `enable_replica_pooler_node_port` are enabled. + Optional. Kubernetes will provide a port number for you if not specified. + * **maintenanceWindows** a list which defines specific time frames when certain maintenance operations such as automatic major upgrades or master pod migration are allowed to happen. diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index b6b58f072..6b43d0c54 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -748,6 +748,18 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "enable_replica_pooler_load_balancer": { Type: "boolean", }, + "enable_master_node_port": { + Type: "boolean", + }, + "enable_master_pooler_node_port": { + Type: "boolean", + }, + "enable_replica_node_port": { + Type: "boolean", + }, + "enable_replica_pooler_node_port": { + Type: "boolean", + }, "external_traffic_policy": { Type: "string", Enum: []apiextv1.JSON{ diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index 0087e5850..3f28effc8 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -137,17 +137,24 @@ type OperatorTimeouts struct { // LoadBalancerConfiguration defines the LB configuration type LoadBalancerConfiguration struct { - DbHostedZone string `json:"db_hosted_zone,omitempty"` - EnableMasterLoadBalancer bool `json:"enable_master_load_balancer,omitempty"` - EnableMasterPoolerLoadBalancer bool `json:"enable_master_pooler_load_balancer,omitempty"` - EnableReplicaLoadBalancer bool `json:"enable_replica_load_balancer,omitempty"` - EnableReplicaPoolerLoadBalancer bool `json:"enable_replica_pooler_load_balancer,omitempty"` - CustomServiceAnnotations map[string]string `json:"custom_service_annotations,omitempty"` - MasterDNSNameFormat config.StringTemplate `json:"master_dns_name_format,omitempty"` - MasterLegacyDNSNameFormat config.StringTemplate `json:"master_legacy_dns_name_format,omitempty"` - ReplicaDNSNameFormat config.StringTemplate `json:"replica_dns_name_format,omitempty"` - ReplicaLegacyDNSNameFormat config.StringTemplate `json:"replica_legacy_dns_name_format,omitempty"` - ExternalTrafficPolicy string `json:"external_traffic_policy" default:"Cluster"` + DbHostedZone string `json:"db_hosted_zone,omitempty"` + EnableMasterLoadBalancer bool `json:"enable_master_load_balancer,omitempty"` + EnableMasterPoolerLoadBalancer bool `json:"enable_master_pooler_load_balancer,omitempty"` + EnableReplicaLoadBalancer bool `json:"enable_replica_load_balancer,omitempty"` + EnableReplicaPoolerLoadBalancer bool `json:"enable_replica_pooler_load_balancer,omitempty"` + + // kept in LoadBalancerConfiguration because all the other parameters apply here too + EnableMasterNodePort bool `json:"enable_master_node_port,omitempty"` + EnableMasterPoolerNodePort bool `json:"enable_master_pooler_node_port,omitempty"` + EnableReplicaNodePort bool `json:"enable_replica_node_port,omitempty"` + EnableReplicaPoolerNodePort bool `json:"enable_replica_pooler_node_port,omitempty"` + + CustomServiceAnnotations map[string]string `json:"custom_service_annotations,omitempty"` + MasterDNSNameFormat config.StringTemplate `json:"master_dns_name_format,omitempty"` + MasterLegacyDNSNameFormat config.StringTemplate `json:"master_legacy_dns_name_format,omitempty"` + ReplicaDNSNameFormat config.StringTemplate `json:"replica_dns_name_format,omitempty"` + ReplicaLegacyDNSNameFormat config.StringTemplate `json:"replica_legacy_dns_name_format,omitempty"` + ExternalTrafficPolicy string `json:"external_traffic_policy" default:"Cluster"` } // AWSGCPConfiguration defines the configuration for AWS diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index d67f23741..71ac73133 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -63,6 +63,18 @@ type PostgresSpec struct { EnableReplicaLoadBalancer *bool `json:"enableReplicaLoadBalancer,omitempty"` EnableReplicaPoolerLoadBalancer *bool `json:"enableReplicaPoolerLoadBalancer,omitempty"` + // vars to enable and configure nodeport services + // set ports to 0 or nil to let kubernetes decide which port to use + // overrides loadbalancer configuration + EnableMasterNodePort *bool `json:"enableMasterNodePort,omitempty"` + MasterNodePort *int32 `json:"masterNodePort,omitempty"` + EnableMasterPoolerNodePort *bool `json:"enableMasterPoolerNodePort,omitempty"` + MasterPoolerNodePort *int32 `json:"masterPoolerNodePort,omitempty"` + EnableReplicaNodePort *bool `json:"enableReplicaNodePort,omitempty"` + ReplicaNodePort *int32 `json:"replicaNodePort,omitempty"` + EnableReplicaPoolerNodePort *bool `json:"enableReplicaPoolerNodePort,omitempty"` + ReplicaPoolerNodePort *int32 `json:"replicaPoolerNodePort,omitempty"` + // deprecated load balancer settings maintained for backward compatibility // see "Load balancers" operator docs UseLoadBalancer *bool `json:"useLoadBalancer,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 3fdc31fa7..ff83abec9 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -735,6 +735,46 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) { *out = new(bool) **out = **in } + if in.EnableMasterNodePort != nil { + in, out := &in.EnableMasterNodePort, &out.EnableMasterNodePort + *out = new(bool) + **out = **in + } + if in.MasterNodePort != nil { + in, out := &in.MasterNodePort, &out.MasterNodePort + *out = new(int32) + **out = **in + } + if in.EnableMasterPoolerNodePort != nil { + in, out := &in.EnableMasterPoolerNodePort, &out.EnableMasterPoolerNodePort + *out = new(bool) + **out = **in + } + if in.MasterPoolerNodePort != nil { + in, out := &in.MasterPoolerNodePort, &out.MasterPoolerNodePort + *out = new(int32) + **out = **in + } + if in.EnableReplicaNodePort != nil { + in, out := &in.EnableReplicaNodePort, &out.EnableReplicaNodePort + *out = new(bool) + **out = **in + } + if in.ReplicaNodePort != nil { + in, out := &in.ReplicaNodePort, &out.ReplicaNodePort + *out = new(int32) + **out = **in + } + if in.EnableReplicaPoolerNodePort != nil { + in, out := &in.EnableReplicaPoolerNodePort, &out.EnableReplicaPoolerNodePort + *out = new(bool) + **out = **in + } + if in.ReplicaPoolerNodePort != nil { + in, out := &in.ReplicaPoolerNodePort, &out.ReplicaPoolerNodePort + *out = new(int32) + **out = **in + } if in.UseLoadBalancer != nil { in, out := &in.UseLoadBalancer, &out.UseLoadBalancer *out = new(bool) diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 95f16d922..6d9e6150a 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -859,6 +859,14 @@ func (c *Cluster) compareServices(old, new *v1.Service) (bool, string) { return false, "new service's ExternalTrafficPolicy does not match the current one" } + if len(old.Spec.Ports) > 0 && len(new.Spec.Ports) > 0 { + // we need to check whether the new port is not zero (=user-defined) + // and only overwrite if it is + if new.Spec.Ports[0].NodePort != 0 && old.Spec.Ports[0].NodePort != new.Spec.Ports[0].NodePort { + return false, "new service's NodePort does not match the current one" + } + } + return true, "" } diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index 84d75cb0a..5fdf1a220 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -1334,7 +1334,8 @@ func newService( svcType v1.ServiceType, sourceRanges []string, selector map[string]string, - policy v1.ServiceExternalTrafficPolicyType) *v1.Service { + policy v1.ServiceExternalTrafficPolicyType, + nodePort *int32) *v1.Service { svc := &v1.Service{ Spec: v1.ServiceSpec{ Selector: selector, @@ -1344,6 +1345,16 @@ func newService( }, } svc.Annotations = annotations + + if nodePort != nil { + svc.Spec.Ports = []v1.ServicePort{ + { + Name: "port", + NodePort: *nodePort, + }, + } + } + return svc } @@ -1370,6 +1381,7 @@ func TestCompareServices(t *testing.T) { []string{"128.141.0.0/16", "137.138.0.0/16"}, nil, defaultPolicy, + nil, ) ownerRef := metav1.OwnerReference{ @@ -1381,6 +1393,9 @@ func TestCompareServices(t *testing.T) { serviceWithOwnerReference.ObjectMeta.OwnerReferences = append(serviceWithOwnerReference.ObjectMeta.OwnerReferences, ownerRef) + portZero := int32(0) + portNotZero := int32(1337) + tests := []struct { about string current *v1.Service @@ -1396,14 +1411,14 @@ func TestCompareServices(t *testing.T) { }, v1.ServiceTypeClusterIP, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", }, v1.ServiceTypeClusterIP, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), match: true, }, { @@ -1414,14 +1429,14 @@ func TestCompareServices(t *testing.T) { }, v1.ServiceTypeClusterIP, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", }, v1.ServiceTypeLoadBalancer, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), match: false, reason: `new service's type "LoadBalancer" does not match the current one "ClusterIP"`, }, @@ -1433,14 +1448,14 @@ func TestCompareServices(t *testing.T) { }, v1.ServiceTypeLoadBalancer, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", }, v1.ServiceTypeLoadBalancer, []string{"185.249.56.0/22"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), match: false, reason: `new service's LoadBalancerSourceRange does not match the current one`, }, @@ -1452,14 +1467,14 @@ func TestCompareServices(t *testing.T) { }, v1.ServiceTypeLoadBalancer, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{ constants.ZalandoDNSNameAnnotation: "clstr.acid.zalan.do", }, v1.ServiceTypeLoadBalancer, []string{}, - nil, defaultPolicy), + nil, defaultPolicy, nil), match: false, reason: `new service's LoadBalancerSourceRange does not match the current one`, }, @@ -1471,7 +1486,7 @@ func TestCompareServices(t *testing.T) { }, v1.ServiceTypeClusterIP, []string{"128.141.0.0/16", "137.138.0.0/16"}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: serviceWithOwnerReference, match: false, }, @@ -1481,12 +1496,12 @@ func TestCompareServices(t *testing.T) { map[string]string{}, v1.ServiceTypeClusterIP, []string{}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{}, v1.ServiceTypeClusterIP, []string{}, - map[string]string{"cluster-name": "clstr", "spilo-role": "master"}, defaultPolicy), + map[string]string{"cluster-name": "clstr", "spilo-role": "master"}, defaultPolicy, nil), match: false, }, { @@ -1495,14 +1510,42 @@ func TestCompareServices(t *testing.T) { map[string]string{}, v1.ServiceTypeClusterIP, []string{}, - nil, defaultPolicy), + nil, defaultPolicy, nil), new: newService( map[string]string{}, v1.ServiceTypeClusterIP, []string{}, - nil, v1.ServiceExternalTrafficPolicyTypeLocal), + nil, v1.ServiceExternalTrafficPolicyTypeLocal, nil), match: false, }, + { + about: "services differ on node port", + current: newService( + map[string]string{}, + v1.ServiceTypeNodePort, + []string{}, + nil, defaultPolicy, &portZero), + new: newService( + map[string]string{}, + v1.ServiceTypeNodePort, + []string{}, + nil, defaultPolicy, &portNotZero), + match: false, + }, + { + about: "services do not differ on node port when requesting 0", + current: newService( + map[string]string{}, + v1.ServiceTypeNodePort, + []string{}, + nil, defaultPolicy, &portNotZero), + new: newService( + map[string]string{}, + v1.ServiceTypeNodePort, + []string{}, + nil, defaultPolicy, &portZero), + match: true, + }, } for _, tt := range tests { diff --git a/pkg/cluster/connection_pooler.go b/pkg/cluster/connection_pooler.go index 9f071068c..85685774d 100644 --- a/pkg/cluster/connection_pooler.go +++ b/pkg/cluster/connection_pooler.go @@ -566,7 +566,9 @@ func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPo }, } - if c.shouldCreateLoadBalancerForPoolerService(poolerRole, spec) { + if ok, port := c.shouldCreateNodePortForPoolerService(poolerRole, spec); ok { + c.configureNodePortService(&serviceSpec, port) + } else if c.shouldCreateLoadBalancerForPoolerService(poolerRole, spec) { c.configureLoadBalanceService(&serviceSpec, spec.AllowedSourceRanges) } @@ -594,7 +596,9 @@ func (c *Cluster) generatePoolerServiceAnnotations(role PostgresRole, spec *acid var dnsString string annotations := c.getCustomServiceAnnotations(role, spec) - if c.shouldCreateLoadBalancerForPoolerService(role, spec) { + nodePort, _ := c.shouldCreateNodePortForPoolerService(role, spec) + + if !nodePort && c.shouldCreateLoadBalancerForPoolerService(role, spec) { // -repl suffix will be added by replicaDNSName clusterNameWithPoolerSuffix := c.connectionPoolerName(Master) if role == Master { @@ -635,6 +639,37 @@ func (c *Cluster) shouldCreateLoadBalancerForPoolerService(role PostgresRole, sp } } +func (c *Cluster) shouldCreateNodePortForPoolerService(role PostgresRole, spec *acidv1.PostgresSpec) (bool, int32) { + switch role { + case Replica: + // if the value is explicitly set in a Postgresql manifest, follow this setting + if spec.EnableReplicaPoolerNodePort != nil { + port := int32(0) + if spec.ReplicaPoolerNodePort != nil { + port = *spec.ReplicaPoolerNodePort + } + + return *spec.EnableReplicaPoolerNodePort, port + } + + // otherwise, follow the operator configuration + return c.OpConfig.EnableReplicaPoolerNodePort, 0 + case Master: + if spec.EnableMasterPoolerNodePort != nil { + port := int32(0) + if spec.MasterPoolerNodePort != nil { + port = *spec.MasterPoolerNodePort + } + + return *spec.EnableMasterPoolerNodePort, port + } + + return c.OpConfig.EnableMasterPoolerNodePort, 0 + default: + panic(fmt.Sprintf("Unknown role %v", role)) + } +} + func (c *Cluster) listPoolerPods(listOptions metav1.ListOptions) ([]v1.Pod, error) { pods, err := c.KubeClient.Pods(c.Namespace).List(context.TODO(), listOptions) if err != nil { diff --git a/pkg/cluster/connection_pooler_test.go b/pkg/cluster/connection_pooler_test.go index 23213520f..1b41cbb02 100644 --- a/pkg/cluster/connection_pooler_test.go +++ b/pkg/cluster/connection_pooler_test.go @@ -1154,3 +1154,181 @@ func TestConnectionPoolerServiceSpec(t *testing.T) { } } } + +func TestConnectionPoolerServiceType(t *testing.T) { + testName := "Test connection pooler service type selection" + + cluster := New( + Config{ + OpConfig: config.Config{ + ProtectedRoles: []string{"admin"}, + Auth: config.Auth{ + SuperUsername: superUserName, + ReplicationUsername: replicationUserName, + }, + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + }, + Resources: config.Resources{ + EnableOwnerReferences: util.True(), + }, + }, + }, + k8sutil.KubernetesClient{}, + acidv1.Postgresql{}, + logger, + eventRecorder, + ) + + cluster.Statefulset = &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-sts", + }, + } + + cluster.ConnectionPooler = map[PostgresRole]*ConnectionPoolerObjects{ + Master: { + Deployment: nil, + Service: nil, + LookupFunction: false, + Role: Master, + }, + Replica: { + Deployment: nil, + Service: nil, + LookupFunction: false, + Role: Replica, + }, + } + + tests := []struct { + subTest string + spec *acidv1.PostgresSpec + cluster *Cluster + expectedType map[PostgresRole]v1.ServiceType + }{ + { + subTest: "default configuration -> ClusterIP for both", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeClusterIP, + Replica: v1.ServiceTypeClusterIP, + }, + }, + { + subTest: "LoadBalancer for both roles", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableMasterPoolerLoadBalancer: boolToPointer(true), + EnableReplicaPoolerLoadBalancer: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeLoadBalancer, + Replica: v1.ServiceTypeLoadBalancer, + }, + }, + { + subTest: "LoadBalancer for master", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableMasterPoolerLoadBalancer: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeLoadBalancer, + Replica: v1.ServiceTypeClusterIP, + }, + }, + { + subTest: "LoadBalancer for replica", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableReplicaPoolerLoadBalancer: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeClusterIP, + Replica: v1.ServiceTypeLoadBalancer, + }, + }, + { + subTest: "NodePort for both roles", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableMasterPoolerNodePort: boolToPointer(true), + EnableReplicaPoolerNodePort: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeNodePort, + Replica: v1.ServiceTypeNodePort, + }, + }, + { + subTest: "NodePort for master", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableMasterPoolerNodePort: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeNodePort, + Replica: v1.ServiceTypeClusterIP, + }, + }, + { + subTest: "NodePort for replica", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableReplicaPoolerNodePort: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeClusterIP, + Replica: v1.ServiceTypeNodePort, + }, + }, + { + subTest: "NodePort overrides LoadBalancer for both roles", + spec: &acidv1.PostgresSpec{ + ConnectionPooler: &acidv1.ConnectionPooler{}, + EnableMasterPoolerLoadBalancer: boolToPointer(true), + EnableReplicaPoolerLoadBalancer: boolToPointer(true), + EnableMasterPoolerNodePort: boolToPointer(true), + EnableReplicaPoolerNodePort: boolToPointer(true), + }, + cluster: cluster, + expectedType: map[PostgresRole]v1.ServiceType{ + Master: v1.ServiceTypeNodePort, + Replica: v1.ServiceTypeNodePort, + }, + }, + } + + roles := []PostgresRole{Master, Replica} + + for _, tt := range tests { + tt.cluster.Spec = *tt.spec + + for _, role := range roles { + svc := tt.cluster.generateConnectionPoolerService(tt.cluster.ConnectionPooler[role]) + + expected, ok := tt.expectedType[role] + if !ok { + t.Fatalf("%s [%s]: missing expectedType for role %v", testName, tt.subTest, role) + } + + if svc.Spec.Type != expected { + t.Errorf("%s [%s] role=%s: service Type is incorrect, got %s, expected %s", + testName, tt.subTest, role, svc.Spec.Type, expected) + } + } + } +} diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 8d3a40d9a..302797dc4 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -2004,6 +2004,37 @@ func (c *Cluster) shouldCreateLoadBalancerForService(role PostgresRole, spec *ac } +func (c *Cluster) shouldCreateNodePortForService(role PostgresRole, spec *acidv1.PostgresSpec) (bool, int32) { + switch role { + case Replica: + // if the value is explicitly set in a Postgresql manifest, follow this setting + if spec.EnableReplicaNodePort != nil { + port := int32(0) + if spec.ReplicaNodePort != nil { + port = *spec.ReplicaNodePort + } + + return *spec.EnableReplicaNodePort, port + } + + // otherwise, follow the operator configuration + return c.OpConfig.EnableReplicaNodePort, 0 + case Master: + if spec.EnableMasterNodePort != nil { + port := int32(0) + if spec.MasterNodePort != nil { + port = *spec.MasterNodePort + } + + return *spec.EnableMasterNodePort, port + } + + return c.OpConfig.EnableMasterNodePort, 0 + default: + panic(fmt.Sprintf("Unknown role %v", role)) + } +} + func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec) *v1.Service { serviceSpec := v1.ServiceSpec{ Ports: []v1.ServicePort{{Name: "postgresql", Port: pgPort, TargetPort: intstr.IntOrString{IntVal: pgPort}}}, @@ -2016,7 +2047,9 @@ func (c *Cluster) generateService(role PostgresRole, spec *acidv1.PostgresSpec) serviceSpec.Selector = c.roleLabelsSet(false, role) } - if c.shouldCreateLoadBalancerForService(role, spec) { + if ok, port := c.shouldCreateNodePortForService(role, spec); ok { + c.configureNodePortService(&serviceSpec, port) + } else if c.shouldCreateLoadBalancerForService(role, spec) { c.configureLoadBalanceService(&serviceSpec, spec.AllowedSourceRanges) } @@ -2049,10 +2082,21 @@ func (c *Cluster) configureLoadBalanceService(serviceSpec *v1.ServiceSpec, sourc serviceSpec.Type = v1.ServiceTypeLoadBalancer } +func (c *Cluster) configureNodePortService(serviceSpec *v1.ServiceSpec, port int32) { + serviceSpec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType(c.OpConfig.ExternalTrafficPolicy) + serviceSpec.Type = v1.ServiceTypeNodePort + + if port != 0 && len(serviceSpec.Ports) > 0 { + serviceSpec.Ports[0].NodePort = port + } +} + func (c *Cluster) generateServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string { annotations := c.getCustomServiceAnnotations(role, spec) - if c.shouldCreateLoadBalancerForService(role, spec) { + nodePort, _ := c.shouldCreateNodePortForService(role, spec) + + if !nodePort && c.shouldCreateLoadBalancerForService(role, spec) { dnsName := c.dnsName(role) // External DNS name annotation is not customizable diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index ef408da4d..bf21b8645 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -2971,32 +2971,32 @@ func newLBFakeClient() (k8sutil.KubernetesClient, *fake.Clientset) { }, clientSet } -func getServices(serviceType v1.ServiceType, sourceRanges []string, extTrafficPolicy, clusterName string) []v1.ServiceSpec { +func getServices(serviceType v1.ServiceType, sourceRanges []string, extTrafficPolicy, clusterName string, nodePort int32) []v1.ServiceSpec { return []v1.ServiceSpec{ { ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy), LoadBalancerSourceRanges: sourceRanges, - Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}}, + Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}}, Type: serviceType, }, { ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy), LoadBalancerSourceRanges: sourceRanges, - Ports: []v1.ServicePort{{Name: clusterName + "-pooler", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}}, + Ports: []v1.ServicePort{{Name: clusterName + "-pooler", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}}, Selector: map[string]string{"connection-pooler": clusterName + "-pooler"}, Type: serviceType, }, { ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy), LoadBalancerSourceRanges: sourceRanges, - Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}}, + Ports: []v1.ServicePort{{Name: "postgresql", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}}, Selector: map[string]string{"spilo-role": "replica", "application": "spilo", "cluster-name": clusterName}, Type: serviceType, }, { ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyType(extTrafficPolicy), LoadBalancerSourceRanges: sourceRanges, - Ports: []v1.ServicePort{{Name: clusterName + "-pooler-repl", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}}}, + Ports: []v1.ServicePort{{Name: clusterName + "-pooler-repl", Port: 5432, TargetPort: intstr.IntOrString{IntVal: 5432}, NodePort: nodePort}}, Selector: map[string]string{"connection-pooler": clusterName + "-pooler-repl"}, Type: serviceType, }, @@ -3064,7 +3064,7 @@ func TestEnableLoadBalancers(t *testing.T) { }, }, }, - expectedServices: getServices(v1.ServiceTypeClusterIP, nil, "", clusterName), + expectedServices: getServices(v1.ServiceTypeClusterIP, nil, "", clusterName, 0), }, { subTest: "LBs enabled in manifest, disabled in config", @@ -3111,7 +3111,7 @@ func TestEnableLoadBalancers(t *testing.T) { }, }, }, - expectedServices: getServices(v1.ServiceTypeLoadBalancer, sourceRanges, extTrafficPolicy, clusterName), + expectedServices: getServices(v1.ServiceTypeLoadBalancer, sourceRanges, extTrafficPolicy, clusterName, 0), }, } @@ -3143,6 +3143,195 @@ func TestEnableLoadBalancers(t *testing.T) { } } +func TestEnableNodePorts(t *testing.T) { + clusterName := "acid-test-cluster" + namespace := "default" + clusterNameLabel := "cluster-name" + roleLabel := "spilo-role" + roles := []PostgresRole{Master, Replica} + extTrafficPolicy := "Cluster" + port := int32(1337) + + tests := []struct { + subTest string + config config.Config + pgSpec acidv1.Postgresql + expectedServices []v1.ServiceSpec + }{ + { + subTest: "NodePorts enabled in config, disabled in manifest", + config: config.Config{ + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + NumberOfInstances: k8sutil.Int32ToPointer(1), + }, + EnableMasterNodePort: true, + EnableMasterPoolerNodePort: true, + EnableReplicaNodePort: true, + EnableReplicaPoolerNodePort: true, + ExternalTrafficPolicy: extTrafficPolicy, + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: clusterNameLabel, + PodRoleLabel: roleLabel, + }, + }, + pgSpec: acidv1.Postgresql{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: namespace, + }, + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: util.True(), + EnableReplicaConnectionPooler: util.True(), + EnableMasterNodePort: util.False(), + EnableMasterPoolerNodePort: util.False(), + EnableReplicaNodePort: util.False(), + EnableReplicaPoolerNodePort: util.False(), + NumberOfInstances: 1, + Resources: &acidv1.Resources{ + ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + }, + TeamID: "acid", + Volume: acidv1.Volume{ + Size: "1G", + }, + }, + }, + expectedServices: getServices(v1.ServiceTypeClusterIP, nil, "", clusterName, 0), + }, + { + subTest: "NodePorts configured in manifest, disabled in config", + config: config.Config{ + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + NumberOfInstances: k8sutil.Int32ToPointer(1), + }, + EnableMasterNodePort: false, + EnableMasterPoolerNodePort: false, + EnableReplicaNodePort: false, + EnableReplicaPoolerNodePort: false, + ExternalTrafficPolicy: extTrafficPolicy, + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: clusterNameLabel, + PodRoleLabel: roleLabel, + }, + }, + pgSpec: acidv1.Postgresql{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: namespace, + }, + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: util.True(), + EnableReplicaConnectionPooler: util.True(), + EnableMasterNodePort: util.True(), + EnableMasterPoolerNodePort: util.True(), + EnableReplicaNodePort: util.True(), + EnableReplicaPoolerNodePort: util.True(), + NumberOfInstances: 1, + Resources: &acidv1.Resources{ + ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + }, + TeamID: "acid", + Volume: acidv1.Volume{ + Size: "1G", + }, + }, + }, + expectedServices: getServices(v1.ServiceTypeNodePort, nil, extTrafficPolicy, clusterName, 0), + }, + { + subTest: "NodePorts configured in manifest, disabled in config, custom port specified", + config: config.Config{ + ConnectionPooler: config.ConnectionPooler{ + ConnectionPoolerDefaultCPURequest: "100m", + ConnectionPoolerDefaultCPULimit: "100m", + ConnectionPoolerDefaultMemoryRequest: "100Mi", + ConnectionPoolerDefaultMemoryLimit: "100Mi", + NumberOfInstances: k8sutil.Int32ToPointer(1), + }, + EnableMasterNodePort: false, + EnableMasterPoolerNodePort: false, + EnableReplicaNodePort: false, + EnableReplicaPoolerNodePort: false, + ExternalTrafficPolicy: extTrafficPolicy, + Resources: config.Resources{ + ClusterLabels: map[string]string{"application": "spilo"}, + ClusterNameLabel: clusterNameLabel, + PodRoleLabel: roleLabel, + }, + }, + pgSpec: acidv1.Postgresql{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: namespace, + }, + Spec: acidv1.PostgresSpec{ + EnableConnectionPooler: util.True(), + EnableReplicaConnectionPooler: util.True(), + EnableMasterNodePort: util.True(), + MasterNodePort: &port, + EnableMasterPoolerNodePort: util.True(), + MasterPoolerNodePort: &port, + EnableReplicaNodePort: util.True(), + ReplicaNodePort: &port, + EnableReplicaPoolerNodePort: util.True(), + ReplicaPoolerNodePort: &port, + NumberOfInstances: 1, + Resources: &acidv1.Resources{ + ResourceRequests: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + ResourceLimits: acidv1.ResourceDescription{CPU: k8sutil.StringToPointer("1"), Memory: k8sutil.StringToPointer("10")}, + }, + TeamID: "acid", + Volume: acidv1.Volume{ + Size: "1G", + }, + }, + }, + expectedServices: getServices(v1.ServiceTypeNodePort, nil, extTrafficPolicy, clusterName, port), + }, + } + + for _, tt := range tests { + client, _ := newLBFakeClient() + + var cluster = New( + Config{ + OpConfig: tt.config, + }, client, tt.pgSpec, logger, eventRecorder) + + cluster.Name = clusterName + cluster.Namespace = namespace + cluster.ConnectionPooler = map[PostgresRole]*ConnectionPoolerObjects{} + generatedServices := make([]v1.ServiceSpec, 0) + for _, role := range roles { + cluster.syncService(role) + cluster.ConnectionPooler[role] = &ConnectionPoolerObjects{ + Name: cluster.connectionPoolerName(role), + ClusterName: cluster.Name, + Namespace: cluster.Namespace, + Role: role, + } + cluster.syncConnectionPoolerWorker(&tt.pgSpec, &tt.pgSpec, role) + generatedServices = append(generatedServices, cluster.Services[role].Spec) + generatedServices = append(generatedServices, cluster.ConnectionPooler[role].Service.Spec) + } + if !reflect.DeepEqual(tt.expectedServices, generatedServices) { + t.Errorf("%s %s: expected %#v but got %#v", t.Name(), tt.subTest, tt.expectedServices, generatedServices) + } + } +} + func TestGenerateResourceRequirements(t *testing.T) { client, _ := newFakeK8sTestClient() clusterName := "acid-test-cluster" diff --git a/pkg/cluster/resources.go b/pkg/cluster/resources.go index ed3eb3d75..1fdad3e5e 100644 --- a/pkg/cluster/resources.go +++ b/pkg/cluster/resources.go @@ -324,9 +324,14 @@ func (c *Cluster) updateService(role PostgresRole, oldService *v1.Service, newSe // patch does not work because of LoadBalancerSourceRanges field (even if set to nil) oldServiceType := oldService.Spec.Type newServiceType := newService.Spec.Type - if newServiceType == "ClusterIP" && newServiceType != oldServiceType { + if newServiceType != oldServiceType && oldServiceType == v1.ServiceTypeLoadBalancer { + // Kubernetes rejects updates that change type away from LoadBalancer while + // loadBalancerSourceRanges is still set; clear it before updating + newService.Spec.LoadBalancerSourceRanges = nil newService.ResourceVersion = oldService.ResourceVersion - newService.Spec.ClusterIP = oldService.Spec.ClusterIP + if newServiceType == v1.ServiceTypeClusterIP { + newService.Spec.ClusterIP = oldService.Spec.ClusterIP + } } svc, err = c.KubeClient.Services(serviceName.Namespace).Update(context.TODO(), newService, metav1.UpdateOptions{}) if err != nil { diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index e304c14a5..9d752a76e 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -171,6 +171,10 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.EnableMasterPoolerLoadBalancer = fromCRD.LoadBalancer.EnableMasterPoolerLoadBalancer result.EnableReplicaLoadBalancer = fromCRD.LoadBalancer.EnableReplicaLoadBalancer result.EnableReplicaPoolerLoadBalancer = fromCRD.LoadBalancer.EnableReplicaPoolerLoadBalancer + result.EnableMasterNodePort = fromCRD.LoadBalancer.EnableMasterNodePort + result.EnableMasterPoolerNodePort = fromCRD.LoadBalancer.EnableMasterPoolerNodePort + result.EnableReplicaNodePort = fromCRD.LoadBalancer.EnableReplicaNodePort + result.EnableReplicaPoolerNodePort = fromCRD.LoadBalancer.EnableReplicaPoolerNodePort result.CustomServiceAnnotations = fromCRD.LoadBalancer.CustomServiceAnnotations result.MasterDNSNameFormat = fromCRD.LoadBalancer.MasterDNSNameFormat result.MasterLegacyDNSNameFormat = fromCRD.LoadBalancer.MasterLegacyDNSNameFormat diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index a14022407..9a18e0d25 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -213,6 +213,10 @@ type Config struct { EnableMasterPoolerLoadBalancer bool `name:"enable_master_pooler_load_balancer" default:"false"` EnableReplicaLoadBalancer bool `name:"enable_replica_load_balancer" default:"false"` EnableReplicaPoolerLoadBalancer bool `name:"enable_replica_pooler_load_balancer" default:"false"` + EnableMasterNodePort bool `name:"enable_master_node_port" default:"false"` + EnableMasterPoolerNodePort bool `name:"enable_master_pooler_node_port" default:"false"` + EnableReplicaNodePort bool `name:"enable_replica_node_port" default:"false"` + EnableReplicaPoolerNodePort bool `name:"enable_replica_pooler_node_port" default:"false"` CustomServiceAnnotations map[string]string `name:"custom_service_annotations"` CustomPodAnnotations map[string]string `name:"custom_pod_annotations"` EnablePodAntiAffinity bool `name:"enable_pod_antiaffinity" default:"false"` From ebf48667f10fb023c9f1eadb7a698e7149b8aef3 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 12 Jun 2026 10:42:37 +0200 Subject: [PATCH 5/8] drop kubectl-pg plugin (#3107) Co-authored-by: Ida Novindasari --- .gitignore | 1 - kubectl-pg/README.md | 137 ------------------------ kubectl-pg/build.sh | 4 - kubectl-pg/cmd/addDb.go | 114 -------------------- kubectl-pg/cmd/addUser.go | 144 ------------------------- kubectl-pg/cmd/check.go | 74 ------------- kubectl-pg/cmd/connect.go | 144 ------------------------- kubectl-pg/cmd/create.go | 82 -------------- kubectl-pg/cmd/delete.go | 134 ----------------------- kubectl-pg/cmd/extVolume.go | 119 --------------------- kubectl-pg/cmd/list.go | 125 ---------------------- kubectl-pg/cmd/logs.go | 143 ------------------------- kubectl-pg/cmd/root.go | 51 --------- kubectl-pg/cmd/scale.go | 194 --------------------------------- kubectl-pg/cmd/update.go | 96 ----------------- kubectl-pg/cmd/util.go | 172 ------------------------------ kubectl-pg/cmd/version.go | 80 -------------- kubectl-pg/go.mod | 72 ------------- kubectl-pg/go.sum | 206 ------------------------------------ kubectl-pg/main.go | 31 ------ 20 files changed, 2123 deletions(-) delete mode 100644 kubectl-pg/README.md delete mode 100755 kubectl-pg/build.sh delete mode 100644 kubectl-pg/cmd/addDb.go delete mode 100644 kubectl-pg/cmd/addUser.go delete mode 100644 kubectl-pg/cmd/check.go delete mode 100644 kubectl-pg/cmd/connect.go delete mode 100644 kubectl-pg/cmd/create.go delete mode 100644 kubectl-pg/cmd/delete.go delete mode 100644 kubectl-pg/cmd/extVolume.go delete mode 100644 kubectl-pg/cmd/list.go delete mode 100644 kubectl-pg/cmd/logs.go delete mode 100644 kubectl-pg/cmd/root.go delete mode 100644 kubectl-pg/cmd/scale.go delete mode 100644 kubectl-pg/cmd/update.go delete mode 100644 kubectl-pg/cmd/util.go delete mode 100644 kubectl-pg/cmd/version.go delete mode 100644 kubectl-pg/go.mod delete mode 100644 kubectl-pg/go.sum delete mode 100644 kubectl-pg/main.go diff --git a/.gitignore b/.gitignore index 5938db216..65aad49c5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ _testmain.go *.test *.prof /vendor/ -/kubectl-pg/vendor/ /build/ /docker/build/ /github.com/ diff --git a/kubectl-pg/README.md b/kubectl-pg/README.md deleted file mode 100644 index 8213d4ff5..000000000 --- a/kubectl-pg/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Kubectl Plugin for Zalando's Postgres Operator - -This plugin is a prototype developed as a part of **Google Summer of Code 2019** under the [Postgres Operator](https://summerofcode.withgoogle.com/archive/2019/organizations/6187982082539520/) organization. - -## Installation of kubectl pg plugin - -This project uses Go Modules for dependency management to build locally. -Install go and enable go modules with ```export GO111MODULE=on```. -From Go >=1.13 Go modules will be enabled by default. - -```bash -# Assumes you have a working KUBECONFIG -$ GO111MODULE="on" -$ GOPATH/src/github.com/zalando/postgres-operator/kubectl-pg go mod vendor -# This generate a vendor directory with all dependencies needed by the plugin. -$ $GOPATH/src/github.com/zalando/postgres-operator/kubectl-pg go install -# This will place the kubectl-pg binary in your $GOPATH/bin -``` - -## Before using the kubectl pg plugin make sure to set KUBECONFIG env variable - -Ideally KUBECONFIG is found in $HOME/.kube/config else specify the KUBECONFIG path here. -```export KUBECONFIG=$HOME/.kube/config``` - -## List all commands available in kubectl pg - -```kubectl pg --help``` (or) ```kubectl pg``` - -## Check if Postgres Operator is installed and running - -```kubectl pg check``` - -## Create a new cluster using manifest file - -```kubectl pg create -f acid-minimal-cluster.yaml``` - -## List postgres resources - -```kubectl pg list``` - -List clusters across namespaces -```kubectl pg list all``` - -## Update existing cluster using manifest file - -```kubectl pg update -f acid-minimal-cluster.yaml``` - -## Delete existing cluster - -Using the manifest file: -```kubectl pg delete -f acid-minimal-cluster.yaml``` - -Or by specifying the cluster name: -```kubectl pg delete acid-minimal-cluster``` - -Use `--namespace` or `-n` flag if your cluster is in a different namespace to where your current context is pointing to: -```kubectl pg delete acid-minimal-cluster -n namespace01``` - -## Adding manifest roles to an existing cluster - -```kubectl pg add-user USER01 -p CREATEDB,LOGIN -c acid-minimal-cluster``` - -Privileges can only be [SUPERUSER, REPLICATION, INHERIT, LOGIN, NOLOGIN, CREATEROLE, CREATEDB, BYPASSRLS] -Note: By default, a LOGIN user is created (unless NOLOGIN is specified). - -## Adding databases to an existing cluster - -You have to specify an owner of the new database and this role must already exist in the cluster: -```kubectl pg add-db DB01 -o OWNER01 -c acid-minimal-cluster``` - -## Extend the volume of an existing pg cluster - -```kubectl pg ext-volume 2Gi -c acid-minimal-cluster``` - -## Print the version of Postgres Operator and kubectl pg plugin - -```kubectl pg version``` - -## Connect to the shell of a postgres pod - -Connect to the master pod: -```kubectl pg connect -c CLUSTER -m``` - -Connect to a random replica pod: -```kubectl pg connect -c CLUSTER``` - -Connect to a certain replica pod: -```kubectl pg connect -c CLUSTER -r 0``` - -## Connect to a database via psql - -Adding the `-p` flag allows you to directly connect to a given database with the psql client. -With `-u` you specify the user. If left out the name of the current OS user is taken. -`-d` lets you specify the database. If no database is specified, it will be the same as the user name. - -Connect to `app_db` database on the master with role `app_user`: -```kubectl pg connect -c CLUSTER -m -p -u app_user -d app_db``` - -Connect to the `postgres` database on a random replica with role `postgres`: -```kubectl pg connect -c CLUSTER -p -u postgres``` - -Connect to a certain replica assuming name of OS user, database role and name are all the same: -```kubectl pg connect -c CLUSTER -r 0 -p``` - - -## Access Postgres Operator logs - -```kubectl pg logs -o``` - -## Access Patroni logs of different database pods - -Fetch logs of master: -```kubectl pg logs -c CLUSTER -m``` - -Fetch logs of a random replica pod: -```kubectl pg logs -c CLUSTER``` - -Fetch logs of specified replica -```kubectl pg logs -c CLUSTER -r 2``` - -## Development - -When making changes to the plugin make sure to change the (major/patch) version of plugin in `build.sh` script and run `./build.sh`. - -## Google Summer of Code 2019 - -### GSoC Proposal - -[kubectl pg proposal](https://docs.google.com/document/d/1-WMy9HkfZ1XnnMbzplMe9rCzKrRMGaMz4owLVXXPb7w/edit) - -### Weekly Reports - -https://github.com/VineethReddy02/GSoC-Kubectl-Plugin-for-Postgres-Operator-tracker - -### Final Project Report - -https://gist.github.com/VineethReddy02/159283bd368a710379eaf0f6bd60a40a diff --git a/kubectl-pg/build.sh b/kubectl-pg/build.sh deleted file mode 100755 index a81bf54fc..000000000 --- a/kubectl-pg/build.sh +++ /dev/null @@ -1,4 +0,0 @@ - -VERSION=1.0 -sed -i "s/KubectlPgVersion string = \"[^\"]*\"/KubectlPgVersion string = \"${VERSION}\"/" cmd/version.go -go install \ No newline at end of file diff --git a/kubectl-pg/cmd/addDb.go b/kubectl-pg/cmd/addDb.go deleted file mode 100644 index 1c33579d9..000000000 --- a/kubectl-pg/cmd/addDb.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "log" - - "github.com/spf13/cobra" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -// addDbCmd represents the addDb command -var addDbCmd = &cobra.Command{ - Use: "add-db", - Short: "Adds a DB and its owner to a Postgres cluster. The owner role is created if it does not exist", - Long: `Adds a new DB to the Postgres cluster. Owner needs to be specified by the -o flag, cluster with -c flag.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) > 0 { - dbName := args[0] - dbOwner, _ := cmd.Flags().GetString("owner") - clusterName, _ := cmd.Flags().GetString("cluster") - addDb(dbName, dbOwner, clusterName) - } else { - fmt.Println("database name can't be empty. Use kubectl pg add-db [-h | --help] for more info") - } - - }, - Example: ` -kubectl pg add-db db01 -o owner01 -c cluster01 -`, -} - -// add db and it's owner to the cluster -func addDb(dbName string, dbOwner string, clusterName string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - namespace := getCurrentNamespace() - postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - var dbOwnerExists bool - dbUsers := postgresql.Spec.Users - for key := range dbUsers { - if key == dbOwner { - dbOwnerExists = true - } - } - var patch []byte - // validating reserved DB names - if dbOwnerExists && dbName != "postgres" && dbName != "template0" && dbName != "template1" { - patch = dbPatch(dbName, dbOwner) - } else if !dbOwnerExists { - log.Fatal("The provided db-owner doesn't exist") - } else { - log.Fatal("The provided db-name is reserved by postgres") - } - - updatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - log.Fatal(err) - } - - if updatedPostgres.ResourceVersion != postgresql.ResourceVersion { - fmt.Printf("Created new database %s with owner %s in PostgreSQL cluster %s.\n", dbName, dbOwner, updatedPostgres.Name) - } else { - fmt.Printf("postgresql %s is unchanged.\n", updatedPostgres.Name) - } -} - -func dbPatch(dbname string, owner string) []byte { - ins := map[string]map[string]map[string]string{"spec": {"databases": {dbname: owner}}} - patchInstances, err := json.Marshal(ins) - if err != nil { - log.Fatal(err, "unable to parse patch for add-db") - } - return patchInstances -} - -func init() { - addDbCmd.Flags().StringP("owner", "o", "", "provide owner of the database.") - addDbCmd.Flags().StringP("cluster", "c", "", "provide a postgres cluster name.") - rootCmd.AddCommand(addDbCmd) -} diff --git a/kubectl-pg/cmd/addUser.go b/kubectl-pg/cmd/addUser.go deleted file mode 100644 index 602adb51d..000000000 --- a/kubectl-pg/cmd/addUser.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "log" - "strings" - - "github.com/spf13/cobra" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -var allowedPrivileges = []string{"SUPERUSER", "REPLICATION", "INHERIT", "LOGIN", "NOLOGIN", "CREATEROLE", "CREATEDB", "BYPASSRLS"} - -// addUserCmd represents the addUser command -var addUserCmd = &cobra.Command{ - Use: "add-user", - Short: "Adds a user to the postgres cluster with given privileges", - Long: `Adds a user to the postgres cluster. You can add privileges as well with -p flag.`, - Run: func(cmd *cobra.Command, args []string) { - clusterName, _ := cmd.Flags().GetString("cluster") - privileges, _ := cmd.Flags().GetString("privileges") - - if len(args) > 0 { - user := args[0] - var permissions []string - var perms []string - - if privileges != "" { - parsedRoles := strings.Replace(privileges, ",", " ", -1) - parsedRoles = strings.ToUpper(parsedRoles) - permissions = strings.Fields(parsedRoles) - var invalidPerms []string - - for _, userPrivilege := range permissions { - validPerm := false - for _, privilege := range allowedPrivileges { - if privilege == userPrivilege { - perms = append(perms, userPrivilege) - validPerm = true - } - } - if !validPerm { - invalidPerms = append(invalidPerms, userPrivilege) - } - } - - if len(invalidPerms) > 0 { - fmt.Printf("Invalid privileges %s\n", invalidPerms) - return - } - } - addUser(user, clusterName, perms) - } - }, - Example: ` -kubectl pg add-user user01 -p login,createdb -c cluster01 -`, -} - -// add user to the cluster with provided permissions -func addUser(user string, clusterName string, permissions []string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - namespace := getCurrentNamespace() - postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - setUsers := make(map[string]bool) - for _, k := range permissions { - setUsers[k] = true - } - - if existingRoles, key := postgresql.Spec.Users[user]; key { - for _, k := range existingRoles { - setUsers[k] = true - } - } - - Privileges := []string{} - for keys, values := range setUsers { - if values { - Privileges = append(Privileges, keys) - } - } - - patch := applyUserPatch(user, Privileges) - updatedPostgresql, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - log.Fatal(err) - } - - if updatedPostgresql.ResourceVersion != postgresql.ResourceVersion { - fmt.Printf("postgresql %s is updated with new user %s and with privileges %s.\n", updatedPostgresql.Name, user, permissions) - } else { - fmt.Printf("postgresql %s is unchanged.\n", updatedPostgresql.Name) - } -} - -func applyUserPatch(user string, value []string) []byte { - ins := map[string]map[string]map[string][]string{"spec": {"users": {user: value}}} - patchInstances, err := json.Marshal(ins) - if err != nil { - log.Fatal(err, "unable to parse number of instances json") - } - return patchInstances -} - -func init() { - addUserCmd.Flags().StringP("cluster", "c", "", "add user to the provided cluster.") - addUserCmd.Flags().StringP("privileges", "p", "", "add privileges separated by commas without spaces") - rootCmd.AddCommand(addUserCmd) -} diff --git a/kubectl-pg/cmd/check.go b/kubectl-pg/cmd/check.go deleted file mode 100644 index 6068c35bb..000000000 --- a/kubectl-pg/cmd/check.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "fmt" - "log" - - "github.com/spf13/cobra" - postgresConstants "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// checkCmd represent kubectl pg check. -var checkCmd = &cobra.Command{ - Use: "check", - Short: "Checks the Postgres operator is installed in the k8s cluster", - Long: `Checks that the Postgres CRD is registered in a k8s cluster. -This means that the operator pod was able to start normally.`, - Run: func(cmd *cobra.Command, args []string) { - check() - }, - Example: ` -kubectl pg check -`, -} - -// check validates postgresql CRD registered or not. -func check() *v1.CustomResourceDefinition { - config := getConfig() - apiExtClient, err := apiextv1.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - crdInfo, err := apiExtClient.CustomResourceDefinitions().Get(context.TODO(), postgresConstants.PostgresCRDResouceName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - if crdInfo.Name == postgresConstants.PostgresCRDResouceName { - fmt.Printf("Postgres Operator is installed in the k8s cluster.\n") - } else { - fmt.Printf("Postgres Operator is not installed in the k8s cluster.\n") - } - return crdInfo -} - -func init() { - rootCmd.AddCommand(checkCmd) -} diff --git a/kubectl-pg/cmd/connect.go b/kubectl-pg/cmd/connect.go deleted file mode 100644 index a7643ca05..000000000 --- a/kubectl-pg/cmd/connect.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "log" - "os" - user "os/user" - - "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/remotecommand" -) - -// connectCmd represents the kubectl pg connect command -var connectCmd = &cobra.Command{ - Use: "connect", - Short: "Connects to the shell prompt, psql prompt of postgres cluster", - Long: `Connects to the shell prompt, psql prompt of postgres cluster and also to specified replica or master.`, - Run: func(cmd *cobra.Command, args []string) { - clusterName, _ := cmd.Flags().GetString("cluster") - master, _ := cmd.Flags().GetBool("master") - replica, _ := cmd.Flags().GetString("replica") - psql, _ := cmd.Flags().GetBool("psql") - userName, _ := cmd.Flags().GetString("user") - dbName, _ := cmd.Flags().GetString("database") - - if psql { - if userName == "" { - userInfo, err := user.Current() - if err != nil { - log.Fatal(err) - } - userName = userInfo.Username - } - } - if dbName == "" { - dbName = userName - } - - connect(clusterName, master, replica, psql, userName, dbName) - }, - Example: ` -#connects to the master of postgres cluster -kubectl pg connect -c cluster -m - -#connects to the random replica of postgres cluster -kubectl pg connect -c cluster - -#connects to the provided replica number of postgres cluster -kubectl pg connect -c cluster -r 2 - -#connects to psql prompt of master for provided postgres cluster with current shell user -kubectl pg connect -c cluster -p -m - -#connects to psql prompt of random replica for provided postgres cluster with provided user and db -kubectl pg connect -c cluster -p -u user01 -d db01 -`, -} - -func connect(clusterName string, master bool, replica string, psql bool, user string, dbName string) { - config := getConfig() - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - podName := getPodName(clusterName, master, replica) - var execRequest *rest.Request - - if psql { - execRequest = client.CoreV1().RESTClient().Post().Resource("pods"). - Name(podName). - Namespace(getCurrentNamespace()). - SubResource("exec"). - Param("container", "postgres"). - Param("command", "psql"). - Param("command", dbName). - Param("command", user). - Param("stdin", "true"). - Param("stdout", "true"). - Param("stderr", "true"). - Param("tty", "true") - } else { - execRequest = client.CoreV1().RESTClient().Post().Resource("pods"). - Name(podName). - Namespace(getCurrentNamespace()). - SubResource("exec"). - Param("container", "postgres"). - Param("command", "su"). - Param("command", "postgres"). - Param("stdin", "true"). - Param("stdout", "true"). - Param("stderr", "true"). - Param("tty", "true") - } - - exec, err := remotecommand.NewSPDYExecutor(config, "POST", execRequest.URL()) - if err != nil { - log.Fatal(err) - } - - err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{ - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - Tty: true, - }) - if err != nil { - log.Fatal(err) - } -} - -func init() { - connectCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.") - connectCmd.Flags().BoolP("master", "m", false, "connect to master.") - connectCmd.Flags().StringP("replica", "r", "", "connect to replica. Specify replica number.") - connectCmd.Flags().BoolP("psql", "p", false, "connect to psql prompt.") - connectCmd.Flags().StringP("user", "u", "", "provide user.") - connectCmd.Flags().StringP("database", "d", "", "provide database name.") - rootCmd.AddCommand(connectCmd) -} diff --git a/kubectl-pg/cmd/create.go b/kubectl-pg/cmd/create.go deleted file mode 100644 index 3d34a7d25..000000000 --- a/kubectl-pg/cmd/create.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "fmt" - "log" - "os" - - "github.com/spf13/cobra" - v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// createCmd kubectl pg create. -var createCmd = &cobra.Command{ - Use: "create", - Short: "Creates postgres object using manifest file", - Long: `Creates postgres custom resource objects from a manifest file.`, - Run: func(cmd *cobra.Command, args []string) { - fileName, _ := cmd.Flags().GetString("file") - create(fileName) - }, - Example: ` -kubectl pg create -f cluster-manifest.yaml -`, -} - -// Create postgresql resources. -func create(fileName string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - ymlFile, err := os.ReadFile(fileName) - if err != nil { - log.Fatal(err) - } - - decode := scheme.Codecs.UniversalDeserializer().Decode - obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{}) - if err != nil { - log.Fatal(err) - } - - postgresSql := obj.(*v1.Postgresql) - _, err = postgresConfig.Postgresqls(postgresSql.Namespace).Create(context.TODO(), postgresSql, metav1.CreateOptions{}) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("postgresql %s created.\n", postgresSql.Name) -} - -func init() { - createCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.") - rootCmd.AddCommand(createCmd) -} diff --git a/kubectl-pg/cmd/delete.go b/kubectl-pg/cmd/delete.go deleted file mode 100644 index 73a6e7b0b..000000000 --- a/kubectl-pg/cmd/delete.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "fmt" - "log" - "os" - - "github.com/spf13/cobra" - v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// deleteCmd represents kubectl pg delete. -var deleteCmd = &cobra.Command{ - Use: "delete", - Short: "Deletes postgresql object by cluster-name/manifest file", - Long: `Deletes the postgres objects identified by a manifest file or cluster-name. -Deleting the manifest is sufficient to delete the cluster.`, - Run: func(cmd *cobra.Command, args []string) { - namespace, _ := cmd.Flags().GetString("namespace") - file, _ := cmd.Flags().GetString("file") - - if file != "" { - deleteByFile(file) - } else if namespace != "" { - if len(args) != 0 { - clusterName := args[0] - deleteByName(clusterName, namespace) - } else { - fmt.Println("cluster name can't be empty") - } - } else { - fmt.Println("use the flag either -n or -f to delete a resource.") - } - }, - Example: ` -#Deleting the postgres cluster using manifest file -kubectl pg delete -f cluster-manifest.yaml - -#Deleting the postgres cluster using cluster name in current namespace. -kubectl pg delete cluster01 - -#Deleting the postgres cluster using cluster name in provided namespace -kubectl pg delete cluster01 -n namespace01 -`, -} - -func deleteByFile(file string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - ymlFile, err := os.ReadFile(file) - if err != nil { - log.Fatal(err) - } - - decode := scheme.Codecs.UniversalDeserializer().Decode - obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{}) - if err != nil { - log.Fatal(err) - } - - postgresSql := obj.(*v1.Postgresql) - _, err = postgresConfig.Postgresqls(postgresSql.Namespace).Get(context.TODO(), postgresSql.Name, metav1.GetOptions{}) - if err != nil { - fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", postgresSql.Name, postgresSql.Namespace, err) - return - } - fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", postgresSql.Namespace, postgresSql.Name) - - confirmAction(postgresSql.Name, postgresSql.Namespace) - err = postgresConfig.Postgresqls(postgresSql.Namespace).Delete(context.TODO(), postgresSql.Name, metav1.DeleteOptions{}) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Postgresql %s deleted from %s.\n", postgresSql.Name, postgresSql.Namespace) -} - -func deleteByName(clusterName string, namespace string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - _, err = postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - fmt.Printf("Postgresql %s not found with the provided namespace %s : %s \n", clusterName, namespace, err) - return - } - fmt.Printf("Are you sure you want to remove this PostgreSQL cluster? If so, please type (%s/%s) and hit Enter\n", namespace, clusterName) - - confirmAction(clusterName, namespace) - err = postgresConfig.Postgresqls(namespace).Delete(context.TODO(), clusterName, metav1.DeleteOptions{}) - if err != nil { - log.Fatal(err) - } - fmt.Printf("Postgresql %s deleted from %s.\n", clusterName, namespace) -} - -func init() { - namespace := getCurrentNamespace() - deleteCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be deleted.") - deleteCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.") - rootCmd.AddCommand(deleteCmd) -} diff --git a/kubectl-pg/cmd/extVolume.go b/kubectl-pg/cmd/extVolume.go deleted file mode 100644 index 02ccc372d..000000000 --- a/kubectl-pg/cmd/extVolume.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "log" - "strconv" - - "github.com/spf13/cobra" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" -) - -// extVolumeCmd represents the extVolume command -var extVolumeCmd = &cobra.Command{ - Use: "ext-volume", - Short: "Increases the volume size of a given Postgres cluster", - Long: `Extends the volume of the postgres cluster. But volume cannot be shrinked.`, - Run: func(cmd *cobra.Command, args []string) { - clusterName, _ := cmd.Flags().GetString("cluster") - if len(args) > 0 { - volume := args[0] - extVolume(volume, clusterName) - } else { - fmt.Println("please enter the cluster name with -c flag & volume in desired units") - } - }, - Example: ` -#Extending the volume size of provided cluster -kubectl pg ext-volume 2Gi -c cluster01 -`, -} - -// extend volume with provided size & cluster name -func extVolume(increasedVolumeSize string, clusterName string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - namespace := getCurrentNamespace() - postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - oldSize, err := resource.ParseQuantity(postgresql.Spec.Volume.Size) - if err != nil { - log.Fatal(err) - } - - newSize, err := resource.ParseQuantity(increasedVolumeSize) - if err != nil { - log.Fatal(err) - } - - _, err = strconv.Atoi(newSize.String()) - if err == nil { - fmt.Println("provide the valid volume size with respective units i.e Ki, Mi, Gi") - return - } - - if newSize.Value() > oldSize.Value() { - patchInstances := volumePatch(newSize) - response, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patchInstances, metav1.PatchOptions{}) - if err != nil { - log.Fatal(err) - } - if postgresql.ResourceVersion != response.ResourceVersion { - fmt.Printf("%s volume is extended to %s.\n", response.Name, increasedVolumeSize) - } else { - fmt.Printf("%s volume %s is unchanged.\n", response.Name, postgresql.Spec.Volume.Size) - } - } else if newSize.Value() == oldSize.Value() { - fmt.Println("volume already has the desired size.") - } else { - fmt.Printf("volume %s size cannot be shrinked.\n", postgresql.Spec.Volume.Size) - } -} - -func volumePatch(volume resource.Quantity) []byte { - patchData := map[string]map[string]map[string]resource.Quantity{"spec": {"volume": {"size": volume}}} - patch, err := json.Marshal(patchData) - if err != nil { - log.Fatal(err, "unable to parse patch to extend volume") - } - return patch -} - -func init() { - extVolumeCmd.Flags().StringP("cluster", "c", "", "provide cluster name.") - rootCmd.AddCommand(extVolumeCmd) -} diff --git a/kubectl-pg/cmd/list.go b/kubectl-pg/cmd/list.go deleted file mode 100644 index 4fd6de3ba..000000000 --- a/kubectl-pg/cmd/list.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "fmt" - "log" - "strconv" - "time" - - "github.com/spf13/cobra" - v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - TrimCreateTimestamp = 6000000000 -) - -// listCmd represents kubectl pg list. -var listCmd = &cobra.Command{ - Use: "list", - Short: "Lists all the resources of kind postgresql", - Long: `Lists all the info specific to postgresql objects.`, - Run: func(cmd *cobra.Command, args []string) { - allNamespaces, _ := cmd.Flags().GetBool("all-namespaces") - namespace, _ := cmd.Flags().GetString("namespace") - if allNamespaces { - list(allNamespaces, "") - } else { - list(allNamespaces, namespace) - } - - }, - Example: ` -#Lists postgres cluster in current namespace -kubectl pg list - -#Lists postgres clusters in all namespaces -kubectl pg list -A -`, -} - -// list command to list postgres. -func list(allNamespaces bool, namespace string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - listPostgres, err := postgresConfig.Postgresqls(namespace).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - log.Fatal(err) - } - - if len(listPostgres.Items) == 0 { - if namespace != "" { - fmt.Printf("No Postgresql clusters found in namespace: %v\n", namespace) - } else { - fmt.Println("No Postgresql clusters found in all namespaces") - } - return - } - - if allNamespaces { - listAll(listPostgres) - } else { - listWithNamespace(listPostgres) - } -} - -func listAll(listPostgres *v1.PostgresqlList) { - template := "%-32s%-16s%-12s%-12s%-12s%-12s%-12s\n" - fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME", "NAMESPACE") - for _, pgObjs := range listPostgres.Items { - fmt.Printf(template, pgObjs.Name, - pgObjs.Status.PostgresClusterStatus, - strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)), - pgObjs.Spec.PostgresqlParam.PgVersion, - time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp), - pgObjs.Spec.Size, pgObjs.Namespace) - } -} - -func listWithNamespace(listPostgres *v1.PostgresqlList) { - template := "%-32s%-16s%-12s%-12s%-12s%-12s\n" - fmt.Printf(template, "NAME", "STATUS", "INSTANCES", "VERSION", "AGE", "VOLUME") - for _, pgObjs := range listPostgres.Items { - fmt.Printf(template, pgObjs.Name, - pgObjs.Status.PostgresClusterStatus, - strconv.Itoa(int(pgObjs.Spec.NumberOfInstances)), - pgObjs.Spec.PostgresqlParam.PgVersion, - time.Since(pgObjs.CreationTimestamp.Time).Truncate(TrimCreateTimestamp), - pgObjs.Spec.Size) - } -} - -func init() { - listCmd.Flags().BoolP("all-namespaces", "A", false, "list pg resources across all namespaces.") - listCmd.Flags().StringP("namespace", "n", getCurrentNamespace(), "provide the namespace") - rootCmd.AddCommand(listCmd) -} diff --git a/kubectl-pg/cmd/logs.go b/kubectl-pg/cmd/logs.go deleted file mode 100644 index 21a4fd6ec..000000000 --- a/kubectl-pg/cmd/logs.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "io" - "log" - "os" - - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -// logsCmd represents the logs command -var logsCmd = &cobra.Command{ - Use: "logs", - Short: "This will fetch the logs of the specified postgres cluster & postgres operator", - Long: `Fetches the logs of the postgres cluster (i.e master( with -m flag) & replica with (-r 1 pod number) and without -m or -r connects to random replica`, - Run: func(cmd *cobra.Command, args []string) { - opLogs, _ := cmd.Flags().GetBool("operator") - clusterName, _ := cmd.Flags().GetString("cluster") - master, _ := cmd.Flags().GetBool("master") - replica, _ := cmd.Flags().GetString("replica") - - if opLogs { - operatorLogs() - } else { - clusterLogs(clusterName, master, replica) - } - }, - Example: ` -#Fetch the logs of the postgres operator -kubectl pg logs -o - -#Fetch the logs of the master for provided cluster -kubectl pg logs -c cluster01 -m - -#Fetch the logs of the random replica for provided cluster -kubectl pg logs -c cluster01 - -#Fetch the logs of the provided replica number of the cluster -kubectl pg logs -c cluster01 -r 3 -`, -} - -func operatorLogs() { - config := getConfig() - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - operator := getPostgresOperator(client) - allPods, err := client.CoreV1().Pods(operator.Namespace).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - log.Fatal(err) - } - - var operatorPodName string - for _, pod := range allPods.Items { - for key, value := range pod.Labels { - if (key == "name" && value == OperatorName) || (key == "app.kubernetes.io/name" && value == OperatorName) { - operatorPodName = pod.Name - break - } - } - } - - execRequest := client.CoreV1().RESTClient().Get().Namespace(operator.Namespace). - Name(operatorPodName). - Resource("pods"). - SubResource("log"). - Param("follow", "--follow"). - Param("container", OperatorName) - - readCloser, err := execRequest.Stream(context.TODO()) - if err != nil { - log.Fatal(err) - } - - defer readCloser.Close() - _, err = io.Copy(os.Stdout, readCloser) - if err != nil { - log.Fatal(err) - } -} - -func clusterLogs(clusterName string, master bool, replica string) { - config := getConfig() - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - podName := getPodName(clusterName, master, replica) - execRequest := client.CoreV1().RESTClient().Get().Namespace(getCurrentNamespace()). - Name(podName). - Resource("pods"). - SubResource("log"). - Param("follow", "--follow"). - Param("container", "postgres") - - readCloser, err := execRequest.Stream(context.TODO()) - if err != nil { - log.Fatal(err) - } - - defer readCloser.Close() - _, err = io.Copy(os.Stdout, readCloser) - if err != nil { - log.Fatal(err) - } -} - -func init() { - rootCmd.AddCommand(logsCmd) - logsCmd.Flags().BoolP("operator", "o", false, "logs of operator") - logsCmd.Flags().StringP("cluster", "c", "", "logs for the provided cluster") - logsCmd.Flags().BoolP("master", "m", false, "Patroni logs of master") - logsCmd.Flags().StringP("replica", "r", "", "Patroni logs of replica. Specify replica number.") -} diff --git a/kubectl-pg/cmd/root.go b/kubectl-pg/cmd/root.go deleted file mode 100644 index 163d6f6ea..000000000 --- a/kubectl-pg/cmd/root.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var rootCmd = &cobra.Command{ - Use: "kubectl-pg", - Short: "kubectl plugin for the Zalando Postgres operator.", - Long: `kubectl pg plugin for interaction with Zalando postgres operator.`, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func init() { - viper.SetDefault("author", "Vineeth Pothulapati ") - viper.SetDefault("license", "mit") -} diff --git a/kubectl-pg/cmd/scale.go b/kubectl-pg/cmd/scale.go deleted file mode 100644 index 0a7bdc60f..000000000 --- a/kubectl-pg/cmd/scale.go +++ /dev/null @@ -1,194 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "log" - "strconv" - - "github.com/spf13/cobra" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" -) - -// scaleCmd represents the scale command -var scaleCmd = &cobra.Command{ - Use: "scale", - Short: "Add/remove pods to a Postgres cluster", - Long: `Scales the postgres objects using cluster-name. -Scaling to 0 leads to down time.`, - Run: func(cmd *cobra.Command, args []string) { - clusterName, err := cmd.Flags().GetString("cluster") - if err != nil { - log.Fatal(err) - } - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatal(err) - } - - if len(args) > 0 { - numberOfInstances, err := strconv.Atoi(args[0]) - if err != nil { - log.Fatal(err) - } - scale(int32(numberOfInstances), clusterName, namespace) - } else { - fmt.Println("Please enter number of instances to scale.") - } - - }, - Example: ` -#Usage -kubectl pg scale [NUMBER-OF-INSTANCES] -c [CLUSTER-NAME] -n [NAMESPACE] - -#Scales the number of instances of the provided cluster -kubectl pg scale 5 -c cluster01 -`, -} - -func scale(numberOfInstances int32, clusterName string, namespace string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - postgresql, err := postgresConfig.Postgresqls(namespace).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - minInstances, maxInstances := allowedMinMaxInstances(config) - - if minInstances == -1 && maxInstances == -1 { - postgresql.Spec.NumberOfInstances = numberOfInstances - } else if numberOfInstances <= maxInstances && numberOfInstances >= minInstances { - postgresql.Spec.NumberOfInstances = numberOfInstances - } else if minInstances == -1 && numberOfInstances < postgresql.Spec.NumberOfInstances || - maxInstances == -1 && numberOfInstances > postgresql.Spec.NumberOfInstances { - postgresql.Spec.NumberOfInstances = numberOfInstances - } else { - log.Fatalf("cannot scale to the provided instances as they don't adhere to MIN_INSTANCES: %v and MAX_INSTANCES: %v provided in configmap or operatorconfiguration", maxInstances, minInstances) - } - - if numberOfInstances == 0 { - fmt.Printf("Scaling to zero leads to down time. please type %s/%s and hit Enter this serves to confirm the action\n", namespace, clusterName) - confirmAction(clusterName, namespace) - } - - patchInstances := scalePatch(numberOfInstances) - UpdatedPostgres, err := postgresConfig.Postgresqls(namespace).Patch(context.TODO(), postgresql.Name, types.MergePatchType, patchInstances, metav1.PatchOptions{}) - if err != nil { - log.Fatal(err) - } - - if UpdatedPostgres.ResourceVersion != postgresql.ResourceVersion { - fmt.Printf("scaled postgresql %s/%s to %d instances\n", UpdatedPostgres.Namespace, UpdatedPostgres.Name, UpdatedPostgres.Spec.NumberOfInstances) - return - } - fmt.Printf("postgresql %s is unchanged.\n", postgresql.Name) -} - -func scalePatch(value int32) []byte { - instances := map[string]map[string]int32{"spec": {"numberOfInstances": value}} - patchInstances, err := json.Marshal(instances) - if err != nil { - log.Fatal(err, "unable to parse patch for scale") - } - return patchInstances -} - -func allowedMinMaxInstances(config *rest.Config) (int32, int32) { - k8sClient, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - operator := getPostgresOperator(k8sClient) - - operatorContainer := operator.Spec.Template.Spec.Containers - var configMapName, operatorConfigName string - // -1 indicates no limitations for min/max instances - minInstances := -1 - maxInstances := -1 - for _, envData := range operatorContainer[0].Env { - if envData.Name == "CONFIG_MAP_NAME" { - configMapName = envData.Value - } - if envData.Name == "POSTGRES_OPERATOR_CONFIGURATION_OBJECT" { - operatorConfigName = envData.Value - } - } - - if operatorConfigName == "" { - configMap, err := k8sClient.CoreV1().ConfigMaps(operator.Namespace).Get(context.TODO(), configMapName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - configMapData := configMap.Data - for key, value := range configMapData { - if key == "min_instances" { - minInstances, err = strconv.Atoi(value) - if err != nil { - log.Fatalf("invalid min instances in configmap %v", err) - } - } - - if key == "max_instances" { - maxInstances, err = strconv.Atoi(value) - if err != nil { - log.Fatalf("invalid max instances in configmap %v", err) - } - } - } - } else if configMapName == "" { - pgClient, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - operatorConfig, err := pgClient.OperatorConfigurations(operator.Namespace).Get(context.TODO(), operatorConfigName, metav1.GetOptions{}) - if err != nil { - log.Fatalf("unable to read operator configuration %v", err) - } - - minInstances = int(operatorConfig.Configuration.MinInstances) - maxInstances = int(operatorConfig.Configuration.MaxInstances) - } - return int32(minInstances), int32(maxInstances) -} - -func init() { - namespace := getCurrentNamespace() - scaleCmd.Flags().StringP("namespace", "n", namespace, "namespace of the cluster to be scaled") - scaleCmd.Flags().StringP("cluster", "c", "", "provide the cluster name.") - rootCmd.AddCommand(scaleCmd) -} diff --git a/kubectl-pg/cmd/update.go b/kubectl-pg/cmd/update.go deleted file mode 100644 index eb9259586..000000000 --- a/kubectl-pg/cmd/update.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "fmt" - "log" - "os" - - "github.com/spf13/cobra" - v1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// updateCmd represents kubectl pg update -var updateCmd = &cobra.Command{ - Use: "update", - Short: "Updates postgresql object using manifest file", - Long: `Updates the state of cluster using manifest file to reflect the changes on the cluster.`, - Run: func(cmd *cobra.Command, args []string) { - fileName, _ := cmd.Flags().GetString("file") - updatePgResources(fileName) - }, - Example: ` -#usage -kubectl pg update -f [File-NAME] - -#update the postgres cluster with updated manifest file -kubectl pg update -f cluster-manifest.yaml -`, -} - -// Update postgresql resources. -func updatePgResources(fileName string) { - config := getConfig() - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - ymlFile, err := os.ReadFile(fileName) - if err != nil { - log.Fatal(err) - } - - decode := scheme.Codecs.UniversalDeserializer().Decode - obj, _, err := decode([]byte(ymlFile), nil, &v1.Postgresql{}) - if err != nil { - log.Fatal(err) - } - - newPostgresObj := obj.(*v1.Postgresql) - oldPostgresObj, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Get(context.TODO(), newPostgresObj.Name, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - newPostgresObj.ResourceVersion = oldPostgresObj.ResourceVersion - response, err := postgresConfig.Postgresqls(newPostgresObj.Namespace).Update(context.TODO(), newPostgresObj, metav1.UpdateOptions{}) - if err != nil { - log.Fatal(err) - } - - if newPostgresObj.ResourceVersion != response.ResourceVersion { - fmt.Printf("postgresql %s updated.\n", response.Name) - } else { - fmt.Printf("postgresql %s is unchanged.\n", response.Name) - } -} - -func init() { - updateCmd.Flags().StringP("file", "f", "", "manifest file with the cluster definition.") - rootCmd.AddCommand(updateCmd) -} diff --git a/kubectl-pg/cmd/util.go b/kubectl-pg/cmd/util.go deleted file mode 100644 index fa0eb6d42..000000000 --- a/kubectl-pg/cmd/util.go +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "context" - "flag" - "fmt" - "log" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - - PostgresqlLister "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/typed/acid.zalan.do/v1" - v1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" -) - -const ( - OperatorName = "postgres-operator" - DefaultNamespace = "default" -) - -func getConfig() *restclient.Config { - var kubeconfig *string - var config *restclient.Config - envKube := os.Getenv("KUBECONFIG") - if envKube != "" { - kubeconfig = &envKube - } else { - if home := homedir.HomeDir(); home != "" { - kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") - } else { - kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") - } - } - flag.Parse() - var err error - config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) - if err != nil { - log.Fatal(err) - } - return config -} - -func getCurrentNamespace() string { - namespace, err := exec.Command("kubectl", "config", "view", "--minify", "--output", "jsonpath={..namespace}").CombinedOutput() - if err != nil { - log.Fatal(err) - } - currentNamespace := string(namespace) - if currentNamespace == "" { - currentNamespace = DefaultNamespace - } - return currentNamespace -} - -func confirmAction(clusterName string, namespace string) { - for { - confirmClusterDetails := "" - _, err := fmt.Scan(&confirmClusterDetails) - if err != nil { - log.Fatalf("couldn't get confirmation from the user %v", err) - } - clusterDetails := strings.Split(confirmClusterDetails, "/") - if clusterDetails[0] != namespace || clusterDetails[1] != clusterName { - fmt.Printf("cluster name or namespace does not match. Please re-enter %s/%s\nHint: Press (ctrl+c) to exit\n", namespace, clusterName) - } else { - return - } - } -} - -func getPodName(clusterName string, master bool, replicaNumber string) string { - config := getConfig() - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - postgresConfig, err := PostgresqlLister.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - postgresCluster, err := postgresConfig.Postgresqls(getCurrentNamespace()).Get(context.TODO(), clusterName, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - numOfInstances := postgresCluster.Spec.NumberOfInstances - var podName string - var podRole string - replica := clusterName + "-" + replicaNumber - - for ins := 0; ins < int(numOfInstances); ins++ { - pod, err := client.CoreV1().Pods(getCurrentNamespace()).Get(context.TODO(), clusterName+"-"+strconv.Itoa(ins), metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - - podRole = pod.Labels["spilo-role"] - if podRole == "master" && master { - podName = pod.Name - fmt.Printf("connected to %s with pod name as %s\n", podRole, podName) - break - } else if podRole == "replica" && !master && (pod.Name == replica || replicaNumber == "") { - podName = pod.Name - fmt.Printf("connected to %s with pod name as %s\n", podRole, podName) - break - } - } - if podName == "" { - log.Fatal("Provided replica doesn't exist") - } - return podName -} - -func getPostgresOperator(k8sClient *kubernetes.Clientset) *v1.Deployment { - var operator *v1.Deployment - operator, err := k8sClient.AppsV1().Deployments(getCurrentNamespace()).Get(context.TODO(), OperatorName, metav1.GetOptions{}) - if err == nil { - return operator - } - - allDeployments := k8sClient.AppsV1().Deployments("") - listDeployments, err := allDeployments.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - log.Fatal(err) - } - - for _, deployment := range listDeployments.Items { - if deployment.Name == OperatorName { - operator = deployment.DeepCopy() - break - } else { - for key, value := range deployment.Labels { - if key == "app.kubernetes.io/name" && value == OperatorName { - operator = deployment.DeepCopy() - break - } - } - } - } - return operator -} diff --git a/kubectl-pg/cmd/version.go b/kubectl-pg/cmd/version.go deleted file mode 100644 index 23cc55422..000000000 --- a/kubectl-pg/cmd/version.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package cmd - -import ( - "fmt" - "log" - "strings" - - "github.com/spf13/cobra" - "k8s.io/client-go/kubernetes" -) - -var KubectlPgVersion string = "1.0" - -// versionCmd represents the version command -var versionCmd = &cobra.Command{ - Use: "version", - Short: "version of kubectl-pg & postgres-operator", - Long: `version of kubectl-pg and current running postgres-operator`, - Run: func(cmd *cobra.Command, args []string) { - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatal(err) - } - version(namespace) - }, - Example: ` -#Lists the version of kubectl pg plugin and postgres operator in current namespace -kubectl pg version - -#Lists the version of kubectl pg plugin and postgres operator in provided namespace -kubectl pg version -n namespace01 -`, -} - -func version(namespace string) { - fmt.Printf("kubectl-pg: %s\n", KubectlPgVersion) - - config := getConfig() - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - operatorDeployment := getPostgresOperator(client) - if operatorDeployment.Name == "" { - log.Fatalf("make sure zalando's postgres operator is running in namespace %s", namespace) - } - operatorImage := operatorDeployment.Spec.Template.Spec.Containers[0].Image - imageDetails := strings.Split(operatorImage, ":") - imageSplit := len(imageDetails) - imageVersion := imageDetails[imageSplit-1] - fmt.Printf("Postgres-Operator: %s\n", imageVersion) -} - -func init() { - rootCmd.AddCommand(versionCmd) - versionCmd.Flags().StringP("namespace", "n", DefaultNamespace, "provide the namespace.") -} diff --git a/kubectl-pg/go.mod b/kubectl-pg/go.mod deleted file mode 100644 index 7f80cbfd7..000000000 --- a/kubectl-pg/go.mod +++ /dev/null @@ -1,72 +0,0 @@ -module github.com/zalando/postgres-operator/kubectl-pg - -go 1.25.3 - -require ( - github.com/spf13/cobra v1.10.1 - github.com/spf13/viper v1.21.0 - github.com/zalando/postgres-operator v1.15.0 - k8s.io/api v0.32.9 - k8s.io/apiextensions-apiserver v0.25.9 - k8s.io/apimachinery v0.32.9 - k8s.io/client-go v0.32.9 -) - -require ( - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/spdystream v0.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.9.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) diff --git a/kubectl-pg/go.sum b/kubectl-pg/go.sum deleted file mode 100644 index 488d24edc..000000000 --- a/kubectl-pg/go.sum +++ /dev/null @@ -1,206 +0,0 @@ -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d h1:LznySqW8MqVeFh+pW6rOkFdld9QQ7jRydBKKM6jyPVI= -github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d/go.mod h1:u3hJ0kqCQu/cPpsu3RbCOPZ0d7V3IjPjv1adNRleM9I= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/zalando/postgres-operator v1.15.0 h1:is/7cOrpuV7OwMiN7TG7GgiYHKvaWx8Ptw3hJruFO1I= -github.com/zalando/postgres-operator v1.15.0/go.mod h1:1cSOA5dG2dEqdG0uami1RHTGYX92bgAKYASfAhuMtHE= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.9 h1:q/59kk8lnecgG0grJqzrmXC1Jcl2hPWp9ltz0FQuoLI= -k8s.io/api v0.32.9/go.mod h1:jIfT3rwW4EU1IXZm9qjzSk/2j91k4CJL5vUULrxqp3Y= -k8s.io/apiextensions-apiserver v0.25.9 h1:Pycd6lm2auABp9wKQHCFSEPG+NPdFSTJXPST6NJFzB8= -k8s.io/apiextensions-apiserver v0.25.9/go.mod h1:ijGxmSG1GLOEaWhTuaEr0M7KUeia3mWCZa6FFQqpt1M= -k8s.io/apimachinery v0.32.9 h1:fXk8ktfsxrdThaEOAQFgkhCK7iyoyvS8nbYJ83o/SSs= -k8s.io/apimachinery v0.32.9/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.9 h1:ZMyIQ1TEpTDAQni3L2gH1NZzyOA/gHfNcAazzCxMJ0c= -k8s.io/client-go v0.32.9/go.mod h1:2OT8aFSYvUjKGadaeT+AVbhkXQSpMAkiSb88Kz2WggI= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/kubectl-pg/main.go b/kubectl-pg/main.go deleted file mode 100644 index bfcd5eb29..000000000 --- a/kubectl-pg/main.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright © 2019 Vineeth Pothulapati - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package main - -import ( - "github.com/zalando/postgres-operator/kubectl-pg/cmd" -) - -func main() { - cmd.Execute() -} From 1036350eb4d51106c07030cd033354fb073d0d55 Mon Sep 17 00:00:00 2001 From: Raphael Torquato <89878688+raphaeltorquat0@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:09:06 -0300 Subject: [PATCH 6/8] feat: add IPv6 support to allowedSourceRanges (#3082) * feat: add IPv6 support to allowedSourceRanges Update regex pattern in CRD validation to accept both IPv4 and IPv6 CIDR notation, enabling dual-stack networking support. Fixes #2787 Signed-off-by: Raphael Torquato <> * add unit test fror ipv6 allowedSourceRanges --------- Signed-off-by: Raphael Torquato <> Co-authored-by: Raphael Torquato <> Co-authored-by: Jociele Padilha --- .../postgres-operator/crds/postgresqls.yaml | 2 +- docs/reference/cluster_manifest.md | 9 ++-- manifests/postgresql.crd.yaml | 2 +- pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml | 2 +- pkg/apis/acid.zalan.do/v1/util_test.go | 45 +++++++++++++++++++ 5 files changed, 53 insertions(+), 7 deletions(-) diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index a52259a85..4d7c2586a 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -107,7 +107,7 @@ spec: description: load balancers' source ranges are the same for master and replica services items: - pattern: ^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$ + pattern: '^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$' type: string nullable: true type: array diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index b216c1fb2..0717e411f 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -113,10 +113,11 @@ These parameters are grouped directly under the `spec` key in the manifest. * **allowedSourceRanges** when one or more load balancers are enabled for the cluster, this parameter - defines the comma-separated range of IP networks (in CIDR-notation). The - corresponding load balancer is accessible only to the networks defined by - this parameter. Optional, when empty the load balancer service becomes - inaccessible from outside of the Kubernetes cluster. + defines the comma-separated range of IP networks (in CIDR-notation). Both + IPv4 (e.g. `192.168.1.0/24`) and IPv6 (e.g. `fd01::/48`) CIDR ranges are + supported. The corresponding load balancer is accessible only to the networks + defined by this parameter. Optional, when empty the load balancer service + becomes inaccessible from outside of the Kubernetes cluster. * **enableMasterNodePort** boolean flag to override the operator defaults (set by the diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index b9652ef22..b139c1db9 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -108,7 +108,7 @@ spec: description: load balancers' source ranges are the same for master and replica services items: - pattern: ^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$ + pattern: ^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$ type: string nullable: true type: array diff --git a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml index b9652ef22..b139c1db9 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml @@ -108,7 +108,7 @@ spec: description: load balancers' source ranges are the same for master and replica services items: - pattern: ^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$ + pattern: ^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$ type: string nullable: true type: array diff --git a/pkg/apis/acid.zalan.do/v1/util_test.go b/pkg/apis/acid.zalan.do/v1/util_test.go index fcc5ae5fd..857622581 100644 --- a/pkg/apis/acid.zalan.do/v1/util_test.go +++ b/pkg/apis/acid.zalan.do/v1/util_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "reflect" + "regexp" "testing" "time" @@ -810,3 +811,47 @@ func TestPostgresqlClone(t *testing.T) { }) } } + +func TestAllowedSourceRangesPattern(t *testing.T) { + // pattern used in CRD validation for allowedSourceRanges + pattern := `^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$` + re := regexp.MustCompile(pattern) + + valid := []string{ + // IPv4 + "192.168.1.0/24", + "0.0.0.0/0", + "127.0.0.1/32", + "10.0.0.0/8", + "185.85.220.0/22", + // IPv6 + "fd01::/48", + "::1/128", + "::/0", + "2001:db8::/32", + "fe80::1/64", + "2001:0db8:85a3:0000:0000:8a2e:0370:7334/128", + } + + invalid := []string{ + "999.999.999.999/24", + "192.168.1.0/33", + "192.168.1.0", + "not-an-ip", + "fd01::/129", + "::gggg/64", + "", + } + + for _, cidr := range valid { + if !re.MatchString(cidr) { + t.Errorf("expected %q to match allowedSourceRanges pattern", cidr) + } + } + + for _, cidr := range invalid { + if re.MatchString(cidr) { + t.Errorf("expected %q NOT to match allowedSourceRanges pattern", cidr) + } + } +} From a30e15e47204830a9caf7bde3bbc1d10a6c9b27d Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 12 Jun 2026 15:44:55 +0200 Subject: [PATCH 7/8] re-generate CRDs to latest state (#3109) --- .../postgres-operator/crds/postgresqls.yaml | 40 ++++++++++--------- manifests/postgresql.crd.yaml | 24 +++++++++++ pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml | 24 +++++++++++ 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 4d7c2586a..568ad28ac 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -273,34 +273,26 @@ spec: vars that enable load balancers are pointers because it is important to know if any of them is omitted from the Postgres manifest in that case the var evaluates to nil and the value is taken from the operator config type: boolean + enableMasterNodePort: + description: |- + vars to enable and configure nodeport services + set ports to 0 or nil to let kubernetes decide which port to use + overrides loadbalancer configuration + type: boolean enableMasterPoolerLoadBalancer: type: boolean + enableMasterPoolerNodePort: + type: boolean enableReplicaConnectionPooler: type: boolean enableReplicaLoadBalancer: type: boolean - enableReplicaPoolerLoadBalancer: - type: boolean - enableMasterNodePort: - type: boolean - masterNodePort: - type: integer - minimum: 0 - enableMasterPoolerNodePort: - type: boolean - masterPoolerNodePort: - type: integer - minimum: 0 enableReplicaNodePort: type: boolean - replicaNodePort: - type: integer - minimum: 0 + enableReplicaPoolerLoadBalancer: + type: boolean enableReplicaPoolerNodePort: type: boolean - replicaPoolerNodePort: - type: integer - minimum: 0 enableShmVolume: type: boolean env: @@ -3428,6 +3420,12 @@ spec: pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$' type: string type: array + masterNodePort: + format: int32 + type: integer + masterPoolerNodePort: + format: int32 + type: integer masterServiceAnnotations: additionalProperties: type: string @@ -3732,6 +3730,12 @@ spec: replicaLoadBalancer: description: deprecated type: boolean + replicaNodePort: + format: int32 + type: integer + replicaPoolerNodePort: + format: int32 + type: integer replicaServiceAnnotations: additionalProperties: type: string diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index b139c1db9..72d7153ba 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -274,14 +274,26 @@ spec: vars that enable load balancers are pointers because it is important to know if any of them is omitted from the Postgres manifest in that case the var evaluates to nil and the value is taken from the operator config type: boolean + enableMasterNodePort: + description: |- + vars to enable and configure nodeport services + set ports to 0 or nil to let kubernetes decide which port to use + overrides loadbalancer configuration + type: boolean enableMasterPoolerLoadBalancer: type: boolean + enableMasterPoolerNodePort: + type: boolean enableReplicaConnectionPooler: type: boolean enableReplicaLoadBalancer: type: boolean + enableReplicaNodePort: + type: boolean enableReplicaPoolerLoadBalancer: type: boolean + enableReplicaPoolerNodePort: + type: boolean enableShmVolume: type: boolean env: @@ -3409,6 +3421,12 @@ spec: pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$' type: string type: array + masterNodePort: + format: int32 + type: integer + masterPoolerNodePort: + format: int32 + type: integer masterServiceAnnotations: additionalProperties: type: string @@ -3713,6 +3731,12 @@ spec: replicaLoadBalancer: description: deprecated type: boolean + replicaNodePort: + format: int32 + type: integer + replicaPoolerNodePort: + format: int32 + type: integer replicaServiceAnnotations: additionalProperties: type: string diff --git a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml index b139c1db9..72d7153ba 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml @@ -274,14 +274,26 @@ spec: vars that enable load balancers are pointers because it is important to know if any of them is omitted from the Postgres manifest in that case the var evaluates to nil and the value is taken from the operator config type: boolean + enableMasterNodePort: + description: |- + vars to enable and configure nodeport services + set ports to 0 or nil to let kubernetes decide which port to use + overrides loadbalancer configuration + type: boolean enableMasterPoolerLoadBalancer: type: boolean + enableMasterPoolerNodePort: + type: boolean enableReplicaConnectionPooler: type: boolean enableReplicaLoadBalancer: type: boolean + enableReplicaNodePort: + type: boolean enableReplicaPoolerLoadBalancer: type: boolean + enableReplicaPoolerNodePort: + type: boolean enableShmVolume: type: boolean env: @@ -3409,6 +3421,12 @@ spec: pattern: '^\ *((Mon|Tue|Wed|Thu|Fri|Sat|Sun):(2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))-((2[0-3]|[01]?\d):([0-5]?\d)|(2[0-3]|[01]?\d):([0-5]?\d))\ *$' type: string type: array + masterNodePort: + format: int32 + type: integer + masterPoolerNodePort: + format: int32 + type: integer masterServiceAnnotations: additionalProperties: type: string @@ -3713,6 +3731,12 @@ spec: replicaLoadBalancer: description: deprecated type: boolean + replicaNodePort: + format: int32 + type: integer + replicaPoolerNodePort: + format: int32 + type: integer replicaServiceAnnotations: additionalProperties: type: string From 49cde600b80a46b1e0c414e775a6a748937a7065 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Sat, 13 Jun 2026 00:14:53 +0200 Subject: [PATCH 8/8] update new source ranges validation for kubebuilder (#3110) --- charts/postgres-operator/crds/postgresqls.yaml | 2 +- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 568ad28ac..2bbf5ee49 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -107,7 +107,7 @@ spec: description: load balancers' source ranges are the same for master and replica services items: - pattern: '^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$' + pattern: ^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$ type: string nullable: true type: array diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 71ac73133..81efa42cc 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -83,7 +83,7 @@ type PostgresSpec struct { // load balancers' source ranges are the same for master and replica services // +nullable - // +kubebuilder:validation:items:Pattern=`^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])$` + // +kubebuilder:validation:items:Pattern=`^((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\/(\d|[1-2]\d|3[0-2])|(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9]))$` // +optional AllowedSourceRanges []string `json:"allowedSourceRanges"`