Files
bind-operator/internal/bind/exec.go
T
unkinben fe5fbdaf6d Initial bind-operator: 9 CRDs + controllers
Implements a Kubernetes operator that manages fleets of BIND9 servers
declaratively, using controller-runtime (matching forgebot conventions).

- add BindCluster reconciler: StatefulSet (pod-0 primary, secondaries),
  headless + client Services, rendered named.conf ConfigMap, TSIG keys
  Secret and rndc control Secret; watches dependent CRs to re-render
- add BindTSIGKey reconciler that generates key material into a Secret
- add BindZone/DNSRecord reconcilers using fully-dynamic delivery
  (rndc addzone + TSIG nsupdate against the primary pod)
- add BindCatalogZone reconciler so secondaries auto-provision zones
- add BindPolicy (RPZ), BindDNSSECPolicy, BindView, BindACL reconcilers
- render primary/secondary named.conf variants selected by pod ordinal
- generate CRDs, deepcopy and RBAC; add samples mapping the three Puppet
  roles (authoritative/resolver/external-dns) to three BindClusters
- add Makefile, Dockerfile.operator, Woodpecker CI and kind manifests
2026-07-03 15:48:13 +10:00

67 lines
2.0 KiB
Go

// Package bind contains helpers for driving BIND9 pods: executing rndc and
// nsupdate over the Kubernetes exec subresource, and rendering named.conf.
package bind
import (
"bytes"
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
// ContainerName is the BIND container name within each pod.
const ContainerName = "bind"
// Executor runs commands inside BIND pods via the exec subresource.
type Executor struct {
config *rest.Config
clientset kubernetes.Interface
}
// NewExecutor builds an Executor from a controller-runtime rest config.
func NewExecutor(cfg *rest.Config) (*Executor, error) {
cs, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("build clientset: %w", err)
}
return &Executor{config: cfg, clientset: cs}, nil
}
// Exec runs command in the BIND container of pod, optionally feeding stdin, and
// returns stdout. A non-zero exit or transport error yields an error that
// includes stderr.
func (e *Executor) Exec(ctx context.Context, namespace, pod string, command []string, stdin string) (string, error) {
req := e.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(pod).
Namespace(namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: ContainerName,
Command: command,
Stdin: stdin != "",
Stdout: true,
Stderr: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(e.config, "POST", req.URL())
if err != nil {
return "", fmt.Errorf("spdy executor: %w", err)
}
var stdout, stderr bytes.Buffer
opts := remotecommand.StreamOptions{Stdout: &stdout, Stderr: &stderr}
if stdin != "" {
opts.Stdin = bytes.NewBufferString(stdin)
}
if err := exec.StreamWithContext(ctx, opts); err != nil {
return stdout.String(), fmt.Errorf("exec %v: %w (stderr: %s)", command, err, stderr.String())
}
return stdout.String(), nil
}