Skip to content

fix(remote-config): strip server-managed metadata fields from generated YAML - #832

Merged
souravbiswassanto merged 11 commits into
masterfrom
fix/remote-config-strip-server-managed-fields
Aug 7, 2026
Merged

fix(remote-config): strip server-managed metadata fields from generated YAML#832
souravbiswassanto merged 11 commits into
masterfrom
fix/remote-config-strip-server-managed-fields

Conversation

@souravbiswassanto

@souravbiswassanto souravbiswassanto commented Jun 18, 2026

Copy link
Copy Markdown
Member

Problem

`kubectl dba remote-config postgres` generates a YAML file from live objects fetched out of the source cluster. Two bugs caused `kubectl apply` to fail on the remote cluster.

Bug 1 — server-managed fields in generated YAML (first error)

The fetched AppBinding and TLS Secret had server-assigned fields baked in:

  • `resourceVersion`, `uid`, `creationTimestamp`, `generation`
  • cert-manager ownership label `controller.cert-manager.io/fao` on the TLS Secret

Applying these to the remote cluster caused two errors:

  • AppBinding: stale `resourceVersion` triggered optimistic concurrency conflict — "the object has been modified; please apply your changes to the latest version".
  • TLS Secret: `uid` and `creationTimestamp` are immutable server fields; the API server rejected the patch with a Conflict error.

Bug 2 — 3-way merge corruption of `metadata.resourceVersion` (second error)

Even after clearing `resourceVersion` from the generated YAML, re-applying still failed:

The appbindings "pg-london" is invalid: metadata.resourceVersion: Invalid value: 0: must be specified for an update

Root cause: `kubectl apply` stores the full desired state in the `kubectl.kubernetes.io/last-applied-configuration` annotation. Because the previous (buggy) YAML contained `resourceVersion: "5735"`, the annotation stored it. When re-applied with the new YAML (no `resourceVersion`), the 3-way merge logic sees:

  • last-applied had `resourceVersion: "5735"`
  • desired state has no `resourceVersion`
  • conclusion: user removed the field → patch sets `metadata.resourceVersion: ""`

The API server rejects that as `Invalid value: 0`.

The deeper issue is that the AppBinding was built by mutating the live object fetched from the source cluster, which unavoidably leaked source-cluster metadata into the generated YAML and its last-applied annotation.

Fix

Bug 1 — clear `ResourceVersion`, `UID`, `CreationTimestamp`, and `Labels` from the TLS Secret before marshalling.

Bug 2 — construct the AppBinding from scratch with only the fields the remote cluster needs (connection coordinates, auth/TLS secret refs, DB type/version). This keeps the last-applied annotation minimal and consistent, so no future re-apply can corrupt `metadata.resourceVersion` via the 3-way merge. It also stops carrying over source-cluster labels, annotations, `appRef`, and Stash `parameters` that are irrelevant on the remote side.

One-time migration note: if `kubectl apply` was previously run with the old (buggy) YAML, the AppBinding's last-applied annotation is already polluted. For that cluster, run:

kubectl delete appbinding -n <ns> <dbname>
kubectl apply -f <dbname>-remote-config.yaml

After one clean apply the annotation is correct and subsequent applies are idempotent.

Affected files

  • `pkg/remote_replica/postgres.go`
  • `pkg/remote_replica/mysql.go`

souravbiswassanto and others added 9 commits August 7, 2026 18:55
…ed YAML

kubectl apply fails with a resource version conflict when the generated
config YAML retains resourceVersion, uid, creationTimestamp, and
generation fetched from the source cluster. These fields are
server-assigned and must not be present in manifests applied to a
different cluster.

Also remove Labels from the TLS Secret: the cert-manager ownership label
(controller.cert-manager.io/fao) must not be propagated to the remote
cluster where cert-manager would incorrectly take ownership of the secret.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
…e corruption

When kubectl apply was previously run with a YAML that contained
resourceVersion (from the source cluster), the kubectl.kubernetes.io/
last-applied-configuration annotation stored it. On re-apply with a
clean YAML (no resourceVersion), the 3-way merge treats the missing
field as 'user wants to delete it' and patches metadata.resourceVersion
to empty string, which the API server rejects as invalid value 0.

