From 206f6fc2c14f157facad5687856f0a61a13171f8 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Fri, 5 Sep 2025 16:39:59 -0400 Subject: [PATCH 01/10] add plugin --- plugins/wal-replica/.gitignore | 7 + plugins/wal-replica/Dockerfile | 20 ++ plugins/wal-replica/LICENSE | 23 ++ plugins/wal-replica/README.md | 57 ++++ plugins/wal-replica/cmd/plugin/doc.go | 5 + plugins/wal-replica/cmd/plugin/plugin.go | 39 +++ plugins/wal-replica/doc/development.md | 180 ++++++++++++ .../cluster-example-no-parameters.yaml | 12 + .../cluster-example-with-mistake.yaml | 23 ++ .../doc/examples/cluster-example.yaml | 16 ++ .../example/cluster-with-wal-receiver.yaml | 61 ++++ plugins/wal-replica/go.mod | 94 +++++++ plugins/wal-replica/go.sum | 261 ++++++++++++++++++ plugins/wal-replica/internal/config/config.go | 105 +++++++ plugins/wal-replica/internal/config/doc.go | 5 + plugins/wal-replica/internal/identity/doc.go | 6 + plugins/wal-replica/internal/identity/impl.go | 50 ++++ plugins/wal-replica/internal/k8sclient/doc.go | 5 + .../internal/k8sclient/k8sclient.go | 63 +++++ plugins/wal-replica/internal/operator/doc.go | 6 + plugins/wal-replica/internal/operator/impl.go | 51 ++++ .../internal/operator/mutations.go | 56 ++++ .../wal-replica/internal/operator/status.go | 74 +++++ .../internal/operator/validation.go | 72 +++++ .../wal-replica/internal/reconciler/impl.go | 54 ++++ .../internal/reconciler/replica.go | 165 +++++++++++ plugins/wal-replica/internal/utils/doc.go | 5 + plugins/wal-replica/internal/utils/utils.go | 19 ++ .../kubernetes/certificate-issuer.yaml | 6 + .../kubernetes/client-certificate.yaml | 19 ++ .../wal-replica/kubernetes/deployment.yaml | 43 +++ .../wal-replica/kubernetes/kustomization.yaml | 13 + .../kubernetes/server-certificate.yaml | 21 ++ plugins/wal-replica/kubernetes/service.yaml | 18 ++ plugins/wal-replica/main.go | 33 +++ plugins/wal-replica/pkg/metadata/doc.go | 22 ++ .../wal-replica/release-please-config.json | 12 + plugins/wal-replica/renovate.json5 | 86 ++++++ plugins/wal-replica/scripts/build.sh | 6 + plugins/wal-replica/scripts/run.sh | 39 +++ 40 files changed, 1852 insertions(+) create mode 100644 plugins/wal-replica/.gitignore create mode 100644 plugins/wal-replica/Dockerfile create mode 100644 plugins/wal-replica/LICENSE create mode 100644 plugins/wal-replica/README.md create mode 100644 plugins/wal-replica/cmd/plugin/doc.go create mode 100644 plugins/wal-replica/cmd/plugin/plugin.go create mode 100644 plugins/wal-replica/doc/development.md create mode 100644 plugins/wal-replica/doc/examples/cluster-example-no-parameters.yaml create mode 100644 plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml create mode 100644 plugins/wal-replica/doc/examples/cluster-example.yaml create mode 100644 plugins/wal-replica/example/cluster-with-wal-receiver.yaml create mode 100644 plugins/wal-replica/go.mod create mode 100644 plugins/wal-replica/go.sum create mode 100644 plugins/wal-replica/internal/config/config.go create mode 100644 plugins/wal-replica/internal/config/doc.go create mode 100644 plugins/wal-replica/internal/identity/doc.go create mode 100644 plugins/wal-replica/internal/identity/impl.go create mode 100644 plugins/wal-replica/internal/k8sclient/doc.go create mode 100644 plugins/wal-replica/internal/k8sclient/k8sclient.go create mode 100644 plugins/wal-replica/internal/operator/doc.go create mode 100644 plugins/wal-replica/internal/operator/impl.go create mode 100644 plugins/wal-replica/internal/operator/mutations.go create mode 100644 plugins/wal-replica/internal/operator/status.go create mode 100644 plugins/wal-replica/internal/operator/validation.go create mode 100644 plugins/wal-replica/internal/reconciler/impl.go create mode 100644 plugins/wal-replica/internal/reconciler/replica.go create mode 100644 plugins/wal-replica/internal/utils/doc.go create mode 100644 plugins/wal-replica/internal/utils/utils.go create mode 100644 plugins/wal-replica/kubernetes/certificate-issuer.yaml create mode 100644 plugins/wal-replica/kubernetes/client-certificate.yaml create mode 100644 plugins/wal-replica/kubernetes/deployment.yaml create mode 100644 plugins/wal-replica/kubernetes/kustomization.yaml create mode 100644 plugins/wal-replica/kubernetes/server-certificate.yaml create mode 100644 plugins/wal-replica/kubernetes/service.yaml create mode 100644 plugins/wal-replica/main.go create mode 100644 plugins/wal-replica/pkg/metadata/doc.go create mode 100644 plugins/wal-replica/release-please-config.json create mode 100644 plugins/wal-replica/renovate.json5 create mode 100755 plugins/wal-replica/scripts/build.sh create mode 100755 plugins/wal-replica/scripts/run.sh diff --git a/plugins/wal-replica/.gitignore b/plugins/wal-replica/.gitignore new file mode 100644 index 00000000..57436766 --- /dev/null +++ b/plugins/wal-replica/.gitignore @@ -0,0 +1,7 @@ +bin/ +dist/ +.env +.vscode/ +.idea/ +.task/ +manifest.yaml diff --git a/plugins/wal-replica/Dockerfile b/plugins/wal-replica/Dockerfile new file mode 100644 index 00000000..69a5fddf --- /dev/null +++ b/plugins/wal-replica/Dockerfile @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Step 1: build image +FROM golang:1.24 AS builder + +# Cache the dependencies +WORKDIR /app +COPY go.mod go.sum /app/ +RUN go mod download + +# Compile the application +COPY . /app +RUN --mount=type=cache,target=/root/.cache/go-build ./scripts/build.sh + +# Step 2: build the image to be actually run +FROM golang:1-alpine +USER 10001:10001 +COPY --from=builder /app/bin/cnpg-i-wal-replica /app/bin/cnpg-i-wal-replica +ENTRYPOINT ["/app/bin/cnpg-i-wal-replica"] diff --git a/plugins/wal-replica/LICENSE b/plugins/wal-replica/LICENSE new file mode 100644 index 00000000..a5ed2a9a --- /dev/null +++ b/plugins/wal-replica/LICENSE @@ -0,0 +1,23 @@ +DocumentDB Kubernetes Operator + +Copyright (c) Microsoft Corporation. + +MIT License + +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. \ No newline at end of file diff --git a/plugins/wal-replica/README.md b/plugins/wal-replica/README.md new file mode 100644 index 00000000..ee6d70a5 --- /dev/null +++ b/plugins/wal-replica/README.md @@ -0,0 +1,57 @@ +# WAL Receiver Pod Manager (CNPG-I Plugin) + +This plugin adds an optional standalone WAL receiver (pg_receivewal) Pod/Deployment +alongside a [CloudNativePG](https://github.com/cloudnative-pg/cloudnative-pg/) Cluster. +It reconciles a Deployment named +`-wal-receiver` that continuously streams WAL files from the primary +cluster using `pg_receivewal`, supporting synchronous mode. + +## Parameters + +Add the plugin in the Cluster spec (example): + +```yaml +spec: + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + enabled: "true" + image: "ghcr.io/cloudnative-pg/postgresql:16" + replicationUser: streaming_replica + replicationPasswordSecretName: cluster-replication + replicationPasswordSecretKey: password # optional (default: password) + synchronous: "true" # optional (default true) + walDirectory: /var/lib/wal # optional (default /var/lib/wal) + # replicationHost: override-host.example # optional +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| enabled | bool | false | Enable or disable the plugin | +| image | string | ghcr.io/cloudnative-pg/postgresql:16 | Image providing pg_receivewal | +| replicationHost | string | -rw | Host to connect for streaming | +| replicationUser | string | streaming_replica | Replication user | +| replicationPasswordSecretName | string | (required when enabled) | Secret containing replication password | +| replicationPasswordSecretKey | string | password | Key in the secret | +| synchronous | bool | true | Add --synchronous flag to pg_receivewal | +| walDirectory | string | /var/lib/wal | Local directory to store WAL | + +The Deployment exposes a metrics port (9187) and creates a Service with the same name. + +## Build + +```bash +go build -o bin/cnpg-i-wal-replica main.go +``` + +## Status + +The plugin status reflects only whether it is enabled. + +## Future Work + +* Add PVC / volume configuration for WAL directory +* Expose resource requests/limits and security context +* Garbage collection / retention policy for archived WAL +* Liveness/readiness refinements + diff --git a/plugins/wal-replica/cmd/plugin/doc.go b/plugins/wal-replica/cmd/plugin/doc.go new file mode 100644 index 00000000..b7790c20 --- /dev/null +++ b/plugins/wal-replica/cmd/plugin/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package plugin implements the command to start the plugin +package plugin diff --git a/plugins/wal-replica/cmd/plugin/plugin.go b/plugins/wal-replica/cmd/plugin/plugin.go new file mode 100644 index 00000000..2dc02548 --- /dev/null +++ b/plugins/wal-replica/cmd/plugin/plugin.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package plugin + +import ( + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/http" + "github.com/cloudnative-pg/cnpg-i/pkg/operator" + "github.com/cloudnative-pg/cnpg-i/pkg/reconciler" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + "github.com/documentdb/cnpg-i-wal-replica/internal/identity" + operatorImpl "github.com/documentdb/cnpg-i-wal-replica/internal/operator" + reconcilerImpl "github.com/documentdb/cnpg-i-wal-replica/internal/reconciler" +) + +// NewCmd creates the `plugin` command +func NewCmd() *cobra.Command { + cmd := http.CreateMainCmd(identity.Implementation{}, func(server *grpc.Server) error { + // Register the declared implementations + operator.RegisterOperatorServer(server, operatorImpl.Implementation{}) + reconciler.RegisterReconcilerHooksServer(server, reconcilerImpl.Implementation{}) + return nil + }) + + // If you want to provide your own logr.Logger here, inject it into a context.Context + // with logr.NewContext(ctx, logger) and pass it to cmd.SetContext(ctx) + logger := zap.New(zap.UseDevMode(true)) + log.SetLogger(logger) + + // Additional custom behaviour can be added by wrapping cmd.PersistentPreRun or cmd.Run + + cmd.Use = "plugin" + + return cmd +} diff --git a/plugins/wal-replica/doc/development.md b/plugins/wal-replica/doc/development.md new file mode 100644 index 00000000..b98e9cd1 --- /dev/null +++ b/plugins/wal-replica/doc/development.md @@ -0,0 +1,180 @@ +# Plugin Development + +This section of the documentation illustrates the CNPG-I capabilities used by +the wal-replica plugin, how the plugin implementation uses them, and how +developers can build and deploy the plugin. + +## Concepts + +### Identity + +The Identity interface defines the features supported by the plugin and is the +only interface that must always be implemented. + +This information is essential for the operator to discover the plugin's +capabilities during startup. + +The Identity interface provides: + +- A mechanism for plugins to report readiness probes. Readiness is a + prerequisite for receiving events, and plugins are expected to always report + the most accurate readiness data available. +- The capabilities reported by the plugin, which determine the subsequent calls + the plugin will receive. +- Metadata about the plugin. + +[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/identity.proto) + +### Capabilities + +This plugin implements the Operator and the Lifecycle capabilities. + +#### Operator + +This feature enables the plugin to receive events about the cluster creation and +mutations, this is defined by the following + +``` proto +// ValidateCreate improves the behavior of the validating webhook that +// is called on creation of the Cluster resources +rpc ValidateClusterCreate(OperatorValidateClusterCreateRequest) returns (OperatorValidateClusterCreateResult) {} + +// ValidateClusterChange improves the behavior of the validating webhook of +// is called on updates of the Cluster resources +rpc ValidateClusterChange(OperatorValidateClusterChangeRequest) returns (OperatorValidateClusterChangeResult) {} + +// MutateCluster fills in the defaults inside a Cluster resource +rpc MutateCluster(OperatorMutateClusterRequest) returns (OperatorMutateClusterResult) {} +``` + +This interface allows plugins to implement important features like: + +1. validating the cluster manifest during the creation and mutations + (it is expected that the plugin validate the parameters assigned to their + configuration). + +2. mutating the cluster object before it is submitted to kubernetes API server, + for example to set default values for the plugin parameters. + +[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator.proto) + +The wal-replica plugin is using this to validate used-defined parameters, and to +set default values for the labels and annotations applied by the plugin if not +specified by the user. + +#### Lifecycle + +This feature enables the plugin to receive events and create patches for +Kubernetes resources `before` they are submitted to the API server. + +To use this feature, the plugin must specify the resource and operation it wants +to be notified of. + +Some examples of what it can be achieved through the lifecycle: + +- add volume, volume mounts, sidecar containers, labels, annotations to pods, + especially necessary when implementing custom backup solutions +- modify any resource with some annotations or labels +- add/remove finalizers + +[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator_lifecycle.proto): + +The wal-replica plugin is using this to add labels, annotations and a sidecar +to the pods. + +## Implementation + +### Identity + +1. Define a struct inside the `internal/identity` package that implements + the `pluginhelper.IdentityServer` interface. + +2. Implement the following methods: + + - `GetPluginMetadata`: return human-readable information about the plugin. + - `GetPluginCapabilities`: specify the features supported by the plugin. In + the wal-replica example, the + `PluginCapability_Service_TYPE_LIFECYCLE_SERVICE` is defined in the + corresponding Go [file](../internal/lifecycle/lifecycle.go). + - `Probe`: indicate whether the plugin is ready to serve requests; this + example is stateless, so it will always be ready. + +### Lifecycle + +This example implements the lifecycle service capabilities to add labels and +annotations to the pods. The `OperatorLifecycleServer` interface is implemented +inside the `internal/lifecycle` package. + +The `OperatorLifecycleServer` interface requires several methods: + +- `GetCapabilities`: describe the resources and operations the plugin should be + notified for + +- `LifecycleHook`: is invoked for every operation against the Kubernetes API + server that matches the specifications returned by `GetCapabilities` + + In this function, the plugin is expected to do pattern matching using + the `Kind` and the operation `Type` and proceed with the proper logic. + +### Operator + +The operator interface offers a way for the plugin to interact with the Cluster +resource webhooks. + +Do that, the plugin should implement +the [operator](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator.proto) +interface, specifically the `MutateCluster`, `ValidateClusterCreate`, +and `ValidateClusterChange` rpc calls. + +- `MutateCluster`: enriches the plugin defaulting webhook + +- `ValidateClusterCreate` and `ValidateClusterChange`: enriches the plugin + validation logic. + +The package `internal/operator` implements this interface. + +### Startup Command + +The plugin runs in its own pod, and its main command is implemented in +the `main.go` file. + +This function uses the plugin helper library to create a GRPC server and manage +TLS. + +Plugin developers are expected to use the `pluginhelper.CreateMainCmd` +to implement the `main` function, passing an implemented `Identity` +struct. + +Further implementations can be registered within the callback function. + +In the example we propose, that's done for **operator** and for the +**lifecycle** services in [file](../cmd/plugin/plugin.go): + +``` proto +operator.RegisterOperatorServer(server, operatorImpl.Implementation{}) +lifecycle.RegisterOperatorLifecycleServer(server, lifecycleImpl.Implementation{}) +``` + +## Build and deploy the plugin + +Users can test their own changes to the plugin by building a container image +running it inside a Kubernetes cluster with CloudNativePG and cert-manager +installed. + +### Local build + +The repository provides a [`Taskfile`](https://taskfile.dev/) that contains +several helpful commands to test the plugin in +a [CNPG development environment](https://github.com/cloudnative-pg/cloudnative-pg/tree/main/contribute/e2e_testing_environment#the-local-kubernetes-cluster-for-testing). + +By executing `task local-kind-deploy`, a container image containing the +executable of the repository will be built and loaded inside the kind cluster. + +Having done that, the wal-replica plugin deployment will be applied. + +### CI/CD build + +The repository provides a GitHub Actions workflow that, on pushes, builds a +container image and generates a manifest file that can be used to deploy the +plugin. The manifest is attached to the workflow run as an artifact, and can be +applied to the cluster. diff --git a/plugins/wal-replica/doc/examples/cluster-example-no-parameters.yaml b/plugins/wal-replica/doc/examples/cluster-example-no-parameters.yaml new file mode 100644 index 00000000..a926fe66 --- /dev/null +++ b/plugins/wal-replica/doc/examples/cluster-example-no-parameters.yaml @@ -0,0 +1,12 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: cluster-example +spec: + instances: 3 + + plugins: + - name: cnpg-i-wal-replica.documentdb.io + + storage: + size: 1Gi diff --git a/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml b/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml new file mode 100644 index 00000000..0e2fe81e --- /dev/null +++ b/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml @@ -0,0 +1,23 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: cluster-example +spec: + instances: 3 + + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + labels: | + { + "first-label": "first-label-value", + "second-label": "second-label-value" + } + annotations: | + { + "first-annotation": "first-annotation-value", + this is a mistake + } + + storage: + size: 1Gi diff --git a/plugins/wal-replica/doc/examples/cluster-example.yaml b/plugins/wal-replica/doc/examples/cluster-example.yaml new file mode 100644 index 00000000..f592de4b --- /dev/null +++ b/plugins/wal-replica/doc/examples/cluster-example.yaml @@ -0,0 +1,16 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: cluster-example +spec: + instances: 1 + + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + replicationHost: cluster-example-rw + + storage: + size: 1Gi + + logLevel: "debug" diff --git a/plugins/wal-replica/example/cluster-with-wal-receiver.yaml b/plugins/wal-replica/example/cluster-with-wal-receiver.yaml new file mode 100644 index 00000000..2ea35698 --- /dev/null +++ b/plugins/wal-replica/example/cluster-with-wal-receiver.yaml @@ -0,0 +1,61 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: sample-wal-cluster + namespace: default +spec: + instances: 1 + imageName: ghcr.io/cloudnative-pg/postgresql:16 + storage: + size: 1Gi + superuserSecret: + name: sample-wal-superuser + bootstrap: + initdb: + database: appdb + owner: appuser + secret: + name: sample-wal-app-user + postgresql: + parameters: + wal_level: replica + archive_mode: "on" + archive_timeout: "60s" + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + enabled: "true" + replicationUser: streaming_replica + replicationPasswordSecretName: sample-wal-repl + synchronous: "true" + walDirectory: /var/lib/wal + # optional service exposure + primaryUpdateStrategy: unsupervised + enableSuperuserAccess: true +--- +apiVersion: v1 +kind: Secret +metadata: + name: sample-wal-superuser + namespace: default +stringData: + username: postgres + password: supersecret +--- +apiVersion: v1 +kind: Secret +metadata: + name: sample-wal-app-user + namespace: default +stringData: + username: appuser + password: appsecret +--- +apiVersion: v1 +kind: Secret +metadata: + name: sample-wal-repl + namespace: default +stringData: + username: streaming_replica + password: replsecret diff --git a/plugins/wal-replica/go.mod b/plugins/wal-replica/go.mod new file mode 100644 index 00000000..03058ec4 --- /dev/null +++ b/plugins/wal-replica/go.mod @@ -0,0 +1,94 @@ +module github.com/documentdb/cnpg-i-wal-replica + +go 1.23.5 + +toolchain go1.24.0 + +require ( + github.com/cloudnative-pg/api v1.25.1 + github.com/cloudnative-pg/cnpg-i v0.1.0 + github.com/cloudnative-pg/cnpg-i-machinery v0.2.0 + github.com/cloudnative-pg/machinery v0.1.0 + github.com/spf13/cobra v1.9.1 + google.golang.org/grpc v1.71.0 + k8s.io/api v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/utils v0.0.0-20241210054802-24370beab758 + sigs.k8s.io/controller-runtime v0.20.3 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudnative-pg/barman-cloud v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 // indirect + github.com/hashicorp/hcl v1.0.0 // 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/klauspost/compress v1.17.11 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mitchellh/mapstructure v1.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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 // indirect + github.com/prometheus/client_golang v1.21.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/snorwin/jsonpatch v1.5.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/net v0.36.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.9.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // 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/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.32.2 // indirect + k8s.io/client-go v0.32.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/plugins/wal-replica/go.sum b/plugins/wal-replica/go.sum new file mode 100644 index 00000000..903e773a --- /dev/null +++ b/plugins/wal-replica/go.sum @@ -0,0 +1,261 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudnative-pg/api v1.25.1 h1:uNjKiB0MIspUeH9l651SnFDcuflr1crB3t6LjxUCafQ= +github.com/cloudnative-pg/api v1.25.1/go.mod h1:fwF5g4XkuNZqYXIeRR3AJvUfWlqWig+r2DXc5bEmw6U= +github.com/cloudnative-pg/barman-cloud v0.1.0 h1:e/z52CehMBIh1LjZqNBJnncWJbS+1JYvRMBR8Js6Uiw= +github.com/cloudnative-pg/barman-cloud v0.1.0/go.mod h1:rJUJO/f1yNckLZiVxHAyRmKY+4EPJkYRJsGbTZRJQSY= +github.com/cloudnative-pg/cnpg-i v0.1.0 h1:QH2xTsrODMhEEc6B25GbOYe7ZIttDmSkYvXotfU5dfs= +github.com/cloudnative-pg/cnpg-i v0.1.0/go.mod h1:G28BhgUEHqrxEyyQeHz8BbpMVAsGuLhJm/tHUbDi8Sw= +github.com/cloudnative-pg/cnpg-i-machinery v0.2.0 h1:htNuKirdAOYrc7Hu5mLDoOES+nKSyPaXNDLgbV5dLSI= +github.com/cloudnative-pg/cnpg-i-machinery v0.2.0/go.mod h1:MHVxMMbLeCRnEM8PLWW4C2CsHqOeAU2OsrwWMKy3tPA= +github.com/cloudnative-pg/machinery v0.1.0 h1:tjRmsqQmsO/OlaT0uFmkEtVqgr+SGPM88cKZOHYKLBo= +github.com/cloudnative-pg/machinery v0.1.0/go.mod h1:0V3vm44FaIsY+x4pm8ORry7xCC3AJiO+ebfPNxeP5Ck= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +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.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +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-faker/faker/v4 v4.4.1 h1:LY1jDgjVkBZWIhATCt+gkl0x9i/7wC61gZx73GTFb+Q= +github.com/go-faker/faker/v4 v4.4.1/go.mod h1:HRLrjis+tYsbFtIHufEPTAIzcZiRu0rS9EYl2Ccwme4= +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-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +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.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +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 v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +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/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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/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/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +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/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +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/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/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1/go.mod h1:6x4x0t9BP35g4XcjkHE9EB3RxhyfxpdpmZKd/Qyk8+M= +github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= +github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +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.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/snorwin/jsonpatch v1.5.0 h1:0m56YSt9cHiJOn8U+OcqdPGcDQZmhPM/zsG7Dv5QQP0= +github.com/snorwin/jsonpatch v1.5.0/go.mod h1:e0IDKlyFBLTFPqM0wa79dnMwjMs3XFvmKcrgCRpDqok= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +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/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +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.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +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/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +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.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +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= +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/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= +k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= +k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +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-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= +k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= +sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= +sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +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/plugins/wal-replica/internal/config/config.go b/plugins/wal-replica/internal/config/config.go new file mode 100644 index 00000000..950e2b98 --- /dev/null +++ b/plugins/wal-replica/internal/config/config.go @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package config + +import ( + "fmt" + "strings" + + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/validation" + "github.com/cloudnative-pg/cnpg-i/pkg/operator" +) + +// Plugin parameter keys +const ( + ImageParam = "image" // string + ReplicationHostParam = "replicationHost" // Required: primary host + SynchronousParam = "synchronous" // enum: Active, Inactive, Unset + WalDirectoryParam = "walDirectory" // directory where WAL is stored +) + +// SynchronousMode represents the synchronous replication mode +type SynchronousMode string + +const ( + SynchronousUnset SynchronousMode = "" + SynchronousActive SynchronousMode = "active" + SynchronousInactive SynchronousMode = "inactive" +) + +const ( + defaultImage = "ghcr.io/cloudnative-pg/postgresql:16" + defaultWalDir = "/var/lib/postgres/wal" + defaultSynchronousMode = SynchronousInactive +) + +// Configuration represents the plugin configuration parameters controlling the wal receiver pod +type Configuration struct { + Image string + ReplicationHost string + Synchronous SynchronousMode + WalDirectory string +} + +// FromParameters builds a plugin configuration from the configuration parameters +func FromParameters(helper *common.Plugin) *Configuration { + cfg := &Configuration{} + cfg.Image = helper.Parameters[ImageParam] + cfg.ReplicationHost = helper.Parameters[ReplicationHostParam] + cfg.Synchronous = SynchronousMode(strings.ToLower(helper.Parameters[SynchronousParam])) + cfg.WalDirectory = helper.Parameters[WalDirectoryParam] + return cfg +} + +// ValidateChanges validates the changes between the old configuration to the new configuration +func ValidateChanges(_ *Configuration, _ *Configuration, _ *common.Plugin) []*operator.ValidationError { + return nil +} + +// ToParameters serialize the configuration back to plugin parameters +func (c *Configuration) ToParameters() (map[string]string, error) { + params := map[string]string{} + params[ImageParam] = c.Image + params[ReplicationHostParam] = c.ReplicationHost + params[SynchronousParam] = string(c.Synchronous) + params[WalDirectoryParam] = c.WalDirectory + return params, nil +} + +// ValidateParams ensures that the provided parameters are valid +func ValidateParams(helper *common.Plugin) []*operator.ValidationError { + validationErrors := make([]*operator.ValidationError, 0) + + // Must be present + if raw, present := helper.Parameters[ReplicationHostParam]; !present || raw == "" { + validationErrors = append(validationErrors, validation.BuildErrorForParameter(helper, ReplicationHostParam, "No replication host provided")) + } + + // If present, must be valid + if raw, present := helper.Parameters[SynchronousParam]; present && raw != "" { + switch SynchronousMode(strings.ToLower(raw)) { + case SynchronousActive, SynchronousInactive: + // valid value + default: + validationErrors = append(validationErrors, validation.BuildErrorForParameter(helper, SynchronousParam, + fmt.Sprintf("Invalid value '%s'. Must be 'active' or 'inactive'", raw))) + } + } + return validationErrors +} + +// applyDefaults fills the configuration with the defaults +// We know that replicationhost and sync are valid already +func (c *Configuration) ApplyDefaults() { + if c.Image == "" { + c.Image = defaultImage + } + if c.WalDirectory == "" { + c.WalDirectory = defaultWalDir + } + if c.Synchronous == SynchronousUnset { + c.Synchronous = defaultSynchronousMode + } +} diff --git a/plugins/wal-replica/internal/config/doc.go b/plugins/wal-replica/internal/config/doc.go new file mode 100644 index 00000000..fd2e2564 --- /dev/null +++ b/plugins/wal-replica/internal/config/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package config represents the plugin configuration +package config diff --git a/plugins/wal-replica/internal/identity/doc.go b/plugins/wal-replica/internal/identity/doc.go new file mode 100644 index 00000000..ec39c588 --- /dev/null +++ b/plugins/wal-replica/internal/identity/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package identity contains the implementation of the +// identity service +package identity diff --git a/plugins/wal-replica/internal/identity/impl.go b/plugins/wal-replica/internal/identity/impl.go new file mode 100644 index 00000000..05f63879 --- /dev/null +++ b/plugins/wal-replica/internal/identity/impl.go @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package identity + +import ( + "context" + + "github.com/cloudnative-pg/cnpg-i/pkg/identity" + + "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" +) + +// Implementation is the implementation of the identity service +type Implementation struct { + identity.IdentityServer +} + +// GetPluginMetadata implements the IdentityServer interface +func (Implementation) GetPluginMetadata( + context.Context, + *identity.GetPluginMetadataRequest, +) (*identity.GetPluginMetadataResponse, error) { + return &metadata.Data, nil +} + +// GetPluginCapabilities implements the IdentityServer interface +func (Implementation) GetPluginCapabilities( + context.Context, + *identity.GetPluginCapabilitiesRequest, +) (*identity.GetPluginCapabilitiesResponse, error) { + return &identity.GetPluginCapabilitiesResponse{ + Capabilities: []*identity.PluginCapability{ + { + Type: &identity.PluginCapability_Service_{ + Service: &identity.PluginCapability_Service{ + Type: identity.PluginCapability_Service_TYPE_RECONCILER_HOOKS, + }, + }, + }, + }, + }, nil +} + +// Probe implements the IdentityServer interface +func (Implementation) Probe(context.Context, *identity.ProbeRequest) (*identity.ProbeResponse, error) { + return &identity.ProbeResponse{ + Ready: true, + }, nil +} diff --git a/plugins/wal-replica/internal/k8sclient/doc.go b/plugins/wal-replica/internal/k8sclient/doc.go new file mode 100644 index 00000000..8cf08d78 --- /dev/null +++ b/plugins/wal-replica/internal/k8sclient/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package k8sclient ensure a Kubernetes client is available +package k8sclient diff --git a/plugins/wal-replica/internal/k8sclient/k8sclient.go b/plugins/wal-replica/internal/k8sclient/k8sclient.go new file mode 100644 index 00000000..a6975c55 --- /dev/null +++ b/plugins/wal-replica/internal/k8sclient/k8sclient.go @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package k8sclient + +import ( + "sync" + + apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +var ( + currentClient client.Client + mu sync.Mutex + scheme *runtime.Scheme +) + +// MustGet gets a Kubernetes client or panics is it cannot find it +func MustGet() client.Client { + cl, err := Get() + if err != nil { + panic(err) + } + + return cl +} + +// Get gets the Kubernetes client, creating it if needed. If an error during the +// creation of the Kubernetes client is raised, it will be returned +func Get() (client.Client, error) { + if currentClient != nil { + return currentClient, nil + } + + mu.Lock() + defer mu.Unlock() + + currentConfig, err := config.GetConfig() + if err != nil { + return nil, err + } + + newClient, err := client.New(currentConfig, client.Options{Scheme: scheme}) + if err != nil { + return nil, err + } + + currentClient = newClient + + return currentClient, nil +} + +func init() { + scheme = runtime.NewScheme() + _ = apiv1.AddToScheme(scheme) + _ = appsv1.AddToScheme(scheme) + _ = corev1.AddToScheme(scheme) +} diff --git a/plugins/wal-replica/internal/operator/doc.go b/plugins/wal-replica/internal/operator/doc.go new file mode 100644 index 00000000..15752638 --- /dev/null +++ b/plugins/wal-replica/internal/operator/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package operator contains the implementation of the +// operator service +package operator diff --git a/plugins/wal-replica/internal/operator/impl.go b/plugins/wal-replica/internal/operator/impl.go new file mode 100644 index 00000000..285fdef5 --- /dev/null +++ b/plugins/wal-replica/internal/operator/impl.go @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package operator + +import ( + "context" + + "github.com/cloudnative-pg/cnpg-i/pkg/operator" +) + +// Implementation is the implementation of the identity service +type Implementation struct { + operator.OperatorServer +} + +// GetCapabilities gets the capabilities of this operator lifecycle hook +func (Implementation) GetCapabilities( + context.Context, + *operator.OperatorCapabilitiesRequest, +) (*operator.OperatorCapabilitiesResult, error) { + return &operator.OperatorCapabilitiesResult{ + Capabilities: []*operator.OperatorCapability{ + { + Type: &operator.OperatorCapability_Rpc{ + Rpc: &operator.OperatorCapability_RPC{ + Type: operator.OperatorCapability_RPC_TYPE_VALIDATE_CLUSTER_CREATE, + }, + }, + }, + { + Type: &operator.OperatorCapability_Rpc{ + Rpc: &operator.OperatorCapability_RPC{ + Type: operator.OperatorCapability_RPC_TYPE_VALIDATE_CLUSTER_CHANGE, + }, + }, + }, + { + Type: &operator.OperatorCapability_Rpc{ + Rpc: &operator.OperatorCapability_RPC{ + Type: operator.OperatorCapability_RPC_TYPE_SET_STATUS_IN_CLUSTER, + }, + }, + }, + }, + }, nil +} + +func (Implementation) Deregister(context.Context, *operator.DeregisterRequest) (*operator.DeregisterResponse, error) { + return &operator.DeregisterResponse{}, nil +} diff --git a/plugins/wal-replica/internal/operator/mutations.go b/plugins/wal-replica/internal/operator/mutations.go new file mode 100644 index 00000000..4d37b287 --- /dev/null +++ b/plugins/wal-replica/internal/operator/mutations.go @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package operator + +import ( + "context" + + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object" + "github.com/cloudnative-pg/cnpg-i/pkg/operator" + + "github.com/documentdb/cnpg-i-wal-replica/internal/config" + "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" +) + +// MutateCluster is called to mutate a cluster with the defaulting webhook. +// This function is defaulting the "imagePullPolicy" plugin parameter +func (Implementation) MutateCluster( + _ context.Context, + request *operator.OperatorMutateClusterRequest, +) (*operator.OperatorMutateClusterResult, error) { + cluster, err := decoder.DecodeClusterLenient(request.GetDefinition()) + if err != nil { + return nil, err + } + + helper := common.NewPlugin( + *cluster, + metadata.PluginName, + ) + + config := config.FromParameters(helper) + mutatedCluster := cluster.DeepCopy() + if helper.PluginIndex < 0 { + if mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters == nil { + mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters = make(map[string]string) + } + config.ApplyDefaults() + + mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters, err = config.ToParameters() + if err != nil { + return nil, err + } + } + + patch, err := object.CreatePatch(cluster, mutatedCluster) + if err != nil { + return nil, err + } + + return &operator.OperatorMutateClusterResult{ + JsonPatch: patch, + }, nil +} diff --git a/plugins/wal-replica/internal/operator/status.go b/plugins/wal-replica/internal/operator/status.go new file mode 100644 index 00000000..6f21279e --- /dev/null +++ b/plugins/wal-replica/internal/operator/status.go @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package operator + +import ( + "context" + "encoding/json" + "errors" + + apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/clusterstatus" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" + "github.com/cloudnative-pg/cnpg-i/pkg/operator" + "github.com/cloudnative-pg/machinery/pkg/log" + + "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" +) + +type Status struct { + Enabled bool `json:"enabled"` +} + +func (Implementation) SetStatusInCluster( + ctx context.Context, + req *operator.SetStatusInClusterRequest, +) (*operator.SetStatusInClusterResponse, error) { + logger := log.FromContext(ctx).WithName("SetStatusInCluster") + + cluster, err := decoder.DecodeClusterLenient(req.GetCluster()) + if err != nil { + return nil, err + } + + // TODO remove + logger.Debug("Debug worked?") + + plg := common.NewPlugin(*cluster, metadata.PluginName) + + // Find the status for our plugin + var pluginEntry *apiv1.PluginStatus + for idx, entry := range plg.Cluster.Status.PluginStatus { + if metadata.PluginName == entry.Name { + pluginEntry = &plg.Cluster.Status.PluginStatus[idx] + break + } + } + + if pluginEntry == nil { + err := errors.New("plugin entry not found in the cluster status") + logger.Error(err, "while fetching the plugin status", "plugin", metadata.PluginName) + return nil, errors.New("plugin entry not found") + } + + var status Status + if pluginEntry.Status != "" { + if err := json.Unmarshal([]byte(pluginEntry.Status), &status); err != nil { + logger.Error(err, "while unmarshalling plugin status", + "entry", pluginEntry) + return nil, err + } + } + + if status.Enabled { + logger.Debug("plugin is enabled, no action taken") + return clusterstatus.NewSetStatusInClusterResponseBuilder().NoOpResponse(), nil + } + + // TODO uncomment this line when the `enabled` field stops alternating constantly + //logger.Info("setting enabled plugin status") + + return clusterstatus.NewSetStatusInClusterResponseBuilder().JSONStatusResponse(Status{Enabled: true}) +} diff --git a/plugins/wal-replica/internal/operator/validation.go b/plugins/wal-replica/internal/operator/validation.go new file mode 100644 index 00000000..65653c2f --- /dev/null +++ b/plugins/wal-replica/internal/operator/validation.go @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package operator + +import ( + "context" + + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" + "github.com/cloudnative-pg/cnpg-i/pkg/operator" + + "github.com/documentdb/cnpg-i-wal-replica/internal/config" + "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" +) + +// ValidateClusterCreate validates a cluster that is being created, +// Should validate all plugin parameters +func (Implementation) ValidateClusterCreate( + _ context.Context, + request *operator.OperatorValidateClusterCreateRequest, +) (*operator.OperatorValidateClusterCreateResult, error) { + cluster, err := decoder.DecodeClusterLenient(request.GetDefinition()) + if err != nil { + return nil, err + } + + result := &operator.OperatorValidateClusterCreateResult{} + + helper := common.NewPlugin( + *cluster, + metadata.PluginName, + ) + + result.ValidationErrors = config.ValidateParams(helper) + + return result, nil +} + +// ValidateClusterChange validates a cluster that is being changed +func (Implementation) ValidateClusterChange( + _ context.Context, + request *operator.OperatorValidateClusterChangeRequest, +) (*operator.OperatorValidateClusterChangeResult, error) { + result := &operator.OperatorValidateClusterChangeResult{} + + oldCluster, err := decoder.DecodeClusterLenient(request.GetOldCluster()) + if err != nil { + return nil, err + } + + newCluster, err := decoder.DecodeClusterLenient(request.GetNewCluster()) + if err != nil { + return nil, err + } + + oldClusterHelper := common.NewPlugin( + *oldCluster, + metadata.PluginName, + ) + + newClusterHelper := common.NewPlugin( + *newCluster, + metadata.PluginName, + ) + + newConfiguration := config.FromParameters(newClusterHelper) + oldConfiguration := config.FromParameters(oldClusterHelper) + result.ValidationErrors = config.ValidateChanges(oldConfiguration, newConfiguration, newClusterHelper) + + return result, nil +} diff --git a/plugins/wal-replica/internal/reconciler/impl.go b/plugins/wal-replica/internal/reconciler/impl.go new file mode 100644 index 00000000..a35eec08 --- /dev/null +++ b/plugins/wal-replica/internal/reconciler/impl.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package reconciler + +import ( + "context" + "encoding/json" + + apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + "github.com/cloudnative-pg/cnpg-i/pkg/reconciler" + "github.com/cloudnative-pg/machinery/pkg/log" +) + +// Implementation is the implementation of the identity service +type Implementation struct { + reconciler.UnimplementedReconcilerHooksServer +} + +// GetCapabilities gets the capabilities of this operator lifecycle hook +func (Implementation) GetCapabilities( + context.Context, + *reconciler.ReconcilerHooksCapabilitiesRequest, +) (*reconciler.ReconcilerHooksCapabilitiesResult, error) { + return &reconciler.ReconcilerHooksCapabilitiesResult{ + ReconcilerCapabilities: []*reconciler.ReconcilerHooksCapability{ + { + Kind: reconciler.ReconcilerHooksCapability_KIND_CLUSTER, + }, + }, + }, nil +} + +func (Implementation) Post(ctx context.Context, req *reconciler.ReconcilerHooksRequest) (*reconciler.ReconcilerHooksResult, error) { + logger := log.FromContext(ctx).WithName("PostReconcilerHook") + cluster := &apiv1.Cluster{} + if err := json.Unmarshal(req.GetResourceDefinition(), cluster); err != nil { + logger.Error(err, "while decoding the cluster") + return nil, err + } + logger.Info("Post called for ", "cluster", cluster) + + if err := CreateWalReplica(ctx, cluster); err != nil { + logger.Error(err, "while creating the wal replica") + return nil, err + } + + return &reconciler.ReconcilerHooksResult{Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE}, nil +} + +func (Implementation) Pre(ctx context.Context, req *reconciler.ReconcilerHooksRequest) (*reconciler.ReconcilerHooksResult, error) { + // NOOP + return &reconciler.ReconcilerHooksResult{}, nil +} diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go new file mode 100644 index 00000000..bc0e895a --- /dev/null +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -0,0 +1,165 @@ +package reconciler + +import ( + "context" + "fmt" + + apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" + "github.com/cloudnative-pg/machinery/pkg/log" + "github.com/documentdb/cnpg-i-wal-replica/internal/config" + "github.com/documentdb/cnpg-i-wal-replica/internal/k8sclient" + "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) + +func CreateWalReplica( + ctx context.Context, + cluster *apiv1.Cluster, +) error { + logger := log.FromContext(ctx).WithName("CreateWalReplica") + + if !IsPrimaryCluster(cluster) { + logger.Info("Cluster is not a primary, skipping wal replica creation", "cluster", cluster.Name) + return nil + } + + // Build Deployment name unique per cluster + deploymentName := fmt.Sprintf("%s-wal-receiver", cluster.Name) + namespace := cluster.Namespace + client := k8sclient.MustGet() + + helper := common.NewPlugin( + *cluster, + metadata.PluginName, + ) + + configuration := config.FromParameters(helper) + + walDir := configuration.WalDirectory + cmd := []string{ + "/usr/bin/postgresql/16/bin/pg_receivewal", // TODO find the actual path + "--slot", "wal_replica", + "--compress", "0", + "--directory", walDir, + "--host", configuration.ReplicationHost, + "--port", "5432", + "--username", "postgres", + "--no-password", + "--verbose", + } + + // Add synchronous flag if requested + if configuration.Synchronous == config.SynchronousActive { + cmd = append(cmd, "--synchronous") + } + + // Create a pVC + // Needs a PVC to store the wal data + existingPVC := &corev1.PersistentVolumeClaim{} + err := client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existingPVC) + if err != nil && errors.IsNotFound(err) { + log.Info("WAL replica PVC not found. Creating a new WAL replica PVC") + + walReplicaPVC := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploymentName, + Namespace: namespace, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteOnce, + }, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: resource.MustParse("10Gi"), + }, + }, + }, + } + + err = client.Create(ctx, walReplicaPVC) + if err != nil { + return err + } + } else if err != nil { + return err + } + + // Create or patch Deployment + existing := &appsv1.Deployment{} + err = client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existing) + if err != nil { + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploymentName, + Namespace: namespace, + Labels: map[string]string{ + "app": deploymentName, + "cnpg.io/cluster": cluster.Name, + }, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": deploymentName}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": deploymentName}}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "wal-receiver", + Image: configuration.Image, + Args: cmd, + VolumeMounts: []corev1.VolumeMount{ + { + Name: deploymentName, + MountPath: walDir, + }, + }, + }}, + Volumes: []corev1.Volume{ + { + Name: deploymentName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: deploymentName, + }, + }, + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: int64Ptr(105), + RunAsGroup: int64Ptr(103), + FSGroup: int64Ptr(103), + }, + RestartPolicy: corev1.RestartPolicyAlways, + }, + }, + }, + } + // optional service for metrics + svc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: deploymentName, Namespace: namespace, Labels: map[string]string{"app": deploymentName}}, Spec: corev1.ServiceSpec{Selector: map[string]string{"app": deploymentName}, Ports: []corev1.ServicePort{{Name: "metrics", Port: 9187, TargetPort: intstr.FromInt(9187)}}}} + if createErr := client.Create(ctx, dep); createErr != nil { + logger.Error(createErr, "creating wal receiver deployment") + return createErr + } + _ = client.Create(ctx, svc) // ignore error if exists + logger.Info("created wal receiver deployment", "name", deploymentName) + } else { + // TODO handle patch + } + + return nil +} +func int64Ptr(i int64) *int64 { + return &i +} + +func IsPrimaryCluster(cluster *apiv1.Cluster) bool { + // TODO implement + return true +} diff --git a/plugins/wal-replica/internal/utils/doc.go b/plugins/wal-replica/internal/utils/doc.go new file mode 100644 index 00000000..a9bff958 --- /dev/null +++ b/plugins/wal-replica/internal/utils/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package utils contains methods to interact with kubernetes resources +package utils diff --git a/plugins/wal-replica/internal/utils/utils.go b/plugins/wal-replica/internal/utils/utils.go new file mode 100644 index 00000000..1867f3a2 --- /dev/null +++ b/plugins/wal-replica/internal/utils/utils.go @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package utils + +import "encoding/json" + +// GetKind gets the Kubernetes object kind from its JSON representation +func GetKind(definition []byte) (string, error) { + var genericObject struct { + Kind string `json:"kind"` + } + + if err := json.Unmarshal(definition, &genericObject); err != nil { + return "", err + } + + return genericObject.Kind, nil +} diff --git a/plugins/wal-replica/kubernetes/certificate-issuer.yaml b/plugins/wal-replica/kubernetes/certificate-issuer.yaml new file mode 100644 index 00000000..8d3d6eee --- /dev/null +++ b/plugins/wal-replica/kubernetes/certificate-issuer.yaml @@ -0,0 +1,6 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} diff --git a/plugins/wal-replica/kubernetes/client-certificate.yaml b/plugins/wal-replica/kubernetes/client-certificate.yaml new file mode 100644 index 00000000..58c79b9a --- /dev/null +++ b/plugins/wal-replica/kubernetes/client-certificate.yaml @@ -0,0 +1,19 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: walreplica-client +spec: + secretName: walreplica-client-tls + + commonName: "walreplica-client" + duration: 2160h # 90d + renewBefore: 360h # 15d + + isCA: false + usages: + - client auth + + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io diff --git a/plugins/wal-replica/kubernetes/deployment.yaml b/plugins/wal-replica/kubernetes/deployment.yaml new file mode 100644 index 00000000..a2314adc --- /dev/null +++ b/plugins/wal-replica/kubernetes/deployment.yaml @@ -0,0 +1,43 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: wal-replica + name: wal-replica +spec: + replicas: 1 + selector: + matchLabels: + app: wal-replica + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: wal-replica + spec: + containers: + - image: cnpg-i-wal-replica:latest + name: cnpg-i-wal-replica + ports: + - containerPort: 9090 + protocol: TCP + args: + - plugin + - --server-cert=/server/tls.crt + - --server-key=/server/tls.key + - --client-cert=/client/tls.crt + - --server-address=:9090 + volumeMounts: + - mountPath: /server + name: server + - mountPath: /client + name: client + resources: {} + volumes: + - name: server + secret: + secretName: walreplica-server-tls + - name: client + secret: + secretName: walreplica-client-tls diff --git a/plugins/wal-replica/kubernetes/kustomization.yaml b/plugins/wal-replica/kubernetes/kustomization.yaml new file mode 100644 index 00000000..af21bb1a --- /dev/null +++ b/plugins/wal-replica/kubernetes/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: cnpg-system +resources: +- certificate-issuer.yaml +- client-certificate.yaml +- deployment.yaml +- server-certificate.yaml +- service.yaml +images: +- name: cnpg-i-wal-replica + newName: ghcr.io/microsoft/documentdb-kubernetes-operator/wal-replica + newTag: latest diff --git a/plugins/wal-replica/kubernetes/server-certificate.yaml b/plugins/wal-replica/kubernetes/server-certificate.yaml new file mode 100644 index 00000000..1f573eab --- /dev/null +++ b/plugins/wal-replica/kubernetes/server-certificate.yaml @@ -0,0 +1,21 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: walreplica-server +spec: + secretName: walreplica-server-tls + commonName: "wal-replica" + dnsNames: + - wal-replica + + duration: 2160h # 90d + renewBefore: 360h # 15d + + isCA: false + usages: + - server auth + + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io diff --git a/plugins/wal-replica/kubernetes/service.yaml b/plugins/wal-replica/kubernetes/service.yaml new file mode 100644 index 00000000..b2217cb1 --- /dev/null +++ b/plugins/wal-replica/kubernetes/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: wal-replica + cnpg.io/pluginName: cnpg-i-wal-replica.documentdb.io + annotations: + cnpg.io/pluginClientSecret: walreplica-client-tls + cnpg.io/pluginServerSecret: walreplica-server-tls + cnpg.io/pluginPort: "9090" + name: wal-replica +spec: + ports: + - port: 9090 + protocol: TCP + targetPort: 9090 + selector: + app: wal-replica diff --git a/plugins/wal-replica/main.go b/plugins/wal-replica/main.go new file mode 100644 index 00000000..0c7f251e --- /dev/null +++ b/plugins/wal-replica/main.go @@ -0,0 +1,33 @@ +// Package main is the entrypoint of the application +package main + +import ( + "fmt" + "os" + + "github.com/cloudnative-pg/machinery/pkg/log" + "github.com/spf13/cobra" + + "github.com/documentdb/cnpg-i-wal-replica/cmd/plugin" +) + +func main() { + logFlags := &log.Flags{} + rootCmd := &cobra.Command{ + Use: "cnpg-i-wal-replica", + Short: "WAL Replica", + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + logFlags.ConfigureLogging() + return nil + }, + } + + logFlags.AddFlags(rootCmd.PersistentFlags()) + + rootCmd.AddCommand(plugin.NewCmd()) + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/plugins/wal-replica/pkg/metadata/doc.go b/plugins/wal-replica/pkg/metadata/doc.go new file mode 100644 index 00000000..cca11b21 --- /dev/null +++ b/plugins/wal-replica/pkg/metadata/doc.go @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package metadata contains the metadata of this plugin +package metadata + +import "github.com/cloudnative-pg/cnpg-i/pkg/identity" + +// PluginName is the name of the plugin +const PluginName = "cnpg-i-wal-replica.documentdb.io" + +// Data is the metadata of this plugin +var Data = identity.GetPluginMetadataResponse{ + Name: PluginName, + Version: "0.1.0", + DisplayName: "WAL Replica Pod Manager", + ProjectUrl: "https://github.com/documentdb/cnpg-i-wal-replica", + RepositoryUrl: "https://github.com/documentdb/cnpg-i-wal-replica", + License: "Proprietary", + LicenseUrl: "https://github.com/documentdb/cnpg-i-wal-replica/LICENSE", + Maturity: "alpha", +} diff --git a/plugins/wal-replica/release-please-config.json b/plugins/wal-replica/release-please-config.json new file mode 100644 index 00000000..b3f9a1dc --- /dev/null +++ b/plugins/wal-replica/release-please-config.json @@ -0,0 +1,12 @@ +{ + "changelog-path": "CHANGELOG.md", + "release-type": "go", + "bump-minor-pre-major": false, + "bump-patch-for-minor-pre-major": false, + "draft": false, + "prerelease": false, + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/plugins/wal-replica/renovate.json5 b/plugins/wal-replica/renovate.json5 new file mode 100644 index 00000000..fa60c475 --- /dev/null +++ b/plugins/wal-replica/renovate.json5 @@ -0,0 +1,86 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":gitSignOff", + ":semanticCommitType(chore)", + ":labels(automated,no-issue)", + "customManagers:githubActionsVersions", + ":automergeMinor", + ":automergeDigest" + ], + "prConcurrentLimit": 5, + "ignorePaths": [ + "doc/**", + "CHANGELOG.md" + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "semanticCommits": "enabled", + "commitBodyTable": true, + // Allow renovate to update the following types of dependencies in the Taskfile.yml: + // - digests for env variables ending in _SHA + // - versions for env variables ending in _VERSION + "customManagers": [ + { + "customType": "regex", + "fileMatch": [ + "(^Taskfile\\.yml$)" + ], + "matchStrings": [ + "# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?(?: currentValue=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_SHA\\s*:\\s*[\"']?(?[a-f0-9]+?)[\"']?\\s", + "# renovate: datasource=(?[a-z-.]+?) depName=(?[^\\s]+?)(?: (?:lookupName|packageName)=(?[^\\s]+?))?(?: versioning=(?[^\\s]+?))?(?: extractVersion=(?[^\\s]+?))?\\s+[A-Za-z0-9_]+?_VERSION\\s*:\\s*[\"']?(?.+?)[\"']?\\s" + ] + } + ], + "packageRules": [ + { + "matchDatasources": [ + "go" + ], + "matchPackageNames": [ + // Avoid k8s dependencies from being grouped with other dependencies. We want to be careful + // with how we update them. + "!/k8s.io/" + ], + "matchUpdateTypes": [ + "minor", + "patch", + "digest" + ], + "groupName": "all non-major go dependencies" + }, + { + "matchDatasources": [ + "git-refs" + ], + "matchPackageNames": [ + "https://github.com/cloudnative-pg/daggerverse" + ], + "matchUpdateTypes": [ + "digest" + ], + "groupName": "all cloudnative-pg daggerverse dependencies" + }, + { + "matchDatasources": [ + "git-refs" + ], + "matchPackageNames": [ + "https://github.com/sagikazarmark/daggerverse" + ], + "matchUpdateTypes": [ + "digest" + ], + "groupName": "all sagikazarmark daggerverse dependencies" + }, + { + "matchUpdateTypes": [ + "minor", + "patch" + ], + "matchCurrentVersion": "!/^0/" + } + ] +} diff --git a/plugins/wal-replica/scripts/build.sh b/plugins/wal-replica/scripts/build.sh new file mode 100755 index 00000000..df30b287 --- /dev/null +++ b/plugins/wal-replica/scripts/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +cd "$(dirname "$0")/.." || exit + +# Compile the plugin +CGO_ENABLED=0 go build -o bin/cnpg-i-wal-replica main.go diff --git a/plugins/wal-replica/scripts/run.sh b/plugins/wal-replica/scripts/run.sh new file mode 100755 index 00000000..e9c7da2b --- /dev/null +++ b/plugins/wal-replica/scripts/run.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -eu + +cd "$(dirname "$0")/.." || exit + +if [ -f .env ]; then + source .env +fi + +# The following script builds the plugin image and uploads it to the +# current kind cluster +# WARNING: This will fail with recent releases of kind due to https://github.com/kubernetes-sigs/kind/issues/3853 +# See fix in CNPG https://github.com/cloudnative-pg/cloudnative-pg/pull/6770 +# current_context=$(kubectl config view --raw -o json | jq -r '."current-context"' | sed "s/kind-//") +# kind load docker-image --name=${current_context} cnpg-i-wal-replica:${VERSION:-latest} + +# Constants +registry_name=registry.dev + +load_image_registry() { + local image=$1 + + local image_reg_name=${registry_name}:5000/${image} + local image_local_name=${image_reg_name/${registry_name}/127.0.0.1} + docker tag "${image}" "${image_reg_name}" + docker tag "${image}" "${image_local_name}" + docker push -q "${image_local_name}" +} + +# Now we deploy the plugin inside the `cnpg-system` workspace +kubectl apply -k kubernetes/ + +# We load the image into the registry (which is a prerequisite) +load_image_registry cnpg-i-wal-replica:${VERSION:-latest} + +# Patch the deployment to use the provided image +kubectl patch deployments.apps -n cnpg-system wal-replica -p \ + "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"cnpg-i-wal-replica\",\"image\":\"${registry_name}:5000/cnpg-i-wal-replica:${VERSION:-latest}\"}]}}}}" From ee447cba2d47d1b317b5b45ef94bde545fc08c2d Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Mon, 8 Sep 2025 11:45:52 -0400 Subject: [PATCH 02/10] use new service account --- plugins/wal-replica/internal/config/config.go | 10 +++++++ .../internal/reconciler/replica.go | 9 ++++++- .../wal-replica/kubernetes/deployment.yaml | 1 + .../wal-replica/kubernetes/kustomization.yaml | 3 +++ .../wal-replica/kubernetes/role-binding.yaml | 15 +++++++++++ plugins/wal-replica/kubernetes/role.yaml | 26 +++++++++++++++++++ .../kubernetes/service-account.yaml | 8 ++++++ 7 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 plugins/wal-replica/kubernetes/role-binding.yaml create mode 100644 plugins/wal-replica/kubernetes/role.yaml create mode 100644 plugins/wal-replica/kubernetes/service-account.yaml diff --git a/plugins/wal-replica/internal/config/config.go b/plugins/wal-replica/internal/config/config.go index 950e2b98..72df6f91 100644 --- a/plugins/wal-replica/internal/config/config.go +++ b/plugins/wal-replica/internal/config/config.go @@ -10,6 +10,7 @@ import ( "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/validation" "github.com/cloudnative-pg/cnpg-i/pkg/operator" + "k8s.io/apimachinery/pkg/api/resource" ) // Plugin parameter keys @@ -18,6 +19,7 @@ const ( ReplicationHostParam = "replicationHost" // Required: primary host SynchronousParam = "synchronous" // enum: Active, Inactive, Unset WalDirectoryParam = "walDirectory" // directory where WAL is stored + WalPVCSize = "walPVCSize" // Size of the PVC for WAL storage ) // SynchronousMode represents the synchronous replication mode @@ -87,6 +89,14 @@ func ValidateParams(helper *common.Plugin) []*operator.ValidationError { fmt.Sprintf("Invalid value '%s'. Must be 'active' or 'inactive'", raw))) } } + + // If present, Wal size must be valid + if raw, present := helper.Parameters[WalPVCSize]; present && raw != "" { + if _, err := resource.ParseQuantity(raw); err != nil { + validationErrors = append(validationErrors, validation.BuildErrorForParameter(helper, WalPVCSize, err.Error())) + } + } + return validationErrors } diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index bc0e895a..6e289064 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + package reconciler import ( @@ -52,7 +55,11 @@ func CreateWalReplica( "--port", "5432", "--username", "postgres", "--no-password", - "--verbose", + } + + // TODO have a real check here + if true { + cmd = append(cmd, "--verbose") } // Add synchronous flag if requested diff --git a/plugins/wal-replica/kubernetes/deployment.yaml b/plugins/wal-replica/kubernetes/deployment.yaml index a2314adc..1ae02479 100644 --- a/plugins/wal-replica/kubernetes/deployment.yaml +++ b/plugins/wal-replica/kubernetes/deployment.yaml @@ -16,6 +16,7 @@ spec: labels: app: wal-replica spec: + serviceAccountName: wal-replica-manager containers: - image: cnpg-i-wal-replica:latest name: cnpg-i-wal-replica diff --git a/plugins/wal-replica/kubernetes/kustomization.yaml b/plugins/wal-replica/kubernetes/kustomization.yaml index af21bb1a..b1f0287a 100644 --- a/plugins/wal-replica/kubernetes/kustomization.yaml +++ b/plugins/wal-replica/kubernetes/kustomization.yaml @@ -5,7 +5,10 @@ resources: - certificate-issuer.yaml - client-certificate.yaml - deployment.yaml +- role-binding.yaml +- role.yaml - server-certificate.yaml +- service-account.yaml - service.yaml images: - name: cnpg-i-wal-replica diff --git a/plugins/wal-replica/kubernetes/role-binding.yaml b/plugins/wal-replica/kubernetes/role-binding.yaml new file mode 100644 index 00000000..0542860d --- /dev/null +++ b/plugins/wal-replica/kubernetes/role-binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: wal-replia-manager + app.kubernetes.io/managed-by: kustomize + name: wal-replica-manager-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: wal-replica-manager +subjects: +- kind: ServiceAccount + name: wal-replica-manager + namespace: cnpg-system diff --git a/plugins/wal-replica/kubernetes/role.yaml b/plugins/wal-replica/kubernetes/role.yaml new file mode 100644 index 00000000..aa3d37a0 --- /dev/null +++ b/plugins/wal-replica/kubernetes/role.yaml @@ -0,0 +1,26 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: wal-replica-manager +rules: # TODO trim this down to what's actually needed +- apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: [""] + resources: ["services", "pods", "endpoints", "leases", "serviceaccounts", "configmaps", "namespaces"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["metrics.k8s.io"] + resources: ["pods"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: [""] + resources: ["secrets", "persistentvolumeclaims"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["postgresql.cnpg.io"] + resources: ["clusters", "publications", "subscriptions"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] \ No newline at end of file diff --git a/plugins/wal-replica/kubernetes/service-account.yaml b/plugins/wal-replica/kubernetes/service-account.yaml new file mode 100644 index 00000000..cd9c6956 --- /dev/null +++ b/plugins/wal-replica/kubernetes/service-account.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: wal-replica-manager + namespace: cnpg-system + labels: + app.kubernetes.io/name: wal-replia-manager + app.kubernetes.io/managed-by: kustomize \ No newline at end of file From 28cae363d392a6c4fa51e5498d3fb717ae46a117 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Mon, 8 Sep 2025 17:40:04 -0400 Subject: [PATCH 03/10] fix reconciler and disable operator --- plugins/wal-replica/README.md | 12 +- plugins/wal-replica/cmd/plugin/plugin.go | 6 +- .../cluster-example-with-mistake.yaml | 13 +- .../example/cluster-with-wal-receiver.yaml | 61 ------- plugins/wal-replica/go.mod | 129 ++++++++------- plugins/wal-replica/go.sum | 149 ++++++++++++++++++ plugins/wal-replica/internal/config/config.go | 6 + plugins/wal-replica/internal/identity/impl.go | 7 + plugins/wal-replica/internal/operator/impl.go | 13 +- .../internal/operator/mutations.go | 11 +- .../wal-replica/internal/operator/status.go | 19 ++- .../internal/operator/validation.go | 9 +- .../internal/reconciler/replica.go | 5 +- plugins/wal-replica/internal/utils/doc.go | 5 - plugins/wal-replica/internal/utils/utils.go | 19 --- .../wal-replica/kubernetes/deployment.yaml | 2 +- 16 files changed, 284 insertions(+), 182 deletions(-) delete mode 100644 plugins/wal-replica/example/cluster-with-wal-receiver.yaml delete mode 100644 plugins/wal-replica/internal/utils/doc.go delete mode 100644 plugins/wal-replica/internal/utils/utils.go diff --git a/plugins/wal-replica/README.md b/plugins/wal-replica/README.md index ee6d70a5..d9b11a57 100644 --- a/plugins/wal-replica/README.md +++ b/plugins/wal-replica/README.md @@ -15,24 +15,16 @@ spec: plugins: - name: cnpg-i-wal-replica.documentdb.io parameters: - enabled: "true" image: "ghcr.io/cloudnative-pg/postgresql:16" - replicationUser: streaming_replica - replicationPasswordSecretName: cluster-replication - replicationPasswordSecretKey: password # optional (default: password) - synchronous: "true" # optional (default true) + replicationHost: cluster-name-rw + synchronous: "enabled" # optional (default true) walDirectory: /var/lib/wal # optional (default /var/lib/wal) - # replicationHost: override-host.example # optional ``` | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| enabled | bool | false | Enable or disable the plugin | | image | string | ghcr.io/cloudnative-pg/postgresql:16 | Image providing pg_receivewal | | replicationHost | string | -rw | Host to connect for streaming | -| replicationUser | string | streaming_replica | Replication user | -| replicationPasswordSecretName | string | (required when enabled) | Secret containing replication password | -| replicationPasswordSecretKey | string | password | Key in the secret | | synchronous | bool | true | Add --synchronous flag to pg_receivewal | | walDirectory | string | /var/lib/wal | Local directory to store WAL | diff --git a/plugins/wal-replica/cmd/plugin/plugin.go b/plugins/wal-replica/cmd/plugin/plugin.go index 2dc02548..b0dc4f97 100644 --- a/plugins/wal-replica/cmd/plugin/plugin.go +++ b/plugins/wal-replica/cmd/plugin/plugin.go @@ -26,14 +26,10 @@ func NewCmd() *cobra.Command { return nil }) - // If you want to provide your own logr.Logger here, inject it into a context.Context - // with logr.NewContext(ctx, logger) and pass it to cmd.SetContext(ctx) logger := zap.New(zap.UseDevMode(true)) log.SetLogger(logger) - // Additional custom behaviour can be added by wrapping cmd.PersistentPreRun or cmd.Run - - cmd.Use = "plugin" + cmd.Use = "receivewal" return cmd } diff --git a/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml b/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml index 0e2fe81e..c46aa637 100644 --- a/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml +++ b/plugins/wal-replica/doc/examples/cluster-example-with-mistake.yaml @@ -8,16 +8,7 @@ spec: plugins: - name: cnpg-i-wal-replica.documentdb.io parameters: - labels: | - { - "first-label": "first-label-value", - "second-label": "second-label-value" - } - annotations: | - { - "first-annotation": "first-annotation-value", - this is a mistake - } - + replication_host: "cluster-example-0.cluster-example-svc.cnpg-system.svc.cluster.local" + synchronous: "badvalue" storage: size: 1Gi diff --git a/plugins/wal-replica/example/cluster-with-wal-receiver.yaml b/plugins/wal-replica/example/cluster-with-wal-receiver.yaml deleted file mode 100644 index 2ea35698..00000000 --- a/plugins/wal-replica/example/cluster-with-wal-receiver.yaml +++ /dev/null @@ -1,61 +0,0 @@ -apiVersion: postgresql.cnpg.io/v1 -kind: Cluster -metadata: - name: sample-wal-cluster - namespace: default -spec: - instances: 1 - imageName: ghcr.io/cloudnative-pg/postgresql:16 - storage: - size: 1Gi - superuserSecret: - name: sample-wal-superuser - bootstrap: - initdb: - database: appdb - owner: appuser - secret: - name: sample-wal-app-user - postgresql: - parameters: - wal_level: replica - archive_mode: "on" - archive_timeout: "60s" - plugins: - - name: cnpg-i-wal-replica.documentdb.io - parameters: - enabled: "true" - replicationUser: streaming_replica - replicationPasswordSecretName: sample-wal-repl - synchronous: "true" - walDirectory: /var/lib/wal - # optional service exposure - primaryUpdateStrategy: unsupervised - enableSuperuserAccess: true ---- -apiVersion: v1 -kind: Secret -metadata: - name: sample-wal-superuser - namespace: default -stringData: - username: postgres - password: supersecret ---- -apiVersion: v1 -kind: Secret -metadata: - name: sample-wal-app-user - namespace: default -stringData: - username: appuser - password: appsecret ---- -apiVersion: v1 -kind: Secret -metadata: - name: sample-wal-repl - namespace: default -stringData: - username: streaming_replica - password: replsecret diff --git a/plugins/wal-replica/go.mod b/plugins/wal-replica/go.mod index 03058ec4..59d3d087 100644 --- a/plugins/wal-replica/go.mod +++ b/plugins/wal-replica/go.mod @@ -1,94 +1,109 @@ module github.com/documentdb/cnpg-i-wal-replica -go 1.23.5 - -toolchain go1.24.0 +go 1.24.1 require ( - github.com/cloudnative-pg/api v1.25.1 - github.com/cloudnative-pg/cnpg-i v0.1.0 - github.com/cloudnative-pg/cnpg-i-machinery v0.2.0 - github.com/cloudnative-pg/machinery v0.1.0 - github.com/spf13/cobra v1.9.1 - google.golang.org/grpc v1.71.0 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/utils v0.0.0-20241210054802-24370beab758 - sigs.k8s.io/controller-runtime v0.20.3 + github.com/cloudnative-pg/api v1.27.0 + github.com/cloudnative-pg/cnpg-i v0.3.0 + github.com/cloudnative-pg/cnpg-i-machinery v0.4.0 + github.com/cloudnative-pg/machinery v0.3.1 + github.com/spf13/cobra v1.10.1 + google.golang.org/grpc v1.75.0 + k8s.io/api v0.34.0 + k8s.io/apimachinery v0.34.0 + sigs.k8s.io/controller-runtime v0.22.1 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudnative-pg/barman-cloud v0.1.0 // indirect + github.com/cloudnative-pg/barman-cloud v0.3.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.22.0 // indirect + github.com/go-openapi/jsonreference v0.21.1 // indirect + github.com/go-openapi/swag v0.24.1 // indirect + github.com/go-openapi/swag/cmdutils v0.24.0 // indirect + github.com/go-openapi/swag/conv v0.24.0 // indirect + github.com/go-openapi/swag/fileutils v0.24.0 // indirect + github.com/go-openapi/swag/jsonname v0.24.0 // indirect + github.com/go-openapi/swag/jsonutils v0.24.0 // indirect + github.com/go-openapi/swag/loading v0.24.0 // indirect + github.com/go-openapi/swag/mangling v0.24.0 // indirect + github.com/go-openapi/swag/netutils v0.24.0 // indirect + github.com/go-openapi/swag/stringutils v0.24.0 // indirect + github.com/go-openapi/swag/typeutils v0.24.0 // indirect + github.com/go-openapi/swag/yamlutils v0.24.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/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gnostic-models v0.7.0 // 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/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect github.com/hashicorp/hcl v1.0.0 // 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/klauspost/compress v1.17.11 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mitchellh/mapstructure v1.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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 // indirect - github.com/prometheus/client_golang v1.21.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/snorwin/jsonpatch v1.5.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/spf13/viper v1.19.0 // 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/spf13/viper v1.21.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/net v0.36.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.9.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.13.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.2 // indirect - k8s.io/client-go v0.32.2 // indirect + k8s.io/apiextensions-apiserver v0.34.0 // indirect + k8s.io/client-go v0.34.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + k8s.io/kube-openapi v0.0.0-20250905212525-66792eed8611 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/plugins/wal-replica/go.sum b/plugins/wal-replica/go.sum index 903e773a..72330df6 100644 --- a/plugins/wal-replica/go.sum +++ b/plugins/wal-replica/go.sum @@ -4,14 +4,24 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudnative-pg/api v1.25.1 h1:uNjKiB0MIspUeH9l651SnFDcuflr1crB3t6LjxUCafQ= github.com/cloudnative-pg/api v1.25.1/go.mod h1:fwF5g4XkuNZqYXIeRR3AJvUfWlqWig+r2DXc5bEmw6U= +github.com/cloudnative-pg/api v1.27.0 h1:uSUkF9X/0UZu1Xn5qI33qHVmzZrDKuuyoiRlsOmSTv4= +github.com/cloudnative-pg/api v1.27.0/go.mod h1:IWyAmuirffHiw6iIGD1p18BmZNb13TK9Os/wkp8ltDg= github.com/cloudnative-pg/barman-cloud v0.1.0 h1:e/z52CehMBIh1LjZqNBJnncWJbS+1JYvRMBR8Js6Uiw= github.com/cloudnative-pg/barman-cloud v0.1.0/go.mod h1:rJUJO/f1yNckLZiVxHAyRmKY+4EPJkYRJsGbTZRJQSY= +github.com/cloudnative-pg/barman-cloud v0.3.3 h1:EEcjeV+IUivDpmyF/H/XGY1pGaKJ5LS5MYeB6wgGcak= +github.com/cloudnative-pg/barman-cloud v0.3.3/go.mod h1:5CM4MncAxAjnqxjDt0I5E/oVd7gsMLL0/o/wQ+vUSgs= github.com/cloudnative-pg/cnpg-i v0.1.0 h1:QH2xTsrODMhEEc6B25GbOYe7ZIttDmSkYvXotfU5dfs= github.com/cloudnative-pg/cnpg-i v0.1.0/go.mod h1:G28BhgUEHqrxEyyQeHz8BbpMVAsGuLhJm/tHUbDi8Sw= +github.com/cloudnative-pg/cnpg-i v0.3.0 h1:5ayNOG5x68lU70IVbHDZQrv5p+bErCJ0mqRmOpW2jjE= +github.com/cloudnative-pg/cnpg-i v0.3.0/go.mod h1:VOIWWXcJ1RyioK+elR2DGOa4cBA6K+6UQgx05aZmH+g= github.com/cloudnative-pg/cnpg-i-machinery v0.2.0 h1:htNuKirdAOYrc7Hu5mLDoOES+nKSyPaXNDLgbV5dLSI= github.com/cloudnative-pg/cnpg-i-machinery v0.2.0/go.mod h1:MHVxMMbLeCRnEM8PLWW4C2CsHqOeAU2OsrwWMKy3tPA= +github.com/cloudnative-pg/cnpg-i-machinery v0.4.0 h1:16wQt9qFFqvyxeg+9dPt8ic8dh3PRPq0jCGXVuZyjO4= +github.com/cloudnative-pg/cnpg-i-machinery v0.4.0/go.mod h1:4MCJzbCOsB7ianxlm8rqD+gDpkgVTHoTuglle/i72WA= github.com/cloudnative-pg/machinery v0.1.0 h1:tjRmsqQmsO/OlaT0uFmkEtVqgr+SGPM88cKZOHYKLBo= github.com/cloudnative-pg/machinery v0.1.0/go.mod h1:0V3vm44FaIsY+x4pm8ORry7xCC3AJiO+ebfPNxeP5Ck= +github.com/cloudnative-pg/machinery v0.3.1 h1:KtPA6EwELTUNisCMLiFYkK83GU9606rkGQhDJGPB8Yw= +github.com/cloudnative-pg/machinery v0.3.1/go.mod h1:jebuqKxZAbrRKDEEpVCIDMKW+FbWtB9Kf/hb2kMUu9o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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= @@ -19,6 +29,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 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.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -27,25 +39,61 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +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/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-faker/faker/v4 v4.4.1 h1:LY1jDgjVkBZWIhATCt+gkl0x9i/7wC61gZx73GTFb+Q= github.com/go-faker/faker/v4 v4.4.1/go.mod h1:HRLrjis+tYsbFtIHufEPTAIzcZiRu0rS9EYl2Ccwme4= 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-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 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/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= 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-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= +github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= +github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= +github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= +github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= +github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= +github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= +github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= +github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= +github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= +github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= +github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= +github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= +github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= +github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= +github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= +github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= +github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= +github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= +github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= +github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= +github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= +github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= +github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 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= @@ -54,9 +102,13 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/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= @@ -66,6 +118,8 @@ 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/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -78,6 +132,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -86,6 +142,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -95,6 +153,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w 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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= @@ -103,42 +163,70 @@ github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1 h1:DP+PUNVOc+Bkft8a4QunLzaZ0RspWuD3tBbcPHr2PeE= github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.80.1/go.mod h1:6x4x0t9BP35g4XcjkHE9EB3RxhyfxpdpmZKd/Qyk8+M= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0 h1:oY+F5FZFmCjCyzkHWPjVQpzvnvEB/0FP+iyzDUUlqFc= +github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.85.0/go.mod h1:VB7wtBmDT6W2RJHzsvPZlBId+EnmeQA0d33fFTXvraM= github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= 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.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +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/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/snorwin/jsonpatch v1.5.0 h1:0m56YSt9cHiJOn8U+OcqdPGcDQZmhPM/zsG7Dv5QQP0= github.com/snorwin/jsonpatch v1.5.0/go.mod h1:e0IDKlyFBLTFPqM0wa79dnMwjMs3XFvmKcrgCRpDqok= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +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.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +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.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +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.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +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.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +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= @@ -150,6 +238,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 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= @@ -160,25 +249,36 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +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/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= 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= @@ -187,26 +287,40 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 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/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 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/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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= @@ -219,17 +333,27 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -239,23 +363,48 @@ 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.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= +k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= k8s.io/apiextensions-apiserver v0.32.2 h1:2YMk285jWMk2188V2AERy5yDwBYrjgWYggscghPCvV4= k8s.io/apiextensions-apiserver v0.32.2/go.mod h1:GPwf8sph7YlJT3H6aKUWtd0E+oyShk/YHWQHf/OOgCA= +k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= +k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= +k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= +k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= +k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= 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-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= +k8s.io/kube-openapi v0.0.0-20250905212525-66792eed8611 h1:o4oKOsvSymDkZRsMAPZU7bRdwL+lPOK5VS10Dr1D6eg= +k8s.io/kube-openapi v0.0.0-20250905212525-66792eed8611/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +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.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/plugins/wal-replica/internal/config/config.go b/plugins/wal-replica/internal/config/config.go index 72df6f91..897f7670 100644 --- a/plugins/wal-replica/internal/config/config.go +++ b/plugins/wal-replica/internal/config/config.go @@ -43,6 +43,7 @@ type Configuration struct { ReplicationHost string Synchronous SynchronousMode WalDirectory string + WalPVCSize string } // FromParameters builds a plugin configuration from the configuration parameters @@ -52,6 +53,7 @@ func FromParameters(helper *common.Plugin) *Configuration { cfg.ReplicationHost = helper.Parameters[ReplicationHostParam] cfg.Synchronous = SynchronousMode(strings.ToLower(helper.Parameters[SynchronousParam])) cfg.WalDirectory = helper.Parameters[WalDirectoryParam] + cfg.WalPVCSize = helper.Parameters[WalPVCSize] return cfg } @@ -67,6 +69,7 @@ func (c *Configuration) ToParameters() (map[string]string, error) { params[ReplicationHostParam] = c.ReplicationHost params[SynchronousParam] = string(c.Synchronous) params[WalDirectoryParam] = c.WalDirectory + params[WalPVCSize] = c.WalPVCSize return params, nil } @@ -112,4 +115,7 @@ func (c *Configuration) ApplyDefaults() { if c.Synchronous == SynchronousUnset { c.Synchronous = defaultSynchronousMode } + if c.WalPVCSize == "" { + c.WalPVCSize = "10Gi" + } } diff --git a/plugins/wal-replica/internal/identity/impl.go b/plugins/wal-replica/internal/identity/impl.go index 05f63879..d6bf1cb3 100644 --- a/plugins/wal-replica/internal/identity/impl.go +++ b/plugins/wal-replica/internal/identity/impl.go @@ -31,6 +31,13 @@ func (Implementation) GetPluginCapabilities( ) (*identity.GetPluginCapabilitiesResponse, error) { return &identity.GetPluginCapabilitiesResponse{ Capabilities: []*identity.PluginCapability{ + { + Type: &identity.PluginCapability_Service_{ + Service: &identity.PluginCapability_Service{ + Type: identity.PluginCapability_Service_TYPE_OPERATOR_SERVICE, + }, + }, + }, { Type: &identity.PluginCapability_Service_{ Service: &identity.PluginCapability_Service{ diff --git a/plugins/wal-replica/internal/operator/impl.go b/plugins/wal-replica/internal/operator/impl.go index 285fdef5..efd6d119 100644 --- a/plugins/wal-replica/internal/operator/impl.go +++ b/plugins/wal-replica/internal/operator/impl.go @@ -35,6 +35,7 @@ func (Implementation) GetCapabilities( }, }, }, + /* TODO re-add if we need status or can figure out the oscillation bug { Type: &operator.OperatorCapability_Rpc{ Rpc: &operator.OperatorCapability_RPC{ @@ -42,10 +43,14 @@ func (Implementation) GetCapabilities( }, }, }, + */ + { + Type: &operator.OperatorCapability_Rpc{ + Rpc: &operator.OperatorCapability_RPC{ + Type: operator.OperatorCapability_RPC_TYPE_MUTATE_CLUSTER, + }, + }, + }, }, }, nil } - -func (Implementation) Deregister(context.Context, *operator.DeregisterRequest) (*operator.DeregisterResponse, error) { - return &operator.DeregisterResponse{}, nil -} diff --git a/plugins/wal-replica/internal/operator/mutations.go b/plugins/wal-replica/internal/operator/mutations.go index 4d37b287..56cd0143 100644 --- a/plugins/wal-replica/internal/operator/mutations.go +++ b/plugins/wal-replica/internal/operator/mutations.go @@ -10,17 +10,19 @@ import ( "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object" "github.com/cloudnative-pg/cnpg-i/pkg/operator" + "github.com/cloudnative-pg/machinery/pkg/log" "github.com/documentdb/cnpg-i-wal-replica/internal/config" "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" ) // MutateCluster is called to mutate a cluster with the defaulting webhook. -// This function is defaulting the "imagePullPolicy" plugin parameter func (Implementation) MutateCluster( - _ context.Context, + ctx context.Context, request *operator.OperatorMutateClusterRequest, ) (*operator.OperatorMutateClusterResult, error) { + logger := log.FromContext(ctx).WithName("MutateCluster") + logger.Warning("MutateCluster hook invoked") cluster, err := decoder.DecodeClusterLenient(request.GetDefinition()) if err != nil { return nil, err @@ -33,7 +35,7 @@ func (Implementation) MutateCluster( config := config.FromParameters(helper) mutatedCluster := cluster.DeepCopy() - if helper.PluginIndex < 0 { + if helper.PluginIndex >= 0 { if mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters == nil { mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters = make(map[string]string) } @@ -43,8 +45,11 @@ func (Implementation) MutateCluster( if err != nil { return nil, err } + } else { + logger.Info("Plugin not found in the cluster, skipping mutation", "plugin", metadata.PluginName) } + logger.Info("Mutated cluster", "cluster", mutatedCluster) patch, err := object.CreatePatch(cluster, mutatedCluster) if err != nil { return nil, err diff --git a/plugins/wal-replica/internal/operator/status.go b/plugins/wal-replica/internal/operator/status.go index 6f21279e..649e5279 100644 --- a/plugins/wal-replica/internal/operator/status.go +++ b/plugins/wal-replica/internal/operator/status.go @@ -53,6 +53,11 @@ func (Implementation) SetStatusInCluster( return nil, errors.New("plugin entry not found") } + if plg.PluginIndex < 0 { + logger.Info("Plugin not being used, setting disabled status") + return clusterstatus.NewSetStatusInClusterResponseBuilder().JSONStatusResponse(Status{Enabled: false}) + } + var status Status if pluginEntry.Status != "" { if err := json.Unmarshal([]byte(pluginEntry.Status), &status); err != nil { @@ -62,13 +67,21 @@ func (Implementation) SetStatusInCluster( } } + logger.Info("debug status snapshot", + "resourceVersion", cluster.ResourceVersion, + "pluginIndex", plg.PluginIndex, + "rawPluginStatus", pluginEntry.Status, + "decodedEnabled", status.Enabled, + ) + if status.Enabled { - logger.Debug("plugin is enabled, no action taken") - return clusterstatus.NewSetStatusInClusterResponseBuilder().NoOpResponse(), nil + logger.Info("plugin is enabled, no action taken") + //return clusterstatus.NewSetStatusInClusterResponseBuilder().NoOpResponse(), nil + return clusterstatus.NewSetStatusInClusterResponseBuilder().JSONStatusResponse(Status{Enabled: true}) } // TODO uncomment this line when the `enabled` field stops alternating constantly - //logger.Info("setting enabled plugin status") + logger.Info("setting enabled plugin status") return clusterstatus.NewSetStatusInClusterResponseBuilder().JSONStatusResponse(Status{Enabled: true}) } diff --git a/plugins/wal-replica/internal/operator/validation.go b/plugins/wal-replica/internal/operator/validation.go index 65653c2f..42a0ae02 100644 --- a/plugins/wal-replica/internal/operator/validation.go +++ b/plugins/wal-replica/internal/operator/validation.go @@ -9,6 +9,7 @@ import ( "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" "github.com/cloudnative-pg/cnpg-i/pkg/operator" + "github.com/cloudnative-pg/machinery/pkg/log" "github.com/documentdb/cnpg-i-wal-replica/internal/config" "github.com/documentdb/cnpg-i-wal-replica/pkg/metadata" @@ -17,9 +18,11 @@ import ( // ValidateClusterCreate validates a cluster that is being created, // Should validate all plugin parameters func (Implementation) ValidateClusterCreate( - _ context.Context, + ctx context.Context, request *operator.OperatorValidateClusterCreateRequest, ) (*operator.OperatorValidateClusterCreateResult, error) { + logger := log.FromContext(ctx).WithName("ValidateClusterCreate") + logger.Info("ValidateClusterCreate called") cluster, err := decoder.DecodeClusterLenient(request.GetDefinition()) if err != nil { return nil, err @@ -39,9 +42,11 @@ func (Implementation) ValidateClusterCreate( // ValidateClusterChange validates a cluster that is being changed func (Implementation) ValidateClusterChange( - _ context.Context, + ctx context.Context, request *operator.OperatorValidateClusterChangeRequest, ) (*operator.OperatorValidateClusterChangeResult, error) { + logger := log.FromContext(ctx).WithName("ValidateClusterChange") + logger.Info("ValidateClusterChange called") result := &operator.OperatorValidateClusterChangeResult{} oldCluster, err := decoder.DecodeClusterLenient(request.GetOldCluster()) diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index 6e289064..c7d7e50e 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -45,9 +45,12 @@ func CreateWalReplica( configuration := config.FromParameters(helper) + // TODO remove this once the operator functions are fixed + configuration.ApplyDefaults() + walDir := configuration.WalDirectory cmd := []string{ - "/usr/bin/postgresql/16/bin/pg_receivewal", // TODO find the actual path + "pg_receivewal", // TODO what do we do if it's not on the path? "--slot", "wal_replica", "--compress", "0", "--directory", walDir, diff --git a/plugins/wal-replica/internal/utils/doc.go b/plugins/wal-replica/internal/utils/doc.go deleted file mode 100644 index a9bff958..00000000 --- a/plugins/wal-replica/internal/utils/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Package utils contains methods to interact with kubernetes resources -package utils diff --git a/plugins/wal-replica/internal/utils/utils.go b/plugins/wal-replica/internal/utils/utils.go deleted file mode 100644 index 1867f3a2..00000000 --- a/plugins/wal-replica/internal/utils/utils.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -package utils - -import "encoding/json" - -// GetKind gets the Kubernetes object kind from its JSON representation -func GetKind(definition []byte) (string, error) { - var genericObject struct { - Kind string `json:"kind"` - } - - if err := json.Unmarshal(definition, &genericObject); err != nil { - return "", err - } - - return genericObject.Kind, nil -} diff --git a/plugins/wal-replica/kubernetes/deployment.yaml b/plugins/wal-replica/kubernetes/deployment.yaml index 1ae02479..99469564 100644 --- a/plugins/wal-replica/kubernetes/deployment.yaml +++ b/plugins/wal-replica/kubernetes/deployment.yaml @@ -24,7 +24,7 @@ spec: - containerPort: 9090 protocol: TCP args: - - plugin + - receivewal - --server-cert=/server/tls.crt - --server-key=/server/tls.key - --client-cert=/client/tls.crt From bae9bbf6e997dd3260bf9ba36d7fc66ecc75fdbc Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Tue, 9 Sep 2025 14:04:10 -0400 Subject: [PATCH 04/10] use proper user and different default values --- plugins/wal-replica/internal/config/config.go | 17 ++-- plugins/wal-replica/internal/identity/impl.go | 2 +- .../internal/operator/mutations.go | 2 +- .../wal-replica/internal/operator/status.go | 4 +- .../wal-replica/internal/reconciler/doc.go | 6 ++ .../internal/reconciler/replica.go | 79 ++++++++++++++++--- 6 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 plugins/wal-replica/internal/reconciler/doc.go diff --git a/plugins/wal-replica/internal/config/config.go b/plugins/wal-replica/internal/config/config.go index 897f7670..b4ea8f76 100644 --- a/plugins/wal-replica/internal/config/config.go +++ b/plugins/wal-replica/internal/config/config.go @@ -7,6 +7,7 @@ import ( "fmt" "strings" + cnpgv1 "github.com/cloudnative-pg/api/pkg/api/v1" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/validation" "github.com/cloudnative-pg/cnpg-i/pkg/operator" @@ -32,8 +33,7 @@ const ( ) const ( - defaultImage = "ghcr.io/cloudnative-pg/postgresql:16" - defaultWalDir = "/var/lib/postgres/wal" + defaultWalDir = "/var/lib/postgresql/wal" defaultSynchronousMode = SynchronousInactive ) @@ -77,11 +77,6 @@ func (c *Configuration) ToParameters() (map[string]string, error) { func ValidateParams(helper *common.Plugin) []*operator.ValidationError { validationErrors := make([]*operator.ValidationError, 0) - // Must be present - if raw, present := helper.Parameters[ReplicationHostParam]; !present || raw == "" { - validationErrors = append(validationErrors, validation.BuildErrorForParameter(helper, ReplicationHostParam, "No replication host provided")) - } - // If present, must be valid if raw, present := helper.Parameters[SynchronousParam]; present && raw != "" { switch SynchronousMode(strings.ToLower(raw)) { @@ -105,9 +100,13 @@ func ValidateParams(helper *common.Plugin) []*operator.ValidationError { // applyDefaults fills the configuration with the defaults // We know that replicationhost and sync are valid already -func (c *Configuration) ApplyDefaults() { +func (c *Configuration) ApplyDefaults(cluster *cnpgv1.Cluster) { if c.Image == "" { - c.Image = defaultImage + c.Image = cluster.Status.Image + } + if c.ReplicationHost == "" { + // Only doing reads, but want to make sure we get a primary + c.ReplicationHost = cluster.Status.WriteService } if c.WalDirectory == "" { c.WalDirectory = defaultWalDir diff --git a/plugins/wal-replica/internal/identity/impl.go b/plugins/wal-replica/internal/identity/impl.go index d6bf1cb3..c0bb0288 100644 --- a/plugins/wal-replica/internal/identity/impl.go +++ b/plugins/wal-replica/internal/identity/impl.go @@ -31,7 +31,7 @@ func (Implementation) GetPluginCapabilities( ) (*identity.GetPluginCapabilitiesResponse, error) { return &identity.GetPluginCapabilitiesResponse{ Capabilities: []*identity.PluginCapability{ - { + { // TODO find out why this does nothing Type: &identity.PluginCapability_Service_{ Service: &identity.PluginCapability_Service{ Type: identity.PluginCapability_Service_TYPE_OPERATOR_SERVICE, diff --git a/plugins/wal-replica/internal/operator/mutations.go b/plugins/wal-replica/internal/operator/mutations.go index 56cd0143..68417ecf 100644 --- a/plugins/wal-replica/internal/operator/mutations.go +++ b/plugins/wal-replica/internal/operator/mutations.go @@ -39,7 +39,7 @@ func (Implementation) MutateCluster( if mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters == nil { mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters = make(map[string]string) } - config.ApplyDefaults() + config.ApplyDefaults(cluster) mutatedCluster.Spec.Plugins[helper.PluginIndex].Parameters, err = config.ToParameters() if err != nil { diff --git a/plugins/wal-replica/internal/operator/status.go b/plugins/wal-replica/internal/operator/status.go index 649e5279..1629de99 100644 --- a/plugins/wal-replica/internal/operator/status.go +++ b/plugins/wal-replica/internal/operator/status.go @@ -8,7 +8,7 @@ import ( "encoding/json" "errors" - apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + cnpgv1 "github.com/cloudnative-pg/api/pkg/api/v1" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/clusterstatus" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/decoder" @@ -39,7 +39,7 @@ func (Implementation) SetStatusInCluster( plg := common.NewPlugin(*cluster, metadata.PluginName) // Find the status for our plugin - var pluginEntry *apiv1.PluginStatus + var pluginEntry *cnpgv1.PluginStatus for idx, entry := range plg.Cluster.Status.PluginStatus { if metadata.PluginName == entry.Name { pluginEntry = &plg.Cluster.Status.PluginStatus[idx] diff --git a/plugins/wal-replica/internal/reconciler/doc.go b/plugins/wal-replica/internal/reconciler/doc.go new file mode 100644 index 00000000..43652219 --- /dev/null +++ b/plugins/wal-replica/internal/reconciler/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Package identity contains the implementation of the +// reconciler service +package reconciler diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index c7d7e50e..1b17a0ec 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -7,7 +7,7 @@ import ( "context" "fmt" - apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + cnpgv1 "github.com/cloudnative-pg/api/pkg/api/v1" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" "github.com/cloudnative-pg/machinery/pkg/log" "github.com/documentdb/cnpg-i-wal-replica/internal/config" @@ -19,12 +19,11 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/intstr" ) func CreateWalReplica( ctx context.Context, - cluster *apiv1.Cluster, + cluster *cnpgv1.Cluster, ) error { logger := log.FromContext(ctx).WithName("CreateWalReplica") @@ -46,7 +45,7 @@ func CreateWalReplica( configuration := config.FromParameters(helper) // TODO remove this once the operator functions are fixed - configuration.ApplyDefaults() + configuration.ApplyDefaults(cluster) walDir := configuration.WalDirectory cmd := []string{ @@ -54,10 +53,7 @@ func CreateWalReplica( "--slot", "wal_replica", "--compress", "0", "--directory", walDir, - "--host", configuration.ReplicationHost, - "--port", "5432", - "--username", "postgres", - "--no-password", + "--dbname", GetConnectionString(configuration.ReplicationHost), } // TODO have a real check here @@ -75,12 +71,24 @@ func CreateWalReplica( existingPVC := &corev1.PersistentVolumeClaim{} err := client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existingPVC) if err != nil && errors.IsNotFound(err) { - log.Info("WAL replica PVC not found. Creating a new WAL replica PVC") + logger.Info("WAL replica PVC not found. Creating a new WAL replica PVC") walReplicaPVC := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, Namespace: namespace, + Labels: map[string]string{ + "app": deploymentName, + "cnpg.io/cluster": cluster.Name, + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: cluster.APIVersion, + Kind: cluster.Kind, + Name: cluster.Name, + UID: cluster.UID, + }, + }, }, Spec: corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ @@ -114,6 +122,14 @@ func CreateWalReplica( "app": deploymentName, "cnpg.io/cluster": cluster.Name, }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: cluster.APIVersion, + Kind: cluster.Kind, + Name: cluster.Name, + UID: cluster.UID, + }, + }, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": deploymentName}}, @@ -129,6 +145,16 @@ func CreateWalReplica( Name: deploymentName, MountPath: walDir, }, + { + Name: "ca", + MountPath: "/var/lib/postgresql/rootcert", + ReadOnly: true, + }, + { + Name: "tls", + MountPath: "/var/lib/postgresql/cert", + ReadOnly: true, + }, }, }}, Volumes: []corev1.Volume{ @@ -140,6 +166,24 @@ func CreateWalReplica( }, }, }, + { + Name: "ca", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: cluster.Status.Certificates.ServerCASecret, + DefaultMode: int32Ptr(0600), + }, + }, + }, + { + Name: "tls", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: cluster.Status.Certificates.ReplicationTLSSecret, + DefaultMode: int32Ptr(0600), + }, + }, + }, }, SecurityContext: &corev1.PodSecurityContext{ RunAsUser: int64Ptr(105), @@ -151,13 +195,10 @@ func CreateWalReplica( }, }, } - // optional service for metrics - svc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: deploymentName, Namespace: namespace, Labels: map[string]string{"app": deploymentName}}, Spec: corev1.ServiceSpec{Selector: map[string]string{"app": deploymentName}, Ports: []corev1.ServicePort{{Name: "metrics", Port: 9187, TargetPort: intstr.FromInt(9187)}}}} if createErr := client.Create(ctx, dep); createErr != nil { logger.Error(createErr, "creating wal receiver deployment") return createErr } - _ = client.Create(ctx, svc) // ignore error if exists logger.Info("created wal receiver deployment", "name", deploymentName) } else { // TODO handle patch @@ -165,11 +206,23 @@ func CreateWalReplica( return nil } + +func GetConnectionString(host string) string { + return fmt.Sprintf("postgres://%s@%s/postgres?sslmode=verify-full&sslrootcert=%s&sslcert=%s&sslkey=%s", + "streaming_replica", // user + host, + "/var/lib/postgresql/rootcert/ca.crt", // root cert + "/var/lib/postgresql/cert/tls.crt", // cert + "/var/lib/postgresql/cert/tls.key") // key +} func int64Ptr(i int64) *int64 { return &i } +func int32Ptr(i int32) *int32 { + return &i +} -func IsPrimaryCluster(cluster *apiv1.Cluster) bool { +func IsPrimaryCluster(cluster *cnpgv1.Cluster) bool { // TODO implement return true } From d32bd56ddf9536db5bfab6e12b08012ccc0bb359 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Tue, 9 Sep 2025 20:51:44 -0400 Subject: [PATCH 05/10] Add examples and start making custom image --- .../doc/examples/cluster-example.yaml | 11 +++++++++-- .../wal-replica/internal/reconciler/impl.go | 4 ++-- .../wal-replica/internal/reconciler/replica.go | 3 ++- .../wal-replica/pg-receivewal-min/Dockerfile | 18 ++++++++++++++++++ plugins/wal-replica/scripts/build.sh | 2 +- 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 plugins/wal-replica/pg-receivewal-min/Dockerfile diff --git a/plugins/wal-replica/doc/examples/cluster-example.yaml b/plugins/wal-replica/doc/examples/cluster-example.yaml index f592de4b..258eeaef 100644 --- a/plugins/wal-replica/doc/examples/cluster-example.yaml +++ b/plugins/wal-replica/doc/examples/cluster-example.yaml @@ -7,8 +7,15 @@ spec: plugins: - name: cnpg-i-wal-replica.documentdb.io - parameters: - replicationHost: cluster-example-rw + + replicationSlots: + synchronizeReplicas: + enabled: true + + bootstrap: + initdb: + postInitSQL: + "select * from pg_create_physical_replication_slot('wal_replica'); storage: size: 1Gi diff --git a/plugins/wal-replica/internal/reconciler/impl.go b/plugins/wal-replica/internal/reconciler/impl.go index a35eec08..e94cd99a 100644 --- a/plugins/wal-replica/internal/reconciler/impl.go +++ b/plugins/wal-replica/internal/reconciler/impl.go @@ -7,7 +7,7 @@ import ( "context" "encoding/json" - apiv1 "github.com/cloudnative-pg/api/pkg/api/v1" + cnpgv1 "github.com/cloudnative-pg/api/pkg/api/v1" "github.com/cloudnative-pg/cnpg-i/pkg/reconciler" "github.com/cloudnative-pg/machinery/pkg/log" ) @@ -33,7 +33,7 @@ func (Implementation) GetCapabilities( func (Implementation) Post(ctx context.Context, req *reconciler.ReconcilerHooksRequest) (*reconciler.ReconcilerHooksResult, error) { logger := log.FromContext(ctx).WithName("PostReconcilerHook") - cluster := &apiv1.Cluster{} + cluster := &cnpgv1.Cluster{} if err := json.Unmarshal(req.GetResourceDefinition(), cluster); err != nil { logger.Error(err, "while decoding the cluster") return nil, err diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index 1b17a0ec..7890f890 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -66,7 +66,6 @@ func CreateWalReplica( cmd = append(cmd, "--synchronous") } - // Create a pVC // Needs a PVC to store the wal data existingPVC := &corev1.PersistentVolumeClaim{} err := client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existingPVC) @@ -110,6 +109,8 @@ func CreateWalReplica( return err } + // Create replica slot + // Create or patch Deployment existing := &appsv1.Deployment{} err = client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existing) diff --git a/plugins/wal-replica/pg-receivewal-min/Dockerfile b/plugins/wal-replica/pg-receivewal-min/Dockerfile new file mode 100644 index 00000000..8ebe6345 --- /dev/null +++ b/plugins/wal-replica/pg-receivewal-min/Dockerfile @@ -0,0 +1,18 @@ +FROM gcr.io/distroless/base-debian12:latest + +COPY --from=postgresql:17 /usr/share/postgresql-common/ /bin + +ENV PATH="$PATH:/bin/postgresql-common" + +ENTRYPOINT [ + "pg_wrapper" + --slot + wal_replica + --compress + 0 + --directory + /var/lib/postgresql/wal + --dbname + postgres://streaming_replica@cluster-example-rw/postgres?sslmode=verify-full&sslrootcert=/var/lib/postgresql/rootcert/ca.crt&sslcert=/var/lib/postgresql/cert/tls.crt&sslkey=/var/lib/postgresql/cert/tls.key + --verbose + ] diff --git a/plugins/wal-replica/scripts/build.sh b/plugins/wal-replica/scripts/build.sh index df30b287..aeaa391e 100755 --- a/plugins/wal-replica/scripts/build.sh +++ b/plugins/wal-replica/scripts/build.sh @@ -3,4 +3,4 @@ cd "$(dirname "$0")/.." || exit # Compile the plugin -CGO_ENABLED=0 go build -o bin/cnpg-i-wal-replica main.go +CGO_ENABLED=0 go build -gcflags="all=-N -l" -o bin/cnpg-i-wal-replica main.go From e8534eca6cb446639b146e3dc8d5631284a9ee9b Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Wed, 10 Sep 2025 13:03:33 -0400 Subject: [PATCH 06/10] distroless image first go --- .../wal-replica/pg-receivewal-min/Dockerfile | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/plugins/wal-replica/pg-receivewal-min/Dockerfile b/plugins/wal-replica/pg-receivewal-min/Dockerfile index 8ebe6345..b252720f 100644 --- a/plugins/wal-replica/pg-receivewal-min/Dockerfile +++ b/plugins/wal-replica/pg-receivewal-min/Dockerfile @@ -1,18 +1,30 @@ -FROM gcr.io/distroless/base-debian12:latest +FROM postgres:17 AS pg +FROM gcr.io/distroless/base:latest + +COPY --from=pg /usr/lib/postgresql/17/bin/pg_receivewal /bin/pg_receivewal + +# Copy libraries +COPY --from=pg /usr/lib/x86_64-linux-gnu/libpq.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libzstd.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/liblz4.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libz.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libldap.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libxxhash.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libkrb5.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libk5crypto.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libcom_err.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libkrb5support.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/liblber.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libsasl2.so.* /usr/lib/x86_64-linux-gnu +COPY --from=pg /usr/lib/x86_64-linux-gnu/libkeyutils.so.* /usr/lib/x86_64-linux-gnu -COPY --from=postgresql:17 /usr/share/postgresql-common/ /bin ENV PATH="$PATH:/bin/postgresql-common" -ENTRYPOINT [ - "pg_wrapper" - --slot - wal_replica - --compress - 0 - --directory - /var/lib/postgresql/wal - --dbname - postgres://streaming_replica@cluster-example-rw/postgres?sslmode=verify-full&sslrootcert=/var/lib/postgresql/rootcert/ca.crt&sslcert=/var/lib/postgresql/cert/tls.crt&sslkey=/var/lib/postgresql/cert/tls.key - --verbose - ] +ENTRYPOINT ["/bin/pg_receivewal",\ + "--slot", "wal_replica",\ + "--compress", "0",\ + "--directory", "/var/lib/postgresql/wal",\ + "--dbname", "postgres://streaming_replica@cluster-example-rw/postgres?sslmode=verify-full&sslrootcert=/var/lib/postgresql/rootcert/ca.crt&sslcert=/var/lib/postgresql/cert/tls.crt&sslkey=/var/lib/postgresql/cert/tls.key",\ + "--verbose"] From 4469daf14d4d65713c04b342c9d1cad380333895 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Wed, 10 Sep 2025 16:16:29 -0400 Subject: [PATCH 07/10] add wal slot creation to the plugin --- .../doc/examples/cluster-example.yaml | 5 -- .../internal/reconciler/replica.go | 70 ++++++++++++------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/plugins/wal-replica/doc/examples/cluster-example.yaml b/plugins/wal-replica/doc/examples/cluster-example.yaml index 258eeaef..7961a303 100644 --- a/plugins/wal-replica/doc/examples/cluster-example.yaml +++ b/plugins/wal-replica/doc/examples/cluster-example.yaml @@ -12,11 +12,6 @@ spec: synchronizeReplicas: enabled: true - bootstrap: - initdb: - postInitSQL: - "select * from pg_create_physical_replication_slot('wal_replica'); - storage: size: 1Gi diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index 7890f890..7d11630b 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -6,6 +6,7 @@ package reconciler import ( "context" "fmt" + "strings" cnpgv1 "github.com/cloudnative-pg/api/pkg/api/v1" "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/common" @@ -47,25 +48,6 @@ func CreateWalReplica( // TODO remove this once the operator functions are fixed configuration.ApplyDefaults(cluster) - walDir := configuration.WalDirectory - cmd := []string{ - "pg_receivewal", // TODO what do we do if it's not on the path? - "--slot", "wal_replica", - "--compress", "0", - "--directory", walDir, - "--dbname", GetConnectionString(configuration.ReplicationHost), - } - - // TODO have a real check here - if true { - cmd = append(cmd, "--verbose") - } - - // Add synchronous flag if requested - if configuration.Synchronous == config.SynchronousActive { - cmd = append(cmd, "--synchronous") - } - // Needs a PVC to store the wal data existingPVC := &corev1.PersistentVolumeClaim{} err := client.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: namespace}, existingPVC) @@ -109,7 +91,17 @@ func CreateWalReplica( return err } - // Create replica slot + walDir := configuration.WalDirectory + + // Put the strings together so they run as separate commands, then rewrap + // them in a single arg + args := []string{ + strings.Join([]string{ + GetCommandForWalReceiver(configuration, walDir, true), + "&&", + GetCommandForWalReceiver(configuration, walDir, false), + }, " "), + } // Create or patch Deployment existing := &appsv1.Deployment{} @@ -138,9 +130,10 @@ func CreateWalReplica( ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": deploymentName}}, Spec: corev1.PodSpec{ Containers: []corev1.Container{{ - Name: "wal-receiver", - Image: configuration.Image, - Args: cmd, + Name: "wal-receiver", + Image: configuration.Image, + Command: []string{"/bin/bash", "-c"}, + Args: args, VolumeMounts: []corev1.VolumeMount{ { Name: deploymentName, @@ -208,14 +201,39 @@ func CreateWalReplica( return nil } -func GetConnectionString(host string) string { - return fmt.Sprintf("postgres://%s@%s/postgres?sslmode=verify-full&sslrootcert=%s&sslcert=%s&sslkey=%s", +// TODO change this to just use a custom image that creates the slot and the replica +func GetCommandForWalReceiver(configuration *config.Configuration, walDir string, createSlot bool) string { + connectionString := fmt.Sprintf("postgres://%s@%s/postgres?sslmode=verify-full&sslrootcert=%s&sslcert=%s&sslkey=%s", "streaming_replica", // user - host, + configuration.ReplicationHost, "/var/lib/postgresql/rootcert/ca.crt", // root cert "/var/lib/postgresql/cert/tls.crt", // cert "/var/lib/postgresql/cert/tls.key") // key + createSlotFlag := "" + if createSlot { + createSlotFlag = "--create-slot --if-not-exists" + } + + // TODO have a real check here + verboseFlag := "" + if true { + verboseFlag = "--verbose" + } + + synchronousFlag := "" + if configuration.Synchronous == config.SynchronousActive { + synchronousFlag = "--synchronous" + } + + return fmt.Sprintf("pg_receivewal --slot wal_replica --compress 0 --directory %s --dbname \"%s\" %s %s %s", + walDir, + connectionString, + createSlotFlag, + verboseFlag, + synchronousFlag, + ) } + func int64Ptr(i int64) *int64 { return &i } From fa8b988befc389ec25f99e05995ab8120d7f55b1 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Thu, 11 Sep 2025 09:35:52 -0400 Subject: [PATCH 08/10] remove non-working dockerfile --- plugins/wal-replica/internal/config/config.go | 2 +- .../wal-replica/pg-receivewal-min/Dockerfile | 30 ------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 plugins/wal-replica/pg-receivewal-min/Dockerfile diff --git a/plugins/wal-replica/internal/config/config.go b/plugins/wal-replica/internal/config/config.go index b4ea8f76..e9ee13a3 100644 --- a/plugins/wal-replica/internal/config/config.go +++ b/plugins/wal-replica/internal/config/config.go @@ -17,7 +17,7 @@ import ( // Plugin parameter keys const ( ImageParam = "image" // string - ReplicationHostParam = "replicationHost" // Required: primary host + ReplicationHostParam = "replicationHost" // primary host SynchronousParam = "synchronous" // enum: Active, Inactive, Unset WalDirectoryParam = "walDirectory" // directory where WAL is stored WalPVCSize = "walPVCSize" // Size of the PVC for WAL storage diff --git a/plugins/wal-replica/pg-receivewal-min/Dockerfile b/plugins/wal-replica/pg-receivewal-min/Dockerfile deleted file mode 100644 index b252720f..00000000 --- a/plugins/wal-replica/pg-receivewal-min/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -FROM postgres:17 AS pg -FROM gcr.io/distroless/base:latest - -COPY --from=pg /usr/lib/postgresql/17/bin/pg_receivewal /bin/pg_receivewal - -# Copy libraries -COPY --from=pg /usr/lib/x86_64-linux-gnu/libpq.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libzstd.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/liblz4.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libz.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libldap.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libxxhash.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libkrb5.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libk5crypto.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libcom_err.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libkrb5support.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/liblber.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libsasl2.so.* /usr/lib/x86_64-linux-gnu -COPY --from=pg /usr/lib/x86_64-linux-gnu/libkeyutils.so.* /usr/lib/x86_64-linux-gnu - - -ENV PATH="$PATH:/bin/postgresql-common" - -ENTRYPOINT ["/bin/pg_receivewal",\ - "--slot", "wal_replica",\ - "--compress", "0",\ - "--directory", "/var/lib/postgresql/wal",\ - "--dbname", "postgres://streaming_replica@cluster-example-rw/postgres?sslmode=verify-full&sslrootcert=/var/lib/postgresql/rootcert/ca.crt&sslcert=/var/lib/postgresql/cert/tls.crt&sslkey=/var/lib/postgresql/cert/tls.key",\ - "--verbose"] From ea15d4d549fe7d2c3304d28eea8a84d207cdb685 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Tue, 30 Sep 2025 09:20:57 -0400 Subject: [PATCH 09/10] Update docs --- plugins/wal-replica/README.md | 197 ++++++++-- plugins/wal-replica/doc/development.md | 500 ++++++++++++++++++------ plugins/wal-replica/pkg/metadata/doc.go | 2 +- 3 files changed, 551 insertions(+), 148 deletions(-) diff --git a/plugins/wal-replica/README.md b/plugins/wal-replica/README.md index d9b11a57..1967f50b 100644 --- a/plugins/wal-replica/README.md +++ b/plugins/wal-replica/README.md @@ -1,49 +1,190 @@ -# WAL Receiver Pod Manager (CNPG-I Plugin) +# WAL Replica Pod Manager (CNPG-I Plugin) -This plugin adds an optional standalone WAL receiver (pg_receivewal) Pod/Deployment -alongside a [CloudNativePG](https://github.com/cloudnative-pg/cloudnative-pg/) Cluster. -It reconciles a Deployment named -`-wal-receiver` that continuously streams WAL files from the primary -cluster using `pg_receivewal`, supporting synchronous mode. +This plugin creates a standalone WAL receiver deployment alongside a [CloudNativePG](https://github.com/cloudnative-pg/cloudnative-pg/) cluster. It automatically provisions a Deployment named `-wal-receiver` that continuously streams Write-Ahead Log (WAL) files from the primary PostgreSQL cluster using `pg_receivewal`, with support for both synchronous and asynchronous replication modes. -## Parameters +## Features -Add the plugin in the Cluster spec (example): +- **Automated WAL Streaming**: Continuously receives and stores WAL files from the primary cluster +- **Persistent Storage**: Automatically creates and manages a PersistentVolumeClaim for WAL storage +- **TLS Security**: Uses cluster certificates for secure replication connections +- **Replication Slot Management**: Automatically creates and manages a dedicated replication slot (`wal_replica`) +- **Synchronous Replication Support**: Configurable synchronous/asynchronous replication modes +- **Cluster Lifecycle Management**: Automatically manages resources with proper owner references + +## Configuration + +Add the plugin to your Cluster specification: ```yaml +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: my-cluster spec: - plugins: - - name: cnpg-i-wal-replica.documentdb.io - parameters: - image: "ghcr.io/cloudnative-pg/postgresql:16" - replicationHost: cluster-name-rw - synchronous: "enabled" # optional (default true) - walDirectory: /var/lib/wal # optional (default /var/lib/wal) + instances: 3 + + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + image: "ghcr.io/cloudnative-pg/postgresql:16" + replicationHost: "my-cluster-rw" + synchronous: "active" + walDirectory: "/var/lib/postgresql/wal" + walPVCSize: "20Gi" + + replicationSlots: + synchronizeReplicas: + enabled: true + + storage: + size: 10Gi ``` +### Parameters + | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| image | string | ghcr.io/cloudnative-pg/postgresql:16 | Image providing pg_receivewal | -| replicationHost | string | -rw | Host to connect for streaming | -| synchronous | bool | true | Add --synchronous flag to pg_receivewal | -| walDirectory | string | /var/lib/wal | Local directory to store WAL | +| `image` | string | Cluster status image | Container image providing `pg_receivewal` binary | +| `replicationHost` | string | `-rw` | Primary host endpoint for WAL streaming | +| `synchronous` | string | `inactive` | Replication mode: `active` (synchronous) or `inactive` (asynchronous) | +| `walDirectory` | string | `/var/lib/postgresql/wal` | Directory path for storing received WAL files | +| `walPVCSize` | string | `10Gi` | Size of the PersistentVolumeClaim for WAL storage | + +#### Synchronous Modes + +- **`active`**: Enables synchronous replication with `--synchronous` flag +- **`inactive`**: Standard asynchronous replication (default) + +## Architecture + +The plugin creates the following Kubernetes resources: + +1. **Deployment**: `-wal-receiver` + - Single replica pod running `pg_receivewal` + - Configured with proper security context (user: 105, group: 103) + - Automatic restart policy for high availability + +2. **PersistentVolumeClaim**: `-wal-receiver` + - Stores received WAL files persistently + - Uses `ReadWriteOnce` access mode + - Configurable size via `walPVCSize` parameter + +3. **Volume Mounts**: + - WAL storage: Mounted at configured `walDirectory` + - TLS certificates: Mounted from cluster certificate secrets + - CA certificates: Mounted for SSL verification -The Deployment exposes a metrics port (9187) and creates a Service with the same name. +## Security -## Build +The plugin implements comprehensive security measures: + +- **TLS Encryption**: All replication connections use SSL/TLS +- **Certificate Management**: Automatically mounts cluster CA and client certificates +- **User Privileges**: Runs with dedicated PostgreSQL user and group IDs +- **Connection Authentication**: Uses `streaming_replica` user with certificate-based auth + +## Prerequisites + +- CloudNativePG operator installed and running +- CNPG-I (CloudNativePG Interface) framework deployed +- Cluster with enabled replication slots synchronization +- Sufficient storage for WAL files retention + +## Installation + +### Building from Source ```bash +# Clone the repository +git clone https://github.com/documentdb/cnpg-i-wal-replica +cd cnpg-i-wal-replica + +# Build the binary go build -o bin/cnpg-i-wal-replica main.go ``` -## Status +### Using Docker + +```bash +# Build container image +docker build -t cnpg-i-wal-replica:latest . +``` + +### Deployment Scripts + +```bash +# Make scripts executable +chmod +x scripts/build.sh scripts/run.sh + +# Build and run +./scripts/build.sh +./scripts/run.sh +``` + +## Monitoring and Observability + +The WAL receiver pod provides verbose logging when enabled, including: + +- Connection status to primary cluster +- WAL file reception progress +- Replication slot status +- SSL/TLS connection details + +## Examples + +See the `doc/examples/` directory for complete cluster configurations: + +- [`cluster-example.yaml`](doc/examples/cluster-example.yaml): Basic configuration +- [`cluster-example-no-parameters.yaml`](doc/examples/cluster-example-no-parameters.yaml): Default settings +- [`cluster-example-with-mistake.yaml`](doc/examples/cluster-example-with-mistake.yaml): Common configuration errors + +## Development + +### Project Structure + +``` +├── cmd/plugin/ # Plugin command-line interface +├── internal/ +│ ├── config/ # Configuration management +│ ├── identity/ # Plugin identity and metadata +│ ├── k8sclient/ # Kubernetes client utilities +│ ├── operator/ # Operator implementations +│ └── reconciler/ # Resource reconciliation logic +├── kubernetes/ # Kubernetes manifests +├── pkg/metadata/ # Plugin metadata and constants +└── scripts/ # Build and deployment scripts +``` + +### Running Tests + +```bash +go test ./... +``` + +See [`doc/development.md`](doc/development.md) for detailed development guidelines. + +## Limitations and Future Enhancements + +### Current Limitations + +- Fixed compression level (disabled: `--compress 0`) +- No built-in WAL retention/cleanup policies +- Limited resource configuration options + +### Planned Enhancements + +- [ ] Configurable resource requests and limits +- [ ] WAL retention and garbage collection policies +- [ ] Health checks and readiness probes +- [ ] Metrics exposure for monitoring integration +- [ ] Multi-zone/region WAL archiving support +- [ ] Backup integration with existing CNPG backup strategies + +## License -The plugin status reflects only whether it is enabled. +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## Future Work +## Contributing -* Add PVC / volume configuration for WAL directory -* Expose resource requests/limits and security context -* Garbage collection / retention policy for archived WAL -* Liveness/readiness refinements +Contributions are welcome! Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines on how to contribute to this project. diff --git a/plugins/wal-replica/doc/development.md b/plugins/wal-replica/doc/development.md index b98e9cd1..e8887f47 100644 --- a/plugins/wal-replica/doc/development.md +++ b/plugins/wal-replica/doc/development.md @@ -1,180 +1,442 @@ -# Plugin Development +# WAL Replica Plugin Development Guide -This section of the documentation illustrates the CNPG-I capabilities used by -the wal-replica plugin, how the plugin implementation uses them, and how -developers can build and deploy the plugin. +This document provides comprehensive guidance for developers working with the WAL Replica plugin for CloudNativePG. It covers the CNPG-I framework capabilities used, implementation details, and development workflows. -## Concepts +## Overview -### Identity +The WAL Replica plugin (`cnpg-i-wal-replica.documentdb.io`) is built using the CloudNativePG Interface (CNPG-I) framework, which provides a plugin architecture for extending CloudNativePG clusters with custom functionality. -The Identity interface defines the features supported by the plugin and is the -only interface that must always be implemented. +### Plugin Metadata -This information is essential for the operator to discover the plugin's -capabilities during startup. +```go +// From pkg/metadata/doc.go +const PluginName = "cnpg-i-wal-replica.documentdb.io" +Version: "0.1.0" +DisplayName: "WAL Replica Pod Manager" +License: "MIT" +Maturity: "alpha" +``` + +## CNPG-I Framework Concepts -The Identity interface provides: +### Identity Interface -- A mechanism for plugins to report readiness probes. Readiness is a - prerequisite for receiving events, and plugins are expected to always report - the most accurate readiness data available. -- The capabilities reported by the plugin, which determine the subsequent calls - the plugin will receive. -- Metadata about the plugin. +The Identity interface is fundamental to all CNPG-I plugins and defines: -[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/identity.proto) +- **Plugin Metadata**: Name, version, description, and licensing information +- **Capabilities**: Which CNPG-I services the plugin implements +- **Readiness**: Health check mechanism for the plugin -### Capabilities +The identity implementation is located in [`internal/identity/impl.go`](../internal/identity/impl.go). -This plugin implements the Operator and the Lifecycle capabilities. +**Key Methods:** +- `GetPluginMetadata()`: Returns plugin information from `pkg/metadata` +- `GetPluginCapabilities()`: Declares supported services (Operator + Reconciler) +- `Probe()`: Always returns ready (stateless plugin) -#### Operator +[CNPG-I Identity API Reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/identity.proto) -This feature enables the plugin to receive events about the cluster creation and -mutations, this is defined by the following +### Implemented Capabilities -``` proto -// ValidateCreate improves the behavior of the validating webhook that -// is called on creation of the Cluster resources -rpc ValidateClusterCreate(OperatorValidateClusterCreateRequest) returns (OperatorValidateClusterCreateResult) {} +This plugin implements two core CNPG-I capabilities: -// ValidateClusterChange improves the behavior of the validating webhook of -// is called on updates of the Cluster resources -rpc ValidateClusterChange(OperatorValidateClusterChangeRequest) returns (OperatorValidateClusterChangeResult) {} +#### 1. Operator Interface -// MutateCluster fills in the defaults inside a Cluster resource -rpc MutateCluster(OperatorMutateClusterRequest) returns (OperatorMutateClusterResult) {} +Provides cluster-level validation and mutation capabilities through webhooks: + +```go +// From internal/operator/ +rpc ValidateClusterCreate(OperatorValidateClusterCreateRequest) returns (OperatorValidateClusterCreateResult) +rpc ValidateClusterChange(OperatorValidateClusterChangeRequest) returns (OperatorValidateClusterChangeResult) +rpc MutateCluster(OperatorMutateClusterRequest) returns (OperatorMutateClusterResult) ``` -This interface allows plugins to implement important features like: +**Implementation Features:** +- **Parameter Validation**: Validates `synchronous`, `walPVCSize`, and other plugin parameters +- **Default Application**: Sets default values for image, replication host, WAL directory +- **Configuration Parsing**: Converts plugin parameters to typed configuration objects -1. validating the cluster manifest during the creation and mutations - (it is expected that the plugin validate the parameters assigned to their - configuration). +See [`internal/operator/validation.go`](../internal/operator/validation.go) and [`internal/operator/mutations.go`](../internal/operator/mutations.go). -2. mutating the cluster object before it is submitted to kubernetes API server, - for example to set default values for the plugin parameters. +#### 2. Reconciler Hooks Interface -[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator.proto) +Enables resource reconciliation and custom Kubernetes resource management: -The wal-replica plugin is using this to validate used-defined parameters, and to -set default values for the labels and annotations applied by the plugin if not -specified by the user. +```go +// From internal/reconciler/ +rpc ReconcilerHook(ReconcilerHookRequest) returns (ReconcilerHookResponse) +``` -#### Lifecycle +**Core Functionality:** +- **WAL Receiver Deployment**: Creates and manages the `-wal-receiver` deployment +- **PVC Management**: Provisions persistent storage for WAL files +- **TLS Configuration**: Sets up certificate-based authentication +- **Resource Lifecycle**: Handles creation, updates, and cleanup with owner references -This feature enables the plugin to receive events and create patches for -Kubernetes resources `before` they are submitted to the API server. +See [`internal/reconciler/replica.go`](../internal/reconciler/replica.go) for the main implementation. -To use this feature, the plugin must specify the resource and operation it wants -to be notified of. +[CNPG-I Operator API Reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator.proto) -Some examples of what it can be achieved through the lifecycle: +## Architecture Deep Dive -- add volume, volume mounts, sidecar containers, labels, annotations to pods, - especially necessary when implementing custom backup solutions -- modify any resource with some annotations or labels -- add/remove finalizers +### Configuration Management -[API reference](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator_lifecycle.proto): +The plugin uses a layered configuration approach: -The wal-replica plugin is using this to add labels, annotations and a sidecar -to the pods. +```go +// From internal/config/config.go +type Configuration struct { + Image string // Container image for pg_receivewal + ReplicationHost string // Primary cluster endpoint + Synchronous SynchronousMode // active/inactive replication mode + WalDirectory string // WAL storage path + WalPVCSize string // Storage size for PVC +} +``` -## Implementation +**Configuration Flow:** +1. Raw parameters from Cluster spec +2. Validation using `ValidateParams()` +3. Type conversion via `FromParameters()` +4. Default application with `ApplyDefaults()` + +### Resource Reconciliation + +The plugin creates and manages several Kubernetes resources: + +#### WAL Receiver Deployment + +```yaml +# Generated deployment structure +apiVersion: apps/v1 +kind: Deployment +metadata: + name: -wal-receiver + ownerReferences: [] +spec: + containers: + - name: wal-receiver + image: + command: ["/bin/bash", "-c"] + args: [""] + volumeMounts: + - name: wal-storage + mountPath: + - name: ca + mountPath: /var/lib/postgresql/rootcert + - name: tls + mountPath: /var/lib/postgresql/cert +``` -### Identity +#### pg_receivewal Command Construction -1. Define a struct inside the `internal/identity` package that implements - the `pluginhelper.IdentityServer` interface. +The plugin builds sophisticated `pg_receivewal` commands: -2. Implement the following methods: +```bash +# Two-phase execution: +# 1. Create replication slot (if needed) +pg_receivewal --slot wal_replica --create-slot --if-not-exists --directory /path/to/wal --dbname "postgres://streaming_replica@host/postgres?sslmode=verify-full&..." - - `GetPluginMetadata`: return human-readable information about the plugin. - - `GetPluginCapabilities`: specify the features supported by the plugin. In - the wal-replica example, the - `PluginCapability_Service_TYPE_LIFECYCLE_SERVICE` is defined in the - corresponding Go [file](../internal/lifecycle/lifecycle.go). - - `Probe`: indicate whether the plugin is ready to serve requests; this - example is stateless, so it will always be ready. +# 2. Continuous WAL streaming +pg_receivewal --slot wal_replica --compress 0 --directory /path/to/wal --dbname "postgres://..." [--synchronous] [--verbose] +``` -### Lifecycle +### Security Implementation -This example implements the lifecycle service capabilities to add labels and -annotations to the pods. The `OperatorLifecycleServer` interface is implemented -inside the `internal/lifecycle` package. +**TLS Configuration:** +- Uses cluster-managed certificates from CloudNativePG +- Mounts CA certificate for SSL verification +- Client certificate authentication for `streaming_replica` user +- `sslmode=verify-full` for maximum security -The `OperatorLifecycleServer` interface requires several methods: +**Pod Security:** +- Runs as PostgreSQL user (`uid: 105, gid: 103`) +- Proper filesystem permissions (`fsGroup: 103`) +- Read-only certificate mounts -- `GetCapabilities`: describe the resources and operations the plugin should be - notified for +## Development Environment Setup -- `LifecycleHook`: is invoked for every operation against the Kubernetes API - server that matches the specifications returned by `GetCapabilities` +### Prerequisites - In this function, the plugin is expected to do pattern matching using - the `Kind` and the operation `Type` and proceed with the proper logic. +```bash +# Required tools +go 1.24.1+ +docker or podman +kubectl +kind (for local testing) -### Operator +# Required Kubernetes components +cloudnative-pg operator +cnpg-i framework +cert-manager (for TLS) +``` -The operator interface offers a way for the plugin to interact with the Cluster -resource webhooks. +### Local Development -Do that, the plugin should implement -the [operator](https://github.com/cloudnative-pg/cnpg-i/blob/main/proto/operator.proto) -interface, specifically the `MutateCluster`, `ValidateClusterCreate`, -and `ValidateClusterChange` rpc calls. +#### 1. Environment Setup -- `MutateCluster`: enriches the plugin defaulting webhook +```bash +# Clone repository +git clone https://github.com/documentdb/cnpg-i-wal-replica +cd cnpg-i-wal-replica -- `ValidateClusterCreate` and `ValidateClusterChange`: enriches the plugin - validation logic. +# Install dependencies +go mod download -The package `internal/operator` implements this interface. +# Verify build +go build -o bin/cnpg-i-wal-replica main.go +``` -### Startup Command +#### 2. Code Structure Navigation -The plugin runs in its own pod, and its main command is implemented in -the `main.go` file. +``` +├── cmd/plugin/ # CLI interface and gRPC server setup +│ ├── doc.go # Package documentation +│ └── plugin.go # Main command and service registration +├── internal/ +│ ├── config/ # Configuration management +│ │ ├── config.go # Configuration types and validation +│ │ └── doc.go # Package documentation +│ ├── identity/ # Plugin identity implementation +│ │ ├── impl.go # Identity service methods +│ │ └── doc.go # Package documentation +│ ├── k8sclient/ # Kubernetes client utilities +│ │ ├── k8sclient.go # Client initialization and management +│ │ └── doc.go # Package documentation +│ ├── operator/ # Operator interface implementation +│ │ ├── impl.go # Core operator methods +│ │ ├── mutations.go # Cluster mutation logic +│ │ ├── status.go # Status reporting +│ │ ├── validation.go# Parameter validation +│ │ └── doc.go # Package documentation +│ └── reconciler/ # Resource reconciliation +│ ├── impl.go # Reconciler hook implementation +│ ├── replica.go # WAL receiver resource management +│ └── doc.go # Package documentation +├── kubernetes/ # Deployment manifests +└── pkg/metadata/ # Plugin metadata constants +``` -This function uses the plugin helper library to create a GRPC server and manage -TLS. +#### 3. Testing Locally -Plugin developers are expected to use the `pluginhelper.CreateMainCmd` -to implement the `main` function, passing an implemented `Identity` -struct. +```bash +# Build and test +./scripts/build.sh -Further implementations can be registered within the callback function. +# Run with debugging +./scripts/run.sh -In the example we propose, that's done for **operator** and for the -**lifecycle** services in [file](../cmd/plugin/plugin.go): +# Test configuration parsing +go test ./internal/config/ -``` proto -operator.RegisterOperatorServer(server, operatorImpl.Implementation{}) -lifecycle.RegisterOperatorLifecycleServer(server, lifecycleImpl.Implementation{}) +# Test reconciliation logic +go test ./internal/reconciler/ ``` -## Build and deploy the plugin +### Container Development + +#### Building Images + +```bash +# Local build +docker build -t wal-replica-plugin:dev . + +# Multi-arch build +docker buildx build --platform linux/amd64,linux/arm64 -t wal-replica-plugin:latest . +``` + +#### Kubernetes Testing + +```bash +# Load into kind cluster +kind load docker-image wal-replica-plugin:dev + +# Apply manifests +kubectl apply -f kubernetes/ + +# Deploy test cluster +kubectl apply -f doc/examples/cluster-example.yaml +``` + +## Extending the Plugin + +### Adding New Parameters + +1. **Define in Configuration**: +```go +// internal/config/config.go +const MyNewParam = "myNewParam" + +type Configuration struct { + // existing fields... + MyNewValue string +} +``` + +2. **Add Validation**: +```go +// internal/config/config.go +func ValidateParams(helper *common.Plugin) []*operator.ValidationError { + // existing validation... + + if raw, present := helper.Parameters[MyNewParam]; present { + // Add validation logic + } +} +``` + +3. **Update Reconciliation**: +```go +// internal/reconciler/replica.go +func CreateWalReplica(ctx context.Context, cluster *cnpgv1.Cluster) error { + // Use configuration.MyNewValue in resource creation +} +``` + +### Adding Resource Management + +```go +// Example: Adding a Service resource +func createWalReceiverService(ctx context.Context, cluster *cnpgv1.Cluster) error { + service := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-wal-receiver", cluster.Name), + Namespace: cluster.Namespace, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: cluster.APIVersion, + Kind: cluster.Kind, + Name: cluster.Name, + UID: cluster.UID, + }}, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{"app": fmt.Sprintf("%s-wal-receiver", cluster.Name)}, + Ports: []corev1.ServicePort{{ + Port: 5432, + TargetPort: intstr.FromInt(5432), + }}, + }, + } + + return k8sclient.MustGet().Create(ctx, service) +} +``` + +## Debugging and Troubleshooting + +### Common Development Issues + +1. **gRPC Connection Problems**: +```bash +# Check plugin registration +kubectl logs -l app=cnpg-i-wal-replica + +# Verify TLS certificates +kubectl describe secret -ca-secret +``` + +2. **Resource Creation Failures**: +```bash +# Check reconciler logs +kubectl logs deployment/-wal-receiver + +# Verify owner references +kubectl get deployment -wal-receiver -o yaml +``` + +3. **Parameter Validation Errors**: +```bash +# Check cluster events +kubectl describe cluster + +# Review validation logs +kubectl logs -l app=cnpg-operator +``` + +### Testing Configurations + +```yaml +# Test with minimal parameters +spec: + plugins: + - name: cnpg-i-wal-replica.documentdb.io + # No parameters - should use all defaults + +# Test with full configuration +spec: + plugins: + - name: cnpg-i-wal-replica.documentdb.io + parameters: + image: "postgres:16" + replicationHost: "my-cluster-rw" + synchronous: "active" + walDirectory: "/custom/wal/path" + walPVCSize: "50Gi" +``` + +## CI/CD Integration + +### GitHub Actions Workflow + +The repository includes automated workflows for: + +- **Build Verification**: Compiles plugin for multiple architectures +- **Container Publishing**: Builds and pushes container images +- **Manifest Generation**: Creates deployment artifacts +- **Integration Testing**: Tests against live CloudNativePG clusters + +### Deployment Artifacts + +Generated manifests include: +- Plugin deployment with proper RBAC +- Certificate management for TLS +- Service definitions for plugin discovery +- Example cluster configurations + +## Contributing Guidelines + +### Code Standards + +- Follow Go conventions and `gofmt` formatting +- Add comprehensive unit tests for new functionality +- Document all public interfaces and complex logic +- Use structured logging with appropriate levels + +### Pull Request Process + +1. Fork and create feature branch +2. Implement changes with tests +3. Update documentation +4. Submit PR with detailed description +5. Address review feedback + +### Testing Requirements + +- Unit tests for all new configuration parameters +- Integration tests for resource reconciliation +- End-to-end testing with real CloudNativePG clusters +- Performance testing for WAL streaming scenarios -Users can test their own changes to the plugin by building a container image -running it inside a Kubernetes cluster with CloudNativePG and cert-manager -installed. +## Future Development Roadmap -### Local build +### Planned Enhancements -The repository provides a [`Taskfile`](https://taskfile.dev/) that contains -several helpful commands to test the plugin in -a [CNPG development environment](https://github.com/cloudnative-pg/cloudnative-pg/tree/main/contribute/e2e_testing_environment#the-local-kubernetes-cluster-for-testing). +- **Enhanced Monitoring**: Prometheus metrics for WAL streaming +- **Multi-Zone Support**: Cross-region WAL archival capabilities +- **Backup Integration**: Coordination with CloudNativePG backup strategies +- **Resource Optimization**: Configurable resource requests/limits +- **Advanced Filtering**: WAL file retention and cleanup policies +- **Replica Support**: Extension to replica clusters for cascading replication -By executing `task local-kind-deploy`, a container image containing the -executable of the repository will be built and loaded inside the kind cluster. +### API Stability -Having done that, the wal-replica plugin deployment will be applied. +- Current API is alpha-level with potential breaking changes +- Plugin interface follows CNPG-I versioning conventions +- Configuration parameters may evolve based on user feedback -### CI/CD build +## Resources -The repository provides a GitHub Actions workflow that, on pushes, builds a -container image and generates a manifest file that can be used to deploy the -plugin. The manifest is attached to the workflow run as an artifact, and can be -applied to the cluster. +- [CloudNativePG Documentation](https://cloudnative-pg.io/) +- [CNPG-I Framework](https://github.com/cloudnative-pg/cnpg-i) +- [PostgreSQL WAL Documentation](https://www.postgresql.org/docs/current/wal.html) +- [Plugin Examples Repository](https://github.com/cloudnative-pg/cnpg-i-examples) diff --git a/plugins/wal-replica/pkg/metadata/doc.go b/plugins/wal-replica/pkg/metadata/doc.go index cca11b21..1670754a 100644 --- a/plugins/wal-replica/pkg/metadata/doc.go +++ b/plugins/wal-replica/pkg/metadata/doc.go @@ -16,7 +16,7 @@ var Data = identity.GetPluginMetadataResponse{ DisplayName: "WAL Replica Pod Manager", ProjectUrl: "https://github.com/documentdb/cnpg-i-wal-replica", RepositoryUrl: "https://github.com/documentdb/cnpg-i-wal-replica", - License: "Proprietary", + License: "MIT", LicenseUrl: "https://github.com/documentdb/cnpg-i-wal-replica/LICENSE", Maturity: "alpha", } From e5ada08e96f345512c4c1738b8a779dcce296e81 Mon Sep 17 00:00:00 2001 From: Alexander Laye Date: Tue, 30 Sep 2025 11:58:05 -0400 Subject: [PATCH 10/10] remove plugin-side primary detection --- plugins/wal-replica/internal/reconciler/replica.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugins/wal-replica/internal/reconciler/replica.go b/plugins/wal-replica/internal/reconciler/replica.go index 7d11630b..a448f044 100644 --- a/plugins/wal-replica/internal/reconciler/replica.go +++ b/plugins/wal-replica/internal/reconciler/replica.go @@ -28,11 +28,6 @@ func CreateWalReplica( ) error { logger := log.FromContext(ctx).WithName("CreateWalReplica") - if !IsPrimaryCluster(cluster) { - logger.Info("Cluster is not a primary, skipping wal replica creation", "cluster", cluster.Name) - return nil - } - // Build Deployment name unique per cluster deploymentName := fmt.Sprintf("%s-wal-receiver", cluster.Name) namespace := cluster.Namespace @@ -240,8 +235,3 @@ func int64Ptr(i int64) *int64 { func int32Ptr(i int32) *int32 { return &i } - -func IsPrimaryCluster(cluster *cnpgv1.Cluster) bool { - // TODO implement - return true -}