fe5fbdaf6d
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
67 lines
2.0 KiB
Go
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
|
|
}
|