Kubernetes secrets resolver

Config resolver that swaps kubernetes-secret: placeholders for live values fetched from the Kubernetes API.

Overview

Piko's config loader resolves <prefix>:<value> placeholder strings against any registered resolver. The package handles the kubernetes-secret: prefix. The body is [namespace/]secret-name#key. The resolver reads the named Secret over client-go and substitutes the value of the requested data key. The data key after # is mandatory. Because a Secret holds multiple keys, the #key suffix selects one.

The resolver is pure Go. It needs no CGO, no system libraries, and no build tags. It does pull in the client-go and apimachinery dependency tree, which is heavier than the other secret backends. This resolver is available in compiled builds only. It is not registered in the interpreted symbol set, so the kubernetes-secret: prefix is unavailable in interpreted dev mode.

A single NewResolver() call auto-detects in-cluster versus out-of-cluster mode and the default namespace, so the bootstrap carries no auth glue. When running inside a pod with a service account token, it uses rest.InClusterConfig() and reads the namespace from /var/run/secrets/kubernetes.io/serviceaccount/namespace. When running outside the cluster, it falls back to $KUBECONFIG or ~/.kube/config and uses the default namespace. The namespace file supplies the default when present. Every reference can override the default with a namespace/ prefix.

Requirements

  • One of: a service account mounted into the pod (in-cluster) or a kubeconfig at $KUBECONFIG / ~/.kube/config (out-of-cluster).
  • RBAC get on secrets in the target namespaces. For cross-namespace reads, the binding must cover those namespaces.
  • Network connectivity to the Kubernetes API server.

Configuration

NewResolver takes no arguments, auth and namespace detection happen automatically.

import "piko.sh/piko/wdk/config/config_resolver_kubernetes"

resolver, err := config_resolver_kubernetes.NewResolver()
if err != nil {
    return err
}

Reference secrets in your config struct using the kubernetes-secret: prefix. Always include a #key suffix because Kubernetes Secrets are key/value maps:

database:
  password: kubernetes-secret:db-credentials#password   # implicit namespace
stripe:
  secret_key: kubernetes-secret:billing/api-keys#stripe # explicit namespace

Bootstrap

import (
    "piko.sh/piko/piko"
    "piko.sh/piko/wdk/config/config_resolver_kubernetes"
)

resolver, err := config_resolver_kubernetes.NewResolver()
if err != nil {
    return err
}

ssr := piko.New(
    piko.WithConfigResolvers(resolver),
)

The resolver satisfies the same config.Resolver port as every sibling secret backend, and WithConfigResolvers accepts any of them. Swapping or combining backends needs no change beyond the constructor.

The package also exports a Register() shortcut. It constructs a resolver and registers it in the global resolver registry, which suits an init() function and needs no bootstrap wiring:

func init() {
    if err := config_resolver_kubernetes.Register(); err != nil {
        log.Fatal(err)
    }
}

See also

Sibling resolvers:

Companion provider:

Framework docs:

External: