// 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 }