Skip to content

Add k8ssecret strategy for cross-namespace secret watching - #66

Closed
daniel-garcia wants to merge 1 commit into
mainfrom
feat/k8ssecret-strategy
Closed

Add k8ssecret strategy for cross-namespace secret watching#66
daniel-garcia wants to merge 1 commit into
mainfrom
feat/k8ssecret-strategy

Conversation

@daniel-garcia

Copy link
Copy Markdown
Contributor

Summary

  • New k8ssecret/ directory containing a separate Go module (github.com/infobloxopen/hotload/k8ssecret)
  • Implements hotload.Strategy by watching Kubernetes Secrets via the K8s API
  • Enables cross-namespace secret watching for services that cannot mount secrets from other namespaces as volumes
  • Separate go.mod keeps client-go and its transitive dependencies out of the root hotload module

DSN Format

k8ssecret://pgx/<secret-name>?namespace=<ns>&key=<key>

Usage

import (
    _ "github.com/infobloxopen/hotload"
    _ "github.com/infobloxopen/hotload/k8ssecret"
)

db, err := sql.Open("hotload", "k8ssecret://pgx/myapp-db?namespace=prod&key=dsn")

Motivation

In multi-namespace Kubernetes deployments, a platform service (e.g., a database relay daemon) needs to read database credentials from a Secret in another team's namespace. Kubernetes does not allow mounting Secrets cross-namespace, so volume-based approaches (fsnotify) don't work. This strategy watches the Secret via the K8s API instead.

Test plan

  • 8 unit tests using client-go/kubernetes/fake
  • Initial value fetch, missing secret, missing key, default key
  • Live update propagation via fake clientset watch
  • CloseWatch cleanup, Close cleanup, multiple watchers on same secret

New separate Go module (k8ssecret/) that implements hotload.Strategy by
watching Kubernetes Secrets via the K8s API. This enables cross-namespace
secret watching for services that cannot mount secrets from other
namespaces as volumes.

DSN format: k8ssecret://pgx/<secret-name>?namespace=<ns>&dsn=<key>

The module has its own go.mod to keep client-go and its transitive
dependencies out of the root hotload module.

Also fixes pre-existing CI failure by bumping Dockerfile.test and
Dockerfile.integrationtest from golang:1.23 to golang:1.24 (ginkgo
v2.28.1 requires go >= 1.24.0).
@daniel-garcia
daniel-garcia force-pushed the feat/k8ssecret-strategy branch from 830f6ed to 4b3fb92 Compare March 19, 2026 21:44
daniel-garcia added a commit that referenced this pull request Jun 11, 2026
Port the k8ssecret strategy idea from PR #66 onto v3: a nested module
(github.com/infobloxopen/hotload/k8ssecret, keeping client-go out of the
core) implementing hotload.Strategy by watching a Kubernetes Secret
through the API server, for deployments that need credentials from a
Secret in another namespace, which Kubernetes cannot mount as a volume.

DSN format matches the PR: k8ssecret://<driver>/<secret>?namespace=<ns>&dsn=<key>,
with the namespace defaulting to the pod's own and the key to dsn.txt.

Beyond the port, this fixes four defects in the PR #66 implementation:
- The hotload core passes uri.Path, so the secret name arrives with a
  leading slash; the PR used it verbatim and would look up a secret
  named "/myapp-db" (its tests called Watch directly with bare names,
  masking this). Covered here by an end-to-end test through sql.Open
  with a minimal fake driver.
- Updates could be lost in the gap between the initial Get and the
  watch start, and while a dropped watch waited to reconnect (only
  Modified events were handled, watching from "now"). Every
  (re)connect now re-reads the Secret, delivers missed changes, and
  watches from that read's resource version; Added events (recreated
  secrets, post-reconnect snapshots) are handled too.
- Slow subscribers had the newest value dropped when their buffer was
  full, leaving them permanently stale; delivery now drops the oldest
  queued value so subscribers converge on the latest.
- Watches were keyed by namespace/name only, so two DSNs reading
  different data keys of the same Secret shared one value; the data key
  is now part of the watch identity.

Tests use client-go's fake clientset: initial fetch, missing
secret/key, defaults, update propagation, delete/recreate, reconnect
catch-up (via a watch reactor whose first watcher is killed), slow
subscriber convergence, independent keys, CloseWatch/Close semantics,
and the sql.Open end-to-end path. The module joins go.work, the
Makefile module loop (and therefore CI), and the release ordering docs.
@daniel-garcia
daniel-garcia deleted the feat/k8ssecret-strategy branch June 11, 2026 13:42
daniel-garcia added a commit that referenced this pull request Jul 5, 2026
Port the k8ssecret strategy idea from PR #66 onto v3: a nested module
(github.com/infobloxopen/hotload/k8ssecret, keeping client-go out of the
core) implementing hotload.Strategy by watching a Kubernetes Secret
through the API server, for deployments that need credentials from a
Secret in another namespace, which Kubernetes cannot mount as a volume.

DSN format matches the PR: k8ssecret://<driver>/<secret>?namespace=<ns>&dsn=<key>,
with the namespace defaulting to the pod's own and the key to dsn.txt.

Beyond the port, this fixes four defects in the PR #66 implementation:
- The hotload core passes uri.Path, so the secret name arrives with a
  leading slash; the PR used it verbatim and would look up a secret
  named "/myapp-db" (its tests called Watch directly with bare names,
  masking this). Covered here by an end-to-end test through sql.Open
  with a minimal fake driver.
- Updates could be lost in the gap between the initial Get and the
  watch start, and while a dropped watch waited to reconnect (only
  Modified events were handled, watching from "now"). Every
  (re)connect now re-reads the Secret, delivers missed changes, and
  watches from that read's resource version; Added events (recreated
  secrets, post-reconnect snapshots) are handled too.
- Slow subscribers had the newest value dropped when their buffer was
  full, leaving them permanently stale; delivery now drops the oldest
  queued value so subscribers converge on the latest.
- Watches were keyed by namespace/name only, so two DSNs reading
  different data keys of the same Secret shared one value; the data key
  is now part of the watch identity.

Tests use client-go's fake clientset: initial fetch, missing
secret/key, defaults, update propagation, delete/recreate, reconnect
catch-up (via a watch reactor whose first watcher is killed), slow
subscriber convergence, independent keys, CloseWatch/Close semantics,
and the sql.Open end-to-end path. The module joins go.work, the
Makefile module loop (and therefore CI), and the release ordering docs.
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.

1 participant