package bind import ( "context" "fmt" "strings" ) // TSIGCreds carries the material needed to authenticate a dynamic update. type TSIGCreds struct { Name string // TSIG key name Algorithm string // e.g. hmac-sha256 Secret string // base64-encoded key } // RecordUpdate describes a desired record set to apply to a zone. type RecordUpdate struct { FQDN string // fully-qualified owner name, trailing dot recommended Type string // RR type TTL int32 // record TTL Values []string // RDATA entries Delete bool // when true, delete the RRset instead of replacing it } // NSUpdate applies a set of record changes to zone by executing nsupdate on the // primary pod, targeting the local server and authenticating with creds. All // changes are sent in a single atomic transaction. func (e *Executor) NSUpdate(ctx context.Context, namespace, pod, zone string, creds TSIGCreds, updates []RecordUpdate) error { var b strings.Builder b.WriteString("server 127.0.0.1\n") b.WriteString(fmt.Sprintf("zone %s\n", dot(zone))) for _, u := range updates { // Replace semantics: clear the RRset first, then add the desired values. b.WriteString(fmt.Sprintf("update delete %s %s\n", dot(u.FQDN), u.Type)) if u.Delete { continue } for _, v := range u.Values { b.WriteString(fmt.Sprintf("update add %s %d %s %s\n", dot(u.FQDN), u.TTL, u.Type, v)) } } b.WriteString("send\n") cmd := []string{"nsupdate", "-y", fmt.Sprintf("%s:%s:%s", creds.Algorithm, creds.Name, creds.Secret)} if out, err := e.Exec(ctx, namespace, pod, cmd, b.String()); err != nil { return fmt.Errorf("nsupdate zone %s: %w (out: %s)", zone, err, out) } return nil } // dot ensures a name is fully qualified with a trailing dot. func dot(name string) string { if name == "" || name == "@" { return "@" } if strings.HasSuffix(name, ".") { return name } return name + "." }