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
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user