diff --git a/.wordlist-en-custom.txt b/.wordlist-en-custom.txt
index ced7ed7542..c47435d198 100644
--- a/.wordlist-en-custom.txt
+++ b/.wordlist-en-custom.txt
@@ -246,6 +246,7 @@ OnlineUpgrading
OpenSSL
OpenShift
Openshift
+OperatorCapabilities
OperatorGroup
OperatorHub
PGAudit
@@ -272,6 +273,8 @@ PgBouncerSecrets
PgBouncerSecretsVersions
PgBouncerSpec
Philippe
+PluginConfigurationList
+PluginStatus
PoLA
PodAffinity
PodAntiAffinity
@@ -893,6 +896,7 @@ openldap
openshift
operability
operativity
+operatorCapabilities
operatorframework
operatorgorup
operatorgroup
@@ -931,6 +935,7 @@ pid
pitr
plpgsql
pluggable
+pluginStatus
png
podAffinityTerm
podAntiAffinity
@@ -990,6 +995,7 @@ readService
readinessProbe
readthedocs
readyInstances
+reconciler
reconciliationLoop
recoverability
recoveredCluster
diff --git a/api/v1/cluster_plugins.go b/api/v1/cluster_plugins.go
new file mode 100644
index 0000000000..2b6c726b59
--- /dev/null
+++ b/api/v1/cluster_plugins.go
@@ -0,0 +1,39 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ "context"
+
+ "github.com/cloudnative-pg/cloudnative-pg/internal/cnpi/plugin/client"
+ "github.com/cloudnative-pg/cloudnative-pg/internal/configuration"
+)
+
+// NewLoader creates a new plugin client, loading the plugins that are required
+// by this cluster
+func (plugins PluginConfigurationList) NewLoader(ctx context.Context) (client.Client, error) {
+ pluginLoader := client.NewUnixSocketClient(configuration.Current.PluginSocketDir)
+
+ // Load the plugins
+ for _, pluginDeclaration := range plugins {
+ if err := pluginLoader.Load(ctx, pluginDeclaration.Name); err != nil {
+ return nil, err
+ }
+ }
+
+ return pluginLoader, nil
+}
diff --git a/api/v1/cluster_types.go b/api/v1/cluster_types.go
index 20b6de00e2..99571895f1 100644
--- a/api/v1/cluster_types.go
+++ b/api/v1/cluster_types.go
@@ -489,8 +489,16 @@ type ClusterSpec struct {
// The tablespaces configuration
// +optional
Tablespaces []TablespaceConfiguration `json:"tablespaces,omitempty"`
+
+ // The plugins configuration, containing
+ // any plugin to be loaded with the corresponding configuration
+ Plugins PluginConfigurationList `json:"plugins,omitempty"`
}
+// PluginConfigurationList represent a set of plugin with their
+// configuration parameters
+type PluginConfigurationList []PluginConfiguration
+
const (
// PhaseSwitchover when a cluster is changing the primary node
PhaseSwitchover = "Switchover in progress"
@@ -896,6 +904,9 @@ type ClusterStatus struct {
// Image contains the image name used by the pods
// +optional
Image string `json:"image,omitempty"`
+
+ // PluginStatus is the status of the loaded plugins
+ PluginStatus []PluginStatus `json:"pluginStatus,omitempty"`
}
// InstanceReportedState describes the last reported state of an instance during a reconciliation loop
@@ -2343,6 +2354,34 @@ type ManagedConfiguration struct {
Roles []RoleConfiguration `json:"roles,omitempty"`
}
+// PluginConfiguration specifies a plugin that need to be loaded for this
+// cluster to be reconciled
+type PluginConfiguration struct {
+ // Name is the plugin name
+ Name string `json:"name"`
+
+ // Parameters is the configuration of the plugin
+ Parameters map[string]string `json:"parameters,omitempty"`
+}
+
+// PluginStatus is the status of a loaded plugin
+type PluginStatus struct {
+ // Name is the name of the plugin
+ Name string `json:"name"`
+
+ // Version is the version of the plugin loaded by the
+ // latest reconciliation loop
+ Version string `json:"version"`
+
+ // Capabilities are the list of capabilities of the
+ // plugin
+ Capabilities []string `json:"capabilities,omitempty"`
+
+ // OperatorCapabilities are the list of capabilities of the
+ // plugin regarding the reconciler
+ OperatorCapabilities []string `json:"operatorCapabilities,omitempty"`
+}
+
// RoleConfiguration is the representation, in Kubernetes, of a PostgreSQL role
// with the additional field Ensure specifying whether to ensure the presence or
// absence of the role in the database
diff --git a/api/v1/cluster_webhook.go b/api/v1/cluster_webhook.go
index 26e2352668..7aefe1c3ea 100644
--- a/api/v1/cluster_webhook.go
+++ b/api/v1/cluster_webhook.go
@@ -17,6 +17,7 @@ limitations under the License.
package v1
import (
+ "context"
"encoding/json"
"fmt"
"strconv"
@@ -162,6 +163,27 @@ func (r *Cluster) setDefaults(preserveUserSettings bool) {
if len(r.Spec.Tablespaces) > 0 {
r.defaultTablespaces()
}
+
+ ctx := context.Background()
+
+ // Call the plugins to help with defaulting this cluster
+ contextLogger := log.FromContext(ctx)
+ pluginClient, err := r.Spec.Plugins.NewLoader(ctx)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the defaulting webhook, skipping")
+ return
+ }
+ defer func() {
+ pluginClient.Close(ctx)
+ }()
+
+ var mutatedCluster Cluster
+ if err := pluginClient.MutateCluster(ctx, r, &mutatedCluster); err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the defaulting webhook, skipping")
+ return
+ }
+
+ mutatedCluster.DeepCopyInto(r)
}
// defaultTablespaces adds the tablespace owner where the
@@ -286,6 +308,26 @@ var _ webhook.Validator = &Cluster{}
func (r *Cluster) ValidateCreate() (admission.Warnings, error) {
clusterLog.Info("validate create", "name", r.Name, "namespace", r.Namespace)
allErrs := r.Validate()
+
+ // Call the plugins to help validating this cluster creation
+ ctx := context.Background()
+ contextLogger := log.FromContext(ctx)
+ pluginClient, err := r.Spec.Plugins.NewLoader(ctx)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the validate/create webhook")
+ return nil, err
+ }
+ defer func() {
+ pluginClient.Close(ctx)
+ }()
+
+ pluginValidationResult, err := pluginClient.ValidateClusterCreate(ctx, r)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the validate/update webhook")
+ return nil, err
+ }
+ allErrs = append(allErrs, pluginValidationResult...)
+
if len(allErrs) == 0 {
return nil, nil
}
@@ -356,6 +398,25 @@ func (r *Cluster) ValidateUpdate(old runtime.Object) (admission.Warnings, error)
r.ValidateChanges(oldCluster)...,
)
+ // Call the plugins to help validating this cluster update
+ ctx := context.Background()
+ contextLogger := log.FromContext(ctx)
+ pluginClient, err := r.Spec.Plugins.NewLoader(ctx)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the validate/create webhook")
+ return nil, err
+ }
+ defer func() {
+ pluginClient.Close(ctx)
+ }()
+
+ pluginValidationResult, err := pluginClient.ValidateClusterUpdate(ctx, oldCluster, r)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the validate/update webhook")
+ return nil, err
+ }
+ allErrs = append(allErrs, pluginValidationResult...)
+
if len(allErrs) == 0 {
return nil, nil
}
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 3b197d5af4..bcbae22f0e 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -871,6 +871,13 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
+ if in.Plugins != nil {
+ in, out := &in.Plugins, &out.Plugins
+ *out = make(PluginConfigurationList, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterSpec.
@@ -980,6 +987,13 @@ func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.PluginStatus != nil {
+ in, out := &in.PluginStatus, &out.PluginStatus
+ *out = make([]PluginStatus, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus.
@@ -1754,6 +1768,74 @@ func (in *PgBouncerSpec) DeepCopy() *PgBouncerSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PluginConfiguration) DeepCopyInto(out *PluginConfiguration) {
+ *out = *in
+ if in.Parameters != nil {
+ in, out := &in.Parameters, &out.Parameters
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConfiguration.
+func (in *PluginConfiguration) DeepCopy() *PluginConfiguration {
+ if in == nil {
+ return nil
+ }
+ out := new(PluginConfiguration)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in PluginConfigurationList) DeepCopyInto(out *PluginConfigurationList) {
+ {
+ in := &in
+ *out = make(PluginConfigurationList, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginConfigurationList.
+func (in PluginConfigurationList) DeepCopy() PluginConfigurationList {
+ if in == nil {
+ return nil
+ }
+ out := new(PluginConfigurationList)
+ in.DeepCopyInto(out)
+ return *out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PluginStatus) DeepCopyInto(out *PluginStatus) {
+ *out = *in
+ if in.Capabilities != nil {
+ in, out := &in.Capabilities, &out.Capabilities
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.OperatorCapabilities != nil {
+ in, out := &in.OperatorCapabilities, &out.OperatorCapabilities
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginStatus.
+func (in *PluginStatus) DeepCopy() *PluginStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(PluginStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodTemplateSpec) DeepCopyInto(out *PodTemplateSpec) {
*out = *in
diff --git a/config/crd/bases/postgresql.cnpg.io_clusters.yaml b/config/crd/bases/postgresql.cnpg.io_clusters.yaml
index 2ae6fc75ca..633daab84f 100644
--- a/config/crd/bases/postgresql.cnpg.io_clusters.yaml
+++ b/config/crd/bases/postgresql.cnpg.io_clusters.yaml
@@ -3210,6 +3210,25 @@ spec:
up again) or not (recreate it elsewhere - when `instances` >1)
type: boolean
type: object
+ plugins:
+ description: The plugins configuration, containing any plugin to be
+ loaded with the corresponding configuration
+ items:
+ description: PluginConfiguration specifies a plugin that need to
+ be loaded for this cluster to be reconciled
+ properties:
+ name:
+ description: Name is the plugin name
+ type: string
+ parameters:
+ additionalProperties:
+ type: string
+ description: Parameters is the configuration of the plugin
+ type: object
+ required:
+ - name
+ type: object
+ type: array
postgresGID:
default: 26
description: The GID of the `postgres` user inside the image, defaults
@@ -5141,6 +5160,35 @@ spec:
phaseReason:
description: Reason for the current phase
type: string
+ pluginStatus:
+ description: PluginStatus is the status of the loaded plugins
+ items:
+ description: PluginStatus is the status of a loaded plugin
+ properties:
+ capabilities:
+ description: Capabilities are the list of capabilities of the
+ plugin
+ items:
+ type: string
+ type: array
+ name:
+ description: Name is the name of the plugin
+ type: string
+ operatorCapabilities:
+ description: OperatorCapabilities are the list of capabilities
+ of the plugin regarding the reconciler
+ items:
+ type: string
+ type: array
+ version:
+ description: Version is the version of the plugin loaded by
+ the latest reconciliation loop
+ type: string
+ required:
+ - name
+ - version
+ type: object
+ type: array
poolerIntegrations:
description: The integration needed by poolers referencing the cluster
properties:
diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go
index b6a083bcc7..8f78046006 100644
--- a/controllers/cluster_controller.go
+++ b/controllers/cluster_controller.go
@@ -195,6 +195,11 @@ func (r *ClusterReconciler) reconcile(ctx context.Context, cluster *apiv1.Cluste
return ctrl.Result{}, fmt.Errorf("cannot set image name: %w", err)
}
+ // Ensure we load all the plugins that are required to reconcile this cluster
+ if err := r.preReconcilePlugins(ctx, cluster); err != nil {
+ return ctrl.Result{}, fmt.Errorf("cannot reconciled required plugins: %w", err)
+ }
+
// Ensure we reconcile the orphan resources if present when we reconcile for the first time a cluster
if err := r.reconcileRestoredCluster(ctx, cluster); err != nil {
return ctrl.Result{}, fmt.Errorf("cannot reconcile restored Cluster: %w", err)
diff --git a/controllers/cluster_create.go b/controllers/cluster_create.go
index 8d5bf666eb..e532d16646 100644
--- a/controllers/cluster_create.go
+++ b/controllers/cluster_create.go
@@ -1271,6 +1271,23 @@ func (r *ClusterReconciler) ensureInstancesAreCreated(
utils.InheritLabels(&instanceToCreate.ObjectMeta, cluster.Labels,
cluster.GetFixedInheritedLabels(), configuration.Current)
+ // Call the plugins to enrich this Pod definition
+ pluginClient, err := cluster.Spec.Plugins.NewLoader(ctx)
+ if err != nil {
+ contextLogger.Error(err, "Error invoking plugin meanwhile creating Pods")
+ return ctrl.Result{}, err
+ }
+ defer func() {
+ pluginClient.Close(ctx)
+ }()
+
+ var mutatedPod corev1.Pod
+ if err := pluginClient.MutatePod(ctx, cluster, instanceToCreate, &mutatedPod); err != nil {
+ contextLogger.Error(err, "Error invoking plugin in the defaulting webhook, skipping")
+ return ctrl.Result{}, err
+ }
+ mutatedPod.DeepCopyInto(instanceToCreate)
+
if err := r.Create(ctx, instanceToCreate); err != nil {
if apierrs.IsAlreadyExists(err) {
// This Pod was already created, maybe the cache is stale.
diff --git a/controllers/cluster_plugins.go b/controllers/cluster_plugins.go
new file mode 100644
index 0000000000..82db27ea38
--- /dev/null
+++ b/controllers/cluster_plugins.go
@@ -0,0 +1,56 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package controllers contains the controller of the CRD
+package controllers
+
+import (
+ "context"
+
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
+ "github.com/cloudnative-pg/cloudnative-pg/pkg/management/log"
+)
+
+// reconcilePlugins ensures that we load the plugins that are required to reconcile
+// this cluster
+func (r *ClusterReconciler) preReconcilePlugins(ctx context.Context, cluster *apiv1.Cluster) error {
+ contextLogger := log.FromContext(ctx)
+
+ // Load the plugins
+ pluginClient, err := cluster.Spec.Plugins.NewLoader(ctx)
+ if err != nil {
+ contextLogger.Error(err, "Error loading plugins, retrying")
+ return err
+ }
+ defer func() {
+ pluginClient.Close(ctx)
+ }()
+
+ // Get the status of the plugins and store it inside the status section
+ oldCluster := cluster.DeepCopy()
+ metadataList := pluginClient.MetadataList()
+ cluster.Status.PluginStatus = make([]apiv1.PluginStatus, len(metadataList))
+ for i, entry := range metadataList {
+ cluster.Status.PluginStatus[i].Name = entry.Name
+ cluster.Status.PluginStatus[i].Version = entry.Version
+ cluster.Status.PluginStatus[i].Capabilities = entry.Capabilities
+ cluster.Status.PluginStatus[i].OperatorCapabilities = entry.OperatorCapabilities
+ }
+
+ return r.Client.Status().Patch(ctx, cluster, client.MergeFrom(oldCluster))
+}
diff --git a/docs/src/cloudnative-pg.v1.md b/docs/src/cloudnative-pg.v1.md
index b5478083e5..fa1fcb9ad5 100644
--- a/docs/src/cloudnative-pg.v1.md
+++ b/docs/src/cloudnative-pg.v1.md
@@ -1846,6 +1846,14 @@ Defaults to: RuntimeDefault
The tablespaces configuration
+plugins [Required]
+PluginConfigurationList
+ |
+
+ The plugins configuration, containing
+any plugin to be loaded with the corresponding configuration
+ |
+
@@ -2168,6 +2176,13 @@ This field is reported when .spec.failoverDelay
is populated or dur
Image contains the image name used by the pods
+pluginStatus [Required]
+[]PluginStatus
+ |
+
+ PluginStatus is the status of the loaded plugins
+ |
+
@@ -3406,6 +3421,54 @@ the operator calls PgBouncer's PAUSE
and RESUME
comman
+## PluginStatus {#postgresql-cnpg-io-v1-PluginStatus}
+
+
+**Appears in:**
+
+- [ClusterStatus](#postgresql-cnpg-io-v1-ClusterStatus)
+
+
+PluginStatus is the status of a loaded plugin
+
+
+
+Field | Description |
+
+name [Required]
+string
+ |
+
+ Name is the name of the plugin
+ |
+
+version [Required]
+string
+ |
+
+ Version is the version of the plugin loaded by the
+latest reconciliation loop
+ |
+
+capabilities [Required]
+[]string
+ |
+
+ Capabilities are the list of capabilities of the
+plugin
+ |
+
+operatorCapabilities [Required]
+[]string
+ |
+
+ OperatorCapabilities are the list of capabilities of the
+plugin regarding the reconciler
+ |
+
+
+
+
## PodTemplateSpec {#postgresql-cnpg-io-v1-PodTemplateSpec}
diff --git a/docs/src/samples/cluster-example-bis-restore-cr.yaml b/docs/src/samples/cluster-example-bis-restore-cr.yaml
new file mode 100644
index 0000000000..4958cadc06
--- /dev/null
+++ b/docs/src/samples/cluster-example-bis-restore-cr.yaml
@@ -0,0 +1,26 @@
+apiVersion: postgresql.cnpg.io/v1
+kind: Cluster
+metadata:
+ name: cluster-restore
+spec:
+ instances: 3
+
+ storage:
+ size: 1Gi
+ storageClass: csi-hostpath-sc
+ walStorage:
+ size: 1Gi
+ storageClass: csi-hostpath-sc
+
+ bootstrap:
+ recovery:
+ volumeSnapshots:
+ storage:
+ name: cluster-example-20231031161103
+ kind: VolumeSnapshot
+ apiGroup: snapshot.storage.k8s.io
+ walStorage:
+ name: cluster-example-20231031161103-wal
+ kind: VolumeSnapshot
+ apiGroup: snapshot.storage.k8s.io
+
diff --git a/docs/src/samples/cluster-example-bis-restore.yaml b/docs/src/samples/cluster-example-bis-restore.yaml
new file mode 100644
index 0000000000..7f814a89fe
--- /dev/null
+++ b/docs/src/samples/cluster-example-bis-restore.yaml
@@ -0,0 +1,43 @@
+apiVersion: postgresql.cnpg.io/v1
+kind: Cluster
+metadata:
+ name: cluster-restore
+spec:
+ instances: 3
+ imageName: registry.dev:5000/postgresql:16
+
+ storage:
+ size: 1Gi
+ storageClass: csi-hostpath-sc
+ walStorage:
+ size: 1Gi
+ storageClass: csi-hostpath-sc
+
+ # Backup properties
+ # This assumes a local minio setup
+# backup:
+# barmanObjectStore:
+# destinationPath: s3://backups/
+# endpointURL: http://minio:9000
+# s3Credentials:
+# accessKeyId:
+# name: minio
+# key: ACCESS_KEY_ID
+# secretAccessKey:
+# name: minio
+# key: ACCESS_SECRET_KEY
+# wal:
+# compression: gzip
+
+ bootstrap:
+ recovery:
+ volumeSnapshots:
+ storage:
+ name: snapshot-0bc6095db42768c7a1fe897494a966f541ef5fb29b2eb8e9399d80bd0a32408a-2023-11-13-7.41.53
+ kind: VolumeSnapshot
+ apiGroup: snapshot.storage.k8s.io
+ walStorage:
+ name: snapshot-a67084ba08097fd8c3e34c6afef8110091da67e5895f0379fd2df5b9f73ff524-2023-11-13-7.41.53
+ kind: VolumeSnapshot
+ apiGroup: snapshot.storage.k8s.io
+
diff --git a/docs/src/samples/cluster-example-bis.yaml b/docs/src/samples/cluster-example-bis.yaml
new file mode 100644
index 0000000000..a99b205f1b
--- /dev/null
+++ b/docs/src/samples/cluster-example-bis.yaml
@@ -0,0 +1,29 @@
+apiVersion: postgresql.cnpg.io/v1
+kind: Cluster
+metadata:
+ name: cluster-example
+spec:
+ instances: 3
+ imageName: registry.dev:5000/postgresql:16
+
+ backup:
+ volumeSnapshot:
+ className: csi-hostpath-groupsnapclass
+ #className: csi-hostpath-snapclass
+ groupSnapshot: true
+
+ storage:
+ storageClass: csi-hostpath-sc
+ size: 1Gi
+ walStorage:
+ storageClass: csi-hostpath-sc
+ size: 1Gi
+ # tablespaces:
+ # first:
+ # storage:
+ # storageClass: csi-hostpath-sc
+ # size: 1Gi
+ # second:
+ # storage:
+ # storageClass: csi-hostpath-sc
+ # size: 1Gi
diff --git a/go.mod b/go.mod
index 784cb360f8..2599e03d94 100644
--- a/go.mod
+++ b/go.mod
@@ -8,10 +8,13 @@ require (
github.com/avast/retry-go/v4 v4.5.1
github.com/blang/semver v3.5.1+incompatible
github.com/cheynewallace/tabby v1.1.1
+ github.com/cloudnative-pg/cnpg-i v0.0.0-20240122164555-5215ff219c8f
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
+ github.com/evanphx/json-patch/v5 v5.8.0
github.com/go-logr/logr v1.4.1
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
+ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1
github.com/jackc/pgx/v5 v5.5.4
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0
@@ -31,6 +34,7 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
golang.org/x/sys v0.18.0
+ google.golang.org/grpc v1.60.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.29.2
k8s.io/apiextensions-apiserver v0.29.2
@@ -49,7 +53,6 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
- github.com/evanphx/json-patch/v5 v5.8.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
@@ -100,7 +103,8 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.18.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
- google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/appengine v1.6.8 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 2915f952db..17a55f2194 100644
--- a/go.sum
+++ b/go.sum
@@ -23,6 +23,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudnative-pg/cnpg-i v0.0.0-20240122164555-5215ff219c8f h1:ypwPq45y8ezzwxUTHL0VkzkT2+pcHnE4yRoeGTP8fp8=
+github.com/cloudnative-pg/cnpg-i v0.0.0-20240122164555-5215ff219c8f/go.mod h1:0G5GXQVj09KvONIcYURyroL74zOFGjv4eI5OXz7/G/0=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@@ -62,7 +64,6 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -71,6 +72,7 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
@@ -101,6 +103,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1 h1:HcUWd006luQPljE73d5sk+/VgYPGUReEVz2y1/qylwY=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
@@ -211,6 +215,7 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
@@ -224,6 +229,7 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -234,15 +240,17 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -253,6 +261,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -260,17 +269,23 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
@@ -283,6 +298,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -293,14 +309,18 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
+google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/internal/cnpi/plugin/client/cluster.go b/internal/cnpi/plugin/client/cluster.go
new file mode 100644
index 0000000000..0894f21f19
--- /dev/null
+++ b/internal/cnpi/plugin/client/cluster.go
@@ -0,0 +1,204 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "slices"
+
+ "github.com/cloudnative-pg/cnpg-i/pkg/operator"
+ jsonpatch "github.com/evanphx/json-patch/v5"
+ "k8s.io/apimachinery/pkg/util/validation/field"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/cloudnative-pg/cloudnative-pg/pkg/management/log"
+)
+
+func (data *data) MutateCluster(ctx context.Context, object client.Object, mutatedObject client.Object) error {
+ contextLogger := log.FromContext(ctx)
+
+ serializedObject, err := json.Marshal(object)
+ if err != nil {
+ return fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ object.GetObjectKind().GroupVersionKind().Kind,
+ object.GetNamespace(), object.GetName(),
+ err,
+ )
+ }
+
+ for idx := range data.plugins {
+ plugin := &data.plugins[idx]
+
+ if !slices.Contains(plugin.operatorCapabilities, operator.OperatorCapability_RPC_TYPE_MUTATE_CLUSTER) {
+ continue
+ }
+
+ contextLogger := contextLogger.WithValues(
+ "pluginName", plugin.name,
+ )
+ request := operator.OperatorMutateClusterRequest{
+ Definition: serializedObject,
+ }
+
+ contextLogger.Trace("Calling MutateCluster endpoint", "definition", request.Definition)
+ result, err := plugin.operatorClient.MutateCluster(ctx, &request)
+ if err != nil {
+ contextLogger.Error(err, "Error while calling MutateCluster")
+ return err
+ }
+
+ if len(result.JsonPatch) == 0 {
+ // There's nothing to mutate
+ continue
+ }
+
+ mutatedObject, err := jsonpatch.MergePatch(serializedObject, result.JsonPatch)
+ if err != nil {
+ contextLogger.Error(err, "Error while applying JSON patch from plugin", "patch", result.JsonPatch)
+ return err
+ }
+
+ serializedObject = mutatedObject
+ }
+
+ if err := json.Unmarshal(serializedObject, mutatedObject); err != nil {
+ return fmt.Errorf("while deserializing %s %s/%s to JSON: %w",
+ object.GetObjectKind().GroupVersionKind().Kind,
+ object.GetNamespace(), object.GetName(),
+ err,
+ )
+ }
+
+ return nil
+}
+
+func (data *data) ValidateClusterCreate(
+ ctx context.Context,
+ object client.Object,
+) (field.ErrorList, error) {
+ contextLogger := log.FromContext(ctx)
+
+ serializedObject, err := json.Marshal(object)
+ if err != nil {
+ return nil, fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ object.GetObjectKind().GroupVersionKind().Kind,
+ object.GetNamespace(), object.GetName(),
+ err,
+ )
+ }
+
+ var validationErrors []*operator.ValidationError
+ for idx := range data.plugins {
+ plugin := &data.plugins[idx]
+
+ if !slices.Contains(plugin.operatorCapabilities, operator.OperatorCapability_RPC_TYPE_VALIDATE_CLUSTER_CREATE) {
+ continue
+ }
+
+ contextLogger := contextLogger.WithValues(
+ "pluginName", plugin.name,
+ )
+ request := operator.OperatorValidateClusterCreateRequest{
+ Definition: serializedObject,
+ }
+
+ contextLogger.Trace("Calling ValidatedClusterCreate endpoint", "definition", request.Definition)
+ result, err := plugin.operatorClient.ValidateClusterCreate(ctx, &request)
+ if err != nil {
+ contextLogger.Error(err, "Error while calling ValidatedClusterCreate")
+ return nil, err
+ }
+
+ validationErrors = append(validationErrors, result.ValidationErrors...)
+ }
+
+ return validationErrorsToErrorList(validationErrors), nil
+}
+
+func (data *data) ValidateClusterUpdate(
+ ctx context.Context,
+ oldObject client.Object,
+ newObject client.Object,
+) (field.ErrorList, error) {
+ contextLogger := log.FromContext(ctx)
+
+ serializedOldObject, err := json.Marshal(oldObject)
+ if err != nil {
+ return nil, fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ oldObject.GetObjectKind().GroupVersionKind().Kind,
+ oldObject.GetNamespace(), oldObject.GetName(),
+ err,
+ )
+ }
+
+ serializedNewObject, err := json.Marshal(newObject)
+ if err != nil {
+ return nil, fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ newObject.GetObjectKind().GroupVersionKind().Kind,
+ newObject.GetNamespace(), newObject.GetName(),
+ err,
+ )
+ }
+
+ var validationErrors []*operator.ValidationError
+ for idx := range data.plugins {
+ plugin := &data.plugins[idx]
+
+ if !slices.Contains(plugin.operatorCapabilities, operator.OperatorCapability_RPC_TYPE_VALIDATE_CLUSTER_CHANGE) {
+ continue
+ }
+
+ contextLogger := contextLogger.WithValues(
+ "pluginName", plugin.name,
+ )
+ request := operator.OperatorValidateClusterChangeRequest{
+ OldCluster: serializedOldObject,
+ NewCluster: serializedNewObject,
+ }
+
+ contextLogger.Trace(
+ "Calling ValidateClusterChange endpoint",
+ "oldCluster", request.OldCluster,
+ "newCluster", request.NewCluster)
+ result, err := plugin.operatorClient.ValidateClusterChange(ctx, &request)
+ if err != nil {
+ contextLogger.Error(err, "Error while calling ValidatedClusterCreate")
+ return nil, err
+ }
+
+ validationErrors = append(validationErrors, result.ValidationErrors...)
+ }
+
+ return validationErrorsToErrorList(validationErrors), nil
+}
+
+// validationErrorsToErrorList makes up a list of validation errors as required by
+// the Kubernetes API from the GRPC plugin interface types
+func validationErrorsToErrorList(validationErrors []*operator.ValidationError) (result field.ErrorList) {
+ result = make(field.ErrorList, len(validationErrors))
+ for i, validationError := range validationErrors {
+ result[i] = field.Invalid(
+ field.NewPath(validationError.PathComponents[0], validationError.PathComponents[1:]...),
+ validationError.Value,
+ validationError.Message,
+ )
+ }
+
+ return result
+}
diff --git a/internal/cnpi/plugin/client/connection.go b/internal/cnpi/plugin/client/connection.go
new file mode 100644
index 0000000000..bd79e1950a
--- /dev/null
+++ b/internal/cnpi/plugin/client/connection.go
@@ -0,0 +1,251 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package client
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "path"
+ "slices"
+ "time"
+
+ "github.com/cloudnative-pg/cnpg-i/pkg/identity"
+ "github.com/cloudnative-pg/cnpg-i/pkg/operator"
+ "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/cloudnative-pg/cloudnative-pg/pkg/management/log"
+)
+
+// defaultTimeout is the timeout applied by default to every GRPC call
+const defaultTimeout = 30 * time.Second
+
+type protocol interface {
+ dial(ctx context.Context, path string) (connectionHandler, error)
+}
+
+type connectionHandler interface {
+ grpc.ClientConnInterface
+ io.Closer
+}
+
+type protocolUnix string
+
+func (p protocolUnix) dial(ctx context.Context, path string) (connectionHandler, error) {
+ contextLogger := log.FromContext(ctx)
+ dialPath := fmt.Sprintf("unix://%s", path)
+
+ contextLogger.Debug("Connecting to plugin", "path", dialPath)
+
+ return grpc.Dial(
+ dialPath,
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ grpc.WithUnaryInterceptor(
+ timeout.UnaryClientInterceptor(defaultTimeout),
+ ),
+ )
+}
+
+// data represent a new CNPI client collection
+type data struct {
+ pluginPath string
+ protocol protocol
+ plugins []pluginData
+}
+
+type pluginData struct {
+ connection connectionHandler
+ identityClient identity.IdentityClient
+ operatorClient operator.OperatorClient
+
+ name string
+ version string
+ capabilities []identity.PluginCapability_Service_Type
+ operatorCapabilities []operator.OperatorCapability_RPC_Type
+}
+
+// NewUnixSocketClient creates a new CNPI client discovering plugins
+// registered in a specific path
+func NewUnixSocketClient(pluginPath string) Client {
+ return &data{
+ pluginPath: pluginPath,
+ protocol: protocolUnix(""),
+ }
+}
+
+func (data *data) Load(ctx context.Context, name string) error {
+ pluginData, err := data.loadPlugin(ctx, name)
+ if err != nil {
+ return err
+ }
+
+ data.plugins = append(data.plugins, pluginData)
+ return nil
+}
+
+func (data *data) MetadataList() []Metadata {
+ result := make([]Metadata, len(data.plugins))
+ for i := range data.plugins {
+ result[i] = data.plugins[i].Metadata()
+ }
+
+ return result
+}
+
+func (data *data) loadPlugin(ctx context.Context, name string) (pluginData, error) {
+ var connection connectionHandler
+ var err error
+
+ defer func() {
+ if err != nil && connection != nil {
+ _ = connection.Close()
+ }
+ }()
+
+ contextLogger := log.FromContext(ctx).WithValues("pluginName", name)
+ ctx = log.IntoContext(ctx, contextLogger)
+
+ if connection, err = data.protocol.dial(
+ ctx,
+ path.Join(data.pluginPath, name),
+ ); err != nil {
+ contextLogger.Error(err, "Error while connecting to plugin")
+ return pluginData{}, err
+ }
+
+ var result pluginData
+ result, err = newPluginDataFromConnection(ctx, connection)
+ if err != nil {
+ return pluginData{}, err
+ }
+
+ // Load the list of services implemented by the plugin
+ if err = result.loadPluginCapabilities(ctx); err != nil {
+ return pluginData{}, err
+ }
+
+ // If the plugin implements the Operator service, load its
+ // capabilities
+ if slices.Contains(result.capabilities, identity.PluginCapability_Service_TYPE_OPERATOR_SERVICE) {
+ if err = result.loadOperatorCapabilities(ctx); err != nil {
+ return pluginData{}, err
+ }
+ }
+
+ return result, nil
+}
+
+func (data *data) Close(ctx context.Context) {
+ contextLogger := log.FromContext(ctx)
+ for i := range data.plugins {
+ plugin := &data.plugins[i]
+ contextLogger := contextLogger.WithValues("pluginName", plugin.name)
+
+ if err := plugin.connection.Close(); err != nil {
+ contextLogger.Error(err, "while closing plugin connection")
+ }
+ }
+
+ data.plugins = nil
+}
+
+func newPluginDataFromConnection(ctx context.Context, connection connectionHandler) (pluginData, error) {
+ var err error
+
+ identityClient := identity.NewIdentityClient(connection)
+
+ var pluginInfoResponse *identity.GetPluginMetadataResponse
+
+ if pluginInfoResponse, err = identityClient.GetPluginMetadata(
+ ctx,
+ &identity.GetPluginMetadataRequest{},
+ ); err != nil {
+ return pluginData{}, fmt.Errorf("while querying plugin identity: %w", err)
+ }
+
+ result := pluginData{}
+ result.connection = connection
+ result.name = pluginInfoResponse.Name
+ result.version = pluginInfoResponse.Version
+ result.identityClient = identity.NewIdentityClient(connection)
+ result.operatorClient = operator.NewOperatorClient(connection)
+
+ return result, err
+}
+
+func (pluginData *pluginData) loadPluginCapabilities(ctx context.Context) error {
+ var pluginCapabilitiesResponse *identity.GetPluginCapabilitiesResponse
+ var err error
+
+ if pluginCapabilitiesResponse, err = pluginData.identityClient.GetPluginCapabilities(
+ ctx,
+ &identity.GetPluginCapabilitiesRequest{},
+ ); err != nil {
+ return fmt.Errorf("while querying plugin capabilities: %w", err)
+ }
+
+ pluginData.capabilities = make([]identity.PluginCapability_Service_Type, len(pluginCapabilitiesResponse.Capabilities))
+ for i := range pluginData.capabilities {
+ pluginData.capabilities[i] = pluginCapabilitiesResponse.Capabilities[i].GetService().Type
+ }
+
+ return nil
+}
+
+func (pluginData *pluginData) loadOperatorCapabilities(ctx context.Context) error {
+ var operatorCapabilitiesResponse *operator.OperatorCapabilitiesResult
+ var err error
+
+ if operatorCapabilitiesResponse, err = pluginData.operatorClient.GetCapabilities(
+ ctx,
+ &operator.OperatorCapabilitiesRequest{},
+ ); err != nil {
+ return fmt.Errorf("while querying plugin operator capabilities: %w", err)
+ }
+
+ pluginData.operatorCapabilities = make(
+ []operator.OperatorCapability_RPC_Type,
+ len(operatorCapabilitiesResponse.Capabilities))
+ for i := range pluginData.operatorCapabilities {
+ pluginData.operatorCapabilities[i] = operatorCapabilitiesResponse.Capabilities[i].GetRpc().Type
+ }
+
+ return nil
+}
+
+// Metadata extracts the plugin metadata reading from
+// the internal metadata
+func (pluginData *pluginData) Metadata() Metadata {
+ result := Metadata{
+ Name: pluginData.name,
+ Version: pluginData.version,
+ Capabilities: make([]string, len(pluginData.capabilities)),
+ OperatorCapabilities: make([]string, len(pluginData.operatorCapabilities)),
+ }
+
+ for i := range pluginData.capabilities {
+ result.Capabilities[i] = pluginData.capabilities[i].String()
+ }
+
+ for i := range pluginData.operatorCapabilities {
+ result.OperatorCapabilities[i] = pluginData.operatorCapabilities[i].String()
+ }
+
+ return result
+}
diff --git a/internal/cnpi/plugin/client/contracts.go b/internal/cnpi/plugin/client/contracts.go
new file mode 100644
index 0000000000..dafde761a5
--- /dev/null
+++ b/internal/cnpi/plugin/client/contracts.go
@@ -0,0 +1,90 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package client
+
+import (
+ "context"
+
+ "k8s.io/apimachinery/pkg/util/validation/field"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Metadata expose the metadata as discovered
+// from a plugin
+type Metadata struct {
+ Name string
+ Version string
+ Capabilities []string
+ OperatorCapabilities []string
+}
+
+// Client describes a set of behaviour needed to properly handle all the plugin client expected features
+type Client interface {
+ Connection
+ ClusterCapabilities
+ PodCapabilities
+}
+
+// Connection describes a set of behaviour needed to properly handle the plugin connections
+type Connection interface {
+ // Load connect to the plugin with the specified name
+ Load(ctx context.Context, name string) error
+
+ // Close closes the connection to every loaded plugin
+ Close(ctx context.Context)
+
+ // MetadataList exposes the metadata of the loaded plugins
+ MetadataList() []Metadata
+}
+
+// PodCapabilities describes a set of behaviour needed to implement the Pod capabilities
+type PodCapabilities interface {
+ // MutatePod calls the loaded plugins to help to enhance
+ // a PostgreSQL instance Pod definition
+ MutatePod(
+ ctx context.Context,
+ cluster client.Object,
+ object client.Object,
+ mutatedObject client.Object,
+ ) error
+}
+
+// ClusterCapabilities describes a set of behaviour needed to implement the Cluster capabilities
+type ClusterCapabilities interface {
+ // MutateCluster calls the loaded plugisn to help to enhance
+ // a cluster definition
+ MutateCluster(
+ ctx context.Context,
+ object client.Object,
+ mutatedObject client.Object,
+ ) error
+
+ // ValidateClusterCreate calls all the loaded plugin to check if a cluster definition
+ // is correct
+ ValidateClusterCreate(
+ ctx context.Context,
+ object client.Object,
+ ) (field.ErrorList, error)
+
+ // ValidateClusterUpdate calls all the loaded plugin to check if a cluster can
+ // be changed from a value to another
+ ValidateClusterUpdate(
+ ctx context.Context,
+ oldObject client.Object,
+ newObject client.Object,
+ ) (field.ErrorList, error)
+}
diff --git a/internal/cnpi/plugin/client/doc.go b/internal/cnpi/plugin/client/doc.go
new file mode 100644
index 0000000000..1cb0e5ee6d
--- /dev/null
+++ b/internal/cnpi/plugin/client/doc.go
@@ -0,0 +1,19 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package client contains a set of helper structures for CNPG to use the
+// plugins exposing the CNPI interface
+package client
diff --git a/internal/cnpi/plugin/client/pod.go b/internal/cnpi/plugin/client/pod.go
new file mode 100644
index 0000000000..f9a6fe4ee6
--- /dev/null
+++ b/internal/cnpi/plugin/client/pod.go
@@ -0,0 +1,106 @@
+/*
+Copyright The CloudNativePG Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "slices"
+
+ "github.com/cloudnative-pg/cnpg-i/pkg/operator"
+ jsonpatch "github.com/evanphx/json-patch/v5"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/cloudnative-pg/cloudnative-pg/pkg/management/log"
+)
+
+func (data *data) MutatePod(
+ ctx context.Context,
+ cluster client.Object,
+ object client.Object,
+ mutatedObject client.Object,
+) error {
+ contextLogger := log.FromContext(ctx)
+
+ serializedCluster, err := json.Marshal(cluster)
+ if err != nil {
+ return fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ cluster.GetObjectKind().GroupVersionKind().Kind,
+ cluster.GetNamespace(), cluster.GetName(),
+ err,
+ )
+ }
+
+ serializedObject, err := json.Marshal(object)
+ if err != nil {
+ return fmt.Errorf("while serializing %s %s/%s to JSON: %w",
+ object.GetObjectKind().GroupVersionKind().Kind,
+ object.GetNamespace(), object.GetName(),
+ err,
+ )
+ }
+
+ for idx := range data.plugins {
+ plugin := &data.plugins[idx]
+
+ if !slices.Contains(plugin.operatorCapabilities, operator.OperatorCapability_RPC_TYPE_MUTATE_POD) {
+ continue
+ }
+
+ contextLogger := contextLogger.WithValues(
+ "pluginName", plugin.name,
+ )
+ request := operator.OperatorMutatePodRequest{
+ ClusterDefinition: serializedCluster,
+ PodDefinition: serializedObject,
+ }
+
+ contextLogger.Trace(
+ "Calling MutatePod endpoint",
+ "clusterDefinition", request.ClusterDefinition,
+ "podDefinition", request.PodDefinition)
+ result, err := plugin.operatorClient.MutatePod(ctx, &request)
+ if err != nil {
+ contextLogger.Error(err, "Error while calling MutatePod")
+ return err
+ }
+
+ if len(result.JsonPatch) == 0 {
+ // There's nothing to mutate
+ continue
+ }
+
+ mutatedObject, err := jsonpatch.MergePatch(serializedObject, result.JsonPatch)
+ if err != nil {
+ contextLogger.Error(err, "Error while applying JSON patch from plugin", "patch", result.JsonPatch)
+ return err
+ }
+
+ serializedObject = mutatedObject
+ }
+
+ if err := json.Unmarshal(serializedObject, mutatedObject); err != nil {
+ return fmt.Errorf("while deserializing %s %s/%s to JSON: %w",
+ object.GetObjectKind().GroupVersionKind().Kind,
+ object.GetNamespace(), object.GetName(),
+ err,
+ )
+ }
+
+ return nil
+}
diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go
index 1bcdfb64d7..44cab9a14a 100644
--- a/internal/configuration/configuration.go
+++ b/internal/configuration/configuration.go
@@ -32,6 +32,9 @@ var configurationLog = log.WithName("configuration")
// DefaultOperatorPullSecretName is implicitly copied into newly created clusters.
const DefaultOperatorPullSecretName = "cnpg-pull-secret" // #nosec
+// DefaultPluginSocketDir is the default directory where the plugin sockets are located.
+const DefaultPluginSocketDir = "/plugins"
+
// Data is the struct containing the configuration of the operator.
// Usually the operator code will use the "Current" configuration.
type Data struct {
@@ -39,6 +42,10 @@ type Data struct {
// need to written. This is different between plain Kubernetes and OpenShift
WebhookCertDir string `json:"webhookCertDir" env:"WEBHOOK_CERT_DIR"`
+ // PluginSocketDir is the directory where the plugins sockets are to be
+ // found
+ PluginSocketDir string `json:"pluginSocketDir" env:"PLUGIN_SOCKET_DIR"`
+
// WatchNamespace is the namespace where the operator should watch and
// is configurable via environment variables in the OpenShift console.
// Multiple namespaces can be specified separated by comma
@@ -99,6 +106,7 @@ func newDefaultConfig() *Data {
OperatorPullSecretName: DefaultOperatorPullSecretName,
OperatorImageName: versions.DefaultOperatorImageName,
PostgresImageName: versions.DefaultImageName,
+ PluginSocketDir: DefaultPluginSocketDir,
CreateAnyService: false,
}
}