Fix by constructing a minimal AppBinding containing only the connection
fields needed by the remote replica, rather than mutating the AppBinding
fetched from the source cluster. This keeps the last-applied annotation
minimal and idempotent across re-runs, and avoids carrying over
source-cluster labels, annotations, appRef, and Stash parameters that
are irrelevant on the remote side.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
…from scratch

Building the AppBinding from scratch drops fields that exist in the
source AppBinding spec (appRef, parameters, secretTransforms, and any
future additions). Using DeepCopy + clean ObjectMeta is safer: the full
spec is preserved while server-managed metadata is discarded.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
…ore dereference

ClientConfig.Service (*ServiceReference) and Secret (*TypedLocalObjectReference)
are optional pointer fields. If the source AppBinding uses clientConfig.url
instead of clientConfig.service, or has no secret set, the DeepCopy would
carry nil pointers and the subsequent .Name assignments would panic.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
Signed-off-by: souravbiswassanto <saurov@appscode.com>
…t name

If --auth-secret is provided, the generated auth Secret and the AppBinding's
secret reference both use that name. If omitted, the default
<dbname>-remote-replica-auth is used as before.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
-s is reserved by k8s.io/cli-runtime for --server (the Kubernetes API
server address) and is registered as a root persistent flag inherited by
all subcommands. Using the same shorthand causes a panic in cobra's
mergePersistentFlags. Use the long form --auth-secret only.

Signed-off-by: souravbiswassanto <saurov@appscode.com>
generateAuthSecret declared `var buffer []byte` at the top of the function
and then redeclared it with `:=` after marshalling the secret, which fails
to compile:

  pkg/remote_replica/postgres.go:261:9: no new variables on left side of :=

The leading declaration is dead — buffer is not referenced before the
`make` call — so remove it and keep the sized allocation.

Signed-off-by: Tamal Saha <tamal@appscode.com>
--port (default 5432, also accepted as -d host:port; an explicit --port wins)
is written into the generated AppBinding's spec.clientConfig.service.port.
Sources behind a load balancer are commonly exposed on non-standard frontend
ports; the operator injects this as PRIMARY_PORT into the remote replica
containers.

--replica-name, when set, appends a ready-to-apply remote replica Postgres
manifest to the generated config, sized from the source spec: version,
replicas, storage, storageType, standby mode and the postgres container's
resources are copied; remoteReplica.sourceRef, the generated auth secret and
a disabled write check are added. spec.tls, monitoring, archiver and custom
sidecars are deliberately not carried over (the remote cluster has its own
issuer), clientAuthMode falls back from cert to md5 for the same reason, and
deletionPolicy is forced to Halt so a DR replica's PVCs survive accidental CR
deletion. The result is a starting point; a secondary site is often sized
differently on purpose.

Verified end to end: one command against a live TLS source exposed on port
5434, one kubectl apply on the remote cluster creating secrets, AppBinding
and Postgres CR; the replica reached Ready streaming over port 5434 with the
copied resources in place.

Signed-off-by: Tamal Saha <tamal@appscode.com>
@tamalsaha
tamalsaha force-pushed the fix/remote-config-strip-server-managed-fields branch from f8e7601 to 13c6173 Compare August 7, 2026 12:57
@souravbiswassanto
souravbiswassanto changed the base branch from dcdr-cli to master August 7, 2026 13:29
Signed-off-by: souravbiswassanto <saurov@appscode.com>
@souravbiswassanto
souravbiswassanto force-pushed the fix/remote-config-strip-server-managed-fields branch from 13c6173 to c8dc583 Compare August 7, 2026 14:10
Add --ca-cert/--ca-key (required together: signing needs the CA's
private key, ca.crt alone cannot issue anything). When given, the
client certificate is generated and signed locally instead of through
cert-manager, covering sources whose TLS is not cert-manager-managed.
CN is the replication user, validity is clamped to the CA's own expiry,
and the emitted kubernetes.io/tls Secret carries ca.crt/tls.crt/tls.key
exactly as the operator expects to mount them.

Add --client-sans (comma separated DNS names) applied to the generated
certificate on both the local-CA and cert-manager paths.

Signed-off-by: Tamal Saha <tamal@appscode.com>
@souravbiswassanto
souravbiswassanto marked this pull request as ready for review August 7, 2026 15:18
@souravbiswassanto
souravbiswassanto merged commit 698c8c1 into master Aug 7, 2026
6 checks passed
@souravbiswassanto
souravbiswassanto deleted the fix/remote-config-strip-server-managed-fields branch August 7, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants