Documentation
¶
Overview ¶
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2026 Johan Stenstam, [email protected] * * CLI commands for inspecting agent SDE (SynchedDataEngine) status. * Provides "agent zone edits list [--zone {zone}]" with per-RR * tracking state and outbound queue status.
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2025 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2026 Johan Stenstam, [email protected] * * CLI commands for managing combiner edit approval workflow. * Provides "combiner zone edits {list|approve|reject|clear}".
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) 2026 Johan Stenstam, [email protected] * * Distribution management CLI commands for agents and combiners
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Johan Stenstam
* Copyright (c) 2026 Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2024 Johan Stenstam, [email protected]
* Copyright (c) 2026 Johan Stenstam, [email protected] * * Transaction diagnostic CLI commands for agents and combiners. * Provides visibility into open outgoing/incoming transactions and error history.
* Copyright (c) Johan Stenstam, [email protected]
* Johan Stenstam
* Copyright (c) Johan Stenstam, [email protected]
* Copyright (c) Johan Stenstam, [email protected]
Index ¶
- Constants
- Variables
- func CreateUpdate(updateType string)
- func DnssecGenDS() error
- func DnssecKeyMgmt(cmd string) error
- func ListMPZones(cr tdns.ZoneResponse)
- func ListZones(cr tdns.ZoneResponse)
- func PrepArgs(args ...interface{})
- func PrintAgent(agent *tdns.Agent, showZones bool) error
- func PrintCacheItem(item core.Tuple[string, cache.CachedRRset], suffix string)
- func PrintHsyncRRs(agentid tdns.AgentId, rrs []string)
- func PrintUpdateResult(ur tdns.UpdateResult)
- func ReadZoneData(zonename string) error
- func SendAgentDebugCmd(req tdns.AgentMgmtPost, printJson bool) (*tdns.AgentMgmtResponse, error)
- func SendAgentMgmtCmd(req *tdns.AgentMgmtPost, prefix string) (*tdns.AgentMgmtResponse, error)
- func SendAuthPeerCmd(req tdns.AuthPeerPost) (*tdns.AuthPeerResponse, error)
- func SendCatalogCommand(api *tdns.ApiClient, data tdns.CatalogPost) (*tdns.CatalogResponse, error)
- func SendCombinerDebugCmd(req tdns.CombinerDebugPost) (*tdns.CombinerDebugResponse, error)
- func SendCombinerEditCmd(req tdns.CombinerEditPost) (*tdns.CombinerEditResponse, error)
- func SendCommand(cmd, zone string) (string, error)
- func SendCommandNG(api *tdns.ApiClient, data tdns.CommandPost) (tdns.CommandResponse, error)
- func SendConfigCommand(api *tdns.ApiClient, data tdns.ConfigPost) (tdns.ConfigResponse, error)
- func SendDebug(api *tdns.ApiClient, data tdns.DebugPost) tdns.DebugResponse
- func SendDelegationCmd(api *tdns.ApiClient, data tdns.DelegationPost) (tdns.DelegationResponse, error)
- func SendDsyncCommand(api *tdns.ApiClient, data tdns.ZoneDsyncPost) (tdns.ZoneDsyncResponse, error)
- func SendKeystoreCmd(api *tdns.ApiClient, data tdns.KeystorePost) (tdns.KeystoreResponse, error)
- func SendNotify(zonename string, ntype string)
- func SendTruststore(api *tdns.ApiClient, data tdns.TruststorePost) (tdns.TruststoreResponse, error)
- func SendZoneCommand(api *tdns.ApiClient, data tdns.ZonePost) (tdns.ZoneResponse, error)
- func SetRootCommand(cmd *cobra.Command)
- func Sig0KeyMgmt(cmd string) error
- func Sig0TrustMgmt(subcommand string) error
- func StartImrForCli(rootHints string) (context.Context, context.CancelFunc, *tdns.Imr, error)
- func StartInteractiveMode()
- func Terminate()
- func ValidateBySection(config *Config, configsections map[string]interface{}, cfgfile string) error
- func ValidateConfig(v *viper.Viper, cfgfile string) error
- func ValidateZoneConfig(v *viper.Viper, cfgfile string) error
- func VerboseListZone(cr tdns.ZoneResponse)
- func VerifyAndSendLocalDNSRecord(zonename, dnsRecord, cmd string) error
- type CommandNode
- type Config
Constants ¶
const DefaultDomainSuffix = "example.com."
Default domain suffix for CLI
Variables ¶
var AgentCmd = &cobra.Command{
Use: "agent",
Short: "TDNS Agent commands",
}
var AgentDistribCmd = &cobra.Command{
Use: "distrib",
Short: "Manage distributions",
Long: `Commands for managing distributions created by this agent, including listing distributions and purging completed distributions.`,
}
var AgentTransactionCmd = &cobra.Command{
Use: "transaction",
Short: "Transaction diagnostics",
Long: `Commands for diagnosing open transactions, pending confirmations, and errors.`,
}
var AgentZoneCmd = &cobra.Command{
Use: "zone",
Short: "Agent zone management commands",
}
AgentZoneCmd is the agent-specific "zone" command group. It contains only the zone subcommands relevant to the agent, plus the new addrr/delrr commands for managing synced RRs.
var AuthCmd = &cobra.Command{
Use: "auth",
Short: "Interact with tdns-auth (authoritative) via API",
}
AuthCmd is the parent command for all auth-related commands
var Base32Cmd = &cobra.Command{
Use: "base32",
Short: "Convert data to/from base32 encoding and domain format",
Long: `This command converts data to or from base32 encoding and domain format.
It can read from standard input or from a file.
Examples:
echo '{"name":"example","value":123}' | tdns base32 encode --suffix=example.com.
cat domains.txt | tdns base32 decode`,
}
base32Cmd represents the base32 command
var Base32decodeCmd = &cobra.Command{ Use: "decode", Short: "Decode base32 domain data to JSON", Long: `Decode base32 domain data from stdin to JSON.`, Run: func(cmd *cobra.Command, args []string) { stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) != 0 { fmt.Println("No stdin data provided. Please pipe domain data to this command.") fmt.Println("Example: cat domains.txt | tdns base32 decode") return } cookie, _ := cmd.Flags().GetString("cookie") reader := bufio.NewReader(os.Stdin) processBase32DomainsToJson(reader, cmd, cookie) }, }
decodeCmd represents the decode subcommand
var Base32encodeCmd = &cobra.Command{ Use: "encode", Short: "Encode JSON data to base32 domain format", Long: `Encode JSON data from stdin to base32 domain format.`, Run: func(cmd *cobra.Command, args []string) { stat, _ := os.Stdin.Stat() if (stat.Mode() & os.ModeCharDevice) != 0 { fmt.Println("No stdin data provided. Please pipe JSON data to this command.") fmt.Println("Example: echo '{\"name\":\"example\"}' | tdns base32 encode --suffix=example.com.") return } suffix, _ := cmd.Flags().GetString("suffix") cookie, _ := cmd.Flags().GetString("cookie") if suffix == "" { fmt.Fprintf(os.Stderr, "Error: domain suffix is required. Use --suffix flag.\n") return } if !strings.HasSuffix(suffix, ".") { suffix += "." fmt.Fprintf(os.Stderr, "Note: Added trailing dot to domain suffix: %s\n", suffix) } reader := bufio.NewReader(os.Stdin) processJsonToBase32Domains(reader, suffix, cookie) }, }
encodeCmd represents the encode subcommand
var CatalogCmd = &cobra.Command{
Use: "catalog",
Short: "Manage catalog zones (RFC 9432)",
Long: `Create and manage catalog zones, add/remove member zones and groups.`,
}
CatalogCmd is the root command for catalog zone management
var CatalogGroupCmd = &cobra.Command{
Use: "group",
Short: "Manage groups in catalog",
}
CatalogGroupCmd is the subcommand group for group operations
var CatalogNotifyCmd = &cobra.Command{
Use: "notify",
Short: "Manage notify addresses for catalog zones",
}
CatalogNotifyCmd is the subcommand group for notify address operations
var CatalogZoneCmd = &cobra.Command{
Use: "zone",
Short: "Manage member zones in catalog",
}
CatalogZoneCmd is the subcommand group for zone operations
var CatalogZoneGroupCmd = &cobra.Command{
Use: "group",
Short: "Manage group associations for zones",
}
CatalogZoneGroupCmd is the subcommand group for zone-group associations
var ChildCmd = &cobra.Command{
Use: "child",
Short: "Prefix, only useable via the 'child update create' subcommand",
}
var CombinerCmd = &cobra.Command{
Use: "combiner",
Short: "TDNS Combiner commands",
}
var CombinerDistribCmd = &cobra.Command{
Use: "distrib",
Short: "Manage distributions",
Long: `Commands for managing distributions created by this combiner, including listing distributions and purging completed distributions.`,
}
var CombinerShowDataCmd = &cobra.Command{ Use: "show-combiner-data", Short: "Show the combiner's local data store (merged + per-agent)", Long: `Display the combiner's CombinerData (merged view) and AgentContributions (per-agent breakdown) for all zones or a specific zone. Example: tdns-cliv2 combiner show-combiner-data tdns-cliv2 combiner show-combiner-data --zone whisky.dnslab.`, Run: func(cmd *cobra.Command, args []string) { zone, _ := cmd.Flags().GetString("zone") resp, err := SendCombinerDebugCmd(tdns.CombinerDebugPost{ Command: "show-combiner-data", Zone: zone, }) if err != nil { log.Fatalf("Error: %v", err) } if resp.Error { log.Fatalf("Error: %s", resp.ErrorMsg) } hasMerged := len(resp.CombinerData) > 0 hasContribs := len(resp.AgentContributions) > 0 if !hasMerged && !hasContribs { fmt.Printf("No combiner data stored\n") return } if hasContribs { fmt.Printf("Per-Agent Contributions\n") fmt.Printf("=======================\n\n") zones := sortedKeys(resp.AgentContributions) for _, zoneName := range zones { agentMap := resp.AgentContributions[zoneName] fmt.Printf("Zone: %s\n", zoneName) fmt.Printf("────────────────────────────────────────\n") agents := sortedKeys(agentMap) for _, agentID := range agents { ownerMap := agentMap[agentID] fmt.Printf(" Agent: %s\n", agentID) owners := sortedKeys(ownerMap) for _, owner := range owners { rrTypeMap := ownerMap[owner] rrTypes := sortedKeys(rrTypeMap) for _, rrTypeName := range rrTypes { rrs := rrTypeMap[rrTypeName] fmt.Printf(" %s %s (%d records):\n", owner, rrTypeName, len(rrs)) for _, rr := range rrs { fmt.Printf(" %s\n", rr) } } } } fmt.Printf("\n") } } if hasMerged { fmt.Printf("Merged CombinerData\n") fmt.Printf("===================\n\n") zones := sortedKeys(resp.CombinerData) for _, zoneName := range zones { ownerMap := resp.CombinerData[zoneName] fmt.Printf("Zone: %s\n", zoneName) fmt.Printf("────────────────────────────────────────\n") owners := sortedKeys(ownerMap) for _, ownerName := range owners { rrTypeMap := ownerMap[ownerName] rrTypes := sortedKeys(rrTypeMap) for _, rrTypeName := range rrTypes { rrs := rrTypeMap[rrTypeName] fmt.Printf(" %s %s (%d records):\n", ownerName, rrTypeName, len(rrs)) for _, rr := range rrs { fmt.Printf(" %s\n", rr) } } } fmt.Printf("\n") } } }, }
var CombinerTransactionCmd = &cobra.Command{
Use: "transaction",
Short: "Transaction diagnostics",
Long: `Commands for diagnosing transaction errors on the combiner.`,
}
var Conf tdns.Config
var ConfigCmd = &cobra.Command{
Use: "config",
Short: "Commands to reload config, reload zones, etc",
}
var DaemonApiCmd = &cobra.Command{ Use: "api", Short: "request a statusd api summary", Long: `The daemon api queries the statusd for the provided API and prints that out in a (hopefully) comprehensible fashion.`, Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) if len(args) != 0 { log.Fatal("api must have no arguments") } api.ShowApi() }, }
var DaemonCmd = &cobra.Command{
Use: "daemon",
Short: "Only useful via sub-commands",
}
var DaemonReloadCmd = &cobra.Command{ Use: "reload", Short: "Reload config from file", Long: `Reload config from file (the assumption is that something in the config has changed). Right now this doesn't do much, but later on various services will be able to restart.`, Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) _, resp, _ := api.UpdateDaemon(tdns.CommandPost{Command: "reload"}, true) fmt.Printf("Reload: %s Message: %s\n", resp.Status, resp.Msg) }, }
var DaemonRestartCmd = &cobra.Command{ Use: "restart", Short: "Stop and then start the management daemon", Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) api.StopDaemon() time.Sleep(4 * time.Second) if clearLogFile != "" { if f, err := os.OpenFile(clearLogFile, os.O_WRONLY|os.O_TRUNC, 0); err != nil { fmt.Printf("Warning: could not truncate log file %q: %v\n", clearLogFile, err) } else { f.Close() fmt.Printf("Log file truncated: %s\n", clearLogFile) } } maxwait := viper.GetInt("cli.maxwait") if maxwait < MaxWait { maxwait = MaxWait } clientKey := getClientKeyFromParent(prefixcmd) if clientKey == "" { clientKey = "tdns-auth" } daemonCommand := "" if apiDetails := getApiDetailsByClientKey(clientKey); apiDetails != nil { if cmdStr, ok := apiDetails["command"].(string); ok && cmdStr != "" { daemonCommand = cmdStr } } if daemonCommand == "" { daemonCommand = viper.GetString("common.command") } if updateBinary { dstbin := daemonCommand if dstbin == "" { fmt.Printf("Update binary: destination unspecified (key: apiservers[].command or common.command)\n") os.Exit(1) } srcbin := filepath.Join(os.TempDir(), filepath.Base(dstbin)) dstat, err := os.Stat(dstbin) if err != nil { fmt.Printf("Error from stat(dst: %q): %v\n", dstbin, err) os.Exit(1) } if tdns.Globals.Debug { fmt.Printf("ModTime(%s): %v\n", dstbin, dstat.ModTime()) } sstat, err := os.Stat(srcbin) if err != nil { fmt.Printf("Error from stat(src: %q): %v\n", srcbin, err) os.Exit(1) } if tdns.Globals.Debug { fmt.Printf("ModTime(%s): %v\n", srcbin, sstat.ModTime()) } if sstat.ModTime().After(dstat.ModTime()) { fmt.Printf("%s is newer than %s. Will update installed binary.\n", srcbin, dstbin) n, err := tdns.CopyFile(srcbin, dstbin) if err != nil { fmt.Printf("Error copying %s to %s: %v\n", srcbin, dstbin, err) os.Exit(1) } fmt.Printf("Successfully copied %s to %s (%d bytes)\n", srcbin, dstbin, n) } else { fmt.Printf("%s is not newer than %s. No update.\n", srcbin, dstbin) } } daemonFlags := extractDaemonFlags(cmd) api.StartDaemon(maxwait, false, daemonCommand, daemonFlags) }, }
var DaemonStartCmd = &cobra.Command{ Use: "start", Short: "Start the axfr-statusd daemon", Long: `Start the axfr-statusd daemon. If it was already running, then this is a no-op.`, Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) maxwait := viper.GetInt("cli.maxwait") if maxwait < MaxWait { maxwait = MaxWait } clientKey := getClientKeyFromParent(prefixcmd) if clientKey == "" { clientKey = "tdns-auth" } daemonCommand := "" if apiDetails := getApiDetailsByClientKey(clientKey); apiDetails != nil { if cmdStr, ok := apiDetails["command"].(string); ok && cmdStr != "" { daemonCommand = cmdStr } } daemonFlags := extractDaemonFlags(cmd) api.StartDaemon(maxwait, tdns.Globals.Slurp, daemonCommand, daemonFlags) }, }
var DaemonStatusCmd = &cobra.Command{ Use: "status", Short: "Query for the status of the management daemon", Long: `Query for the status of the management daemon`, Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) _, resp, _ := api.UpdateDaemon(tdns.CommandPost{Command: "status"}, true) fmt.Printf("Status: %s Message: %s\n", resp.Status, resp.Msg) }, }
var DaemonStopCmd = &cobra.Command{ Use: "stop", Short: "Stop the management daemon", Long: `Stop the management daemon. If it was not running, then this is a no-op.`, Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("daemon") api, _ := getApiClient(prefixcmd, true) api.StopDaemon() }, }
var DbCmd = &cobra.Command{
Use: "db",
Short: "TDNS DB commands",
}
var DdnsCmd = &cobra.Command{
Use: "ddns",
Short: "Send a DDNS update. Only usable via sub-commands.",
}
var DebugAgentCmd = &cobra.Command{
Use: "debug",
Short: "TDNS-AGENT debugging commands",
}
var DebugAgentDumpAgentRegistryCmd = &cobra.Command{ Use: "dump-agentregistry", Short: "Dump the agent registry", Run: func(cmd *cobra.Command, args []string) { req := tdns.AgentMgmtPost{ Command: "dump-agentregistry", } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } if len(amr.AgentRegistry.RegularS) == 0 { fmt.Printf("No agent registry data in response from agent %q", amr.Identity) os.Exit(1) } if len(amr.AgentRegistry.RegularS) > 0 { var agentNames []tdns.AgentId for _, agent := range amr.AgentRegistry.RegularS { agentNames = append(agentNames, agent.Identity) } fmt.Printf("Agent registry contains %d agents: %v\n", len(agentNames), agentNames) for _, agent := range amr.AgentRegistry.RegularS { err := PrintAgent(agent, false) if err != nil { log.Printf("Error printing agent: %v", err) } fmt.Println() } } else { fmt.Printf("No remote agents found in the agent registry data from agent %q", amr.Identity) } }, }
var DebugAgentDumpZoneDataRepoCmd = &cobra.Command{ Use: "dump-zonedatarepo", Short: "Dump the zone data repo (deprecated: use show-synced-data)", Hidden: true, Run: DebugAgentShowSyncedDataCmd.Run, }
Keep the old command name as alias for compatibility
var DebugAgentHsyncCmd = &cobra.Command{
Use: "hsync",
Short: "HSYNC debugging commands",
}
Debug agent subcommand for HSYNC
var DebugAgentQueueStatusCmd = &cobra.Command{ Use: "queue-status", Short: "Show reliable message queue status and pending messages", Run: func(cmd *cobra.Command, args []string) { req := tdns.AgentMgmtPost{ Command: "queue-status", } _, buf, err := func() (*tdns.AgentMgmtResponse, []byte, error) { prefixcmd, _ := getCommandContext("debug") api, err := getApiClient(prefixcmd, true) if err != nil { log.Fatalf("Error getting API client: %v", err) } _, buf, err := api.RequestNG("POST", "/agent/debug", req, true) return nil, buf, err }() if err != nil { log.Fatalf("API request failed: %v", err) } var resp map[string]interface{} if err := json.Unmarshal(buf, &resp); err != nil { log.Fatalf("Failed to parse response: %v", err) } if errVal, ok := resp["error"].(bool); ok && errVal { if errMsg, ok := resp["error_msg"].(string); ok { log.Fatalf("Error: %s", errMsg) } log.Fatalf("Error in response") } if msg, ok := resp["msg"].(string); ok && msg != "" { fmt.Println(msg) } data, ok := resp["data"].(map[string]interface{}) if !ok { fmt.Println("No queue data available") return } if stats, ok := data["stats"].(map[string]interface{}); ok { fmt.Println("\nQueue Statistics:") if v, ok := stats["total_pending"].(float64); ok { fmt.Printf(" Pending: %d\n", int(v)) } if v, ok := stats["total_delivered"].(float64); ok { fmt.Printf(" Delivered: %d\n", int(v)) } if v, ok := stats["total_failed"].(float64); ok { fmt.Printf(" Failed: %d\n", int(v)) } if v, ok := stats["total_expired"].(float64); ok { fmt.Printf(" Expired: %d\n", int(v)) } if byState, ok := stats["by_state"].(map[string]interface{}); ok && len(byState) > 0 { fmt.Printf(" By state: ") first := true for state, count := range byState { if !first { fmt.Printf(", ") } fmt.Printf("%s=%d", state, int(count.(float64))) first = false } fmt.Println() } } messages, ok := data["messages"].([]interface{}) if !ok || len(messages) == 0 { fmt.Println("\nNo pending messages") return } fmt.Printf("\nPending Messages (%d):\n", len(messages)) verbose := false if v, err := cmd.Flags().GetBool("verbose"); err == nil { verbose = v } if verbose { for _, mRaw := range messages { m, ok := mRaw.(map[string]interface{}) if !ok { continue } fmt.Println() fmt.Printf(" Distribution ID: %s\n", getStringValue(m, "distribution_id")) fmt.Printf(" Recipient: %s (%s)\n", getStringValue(m, "recipient_id"), getStringValue(m, "recipient_type")) fmt.Printf(" Zone: %s\n", getStringValue(m, "zone")) fmt.Printf(" State: %s\n", getStringValue(m, "state")) fmt.Printf(" Priority: %s\n", getStringValue(m, "priority")) fmt.Printf(" Attempts: %s\n", getStringValue(m, "attempt_count")) fmt.Printf(" Age: %s\n", getStringValue(m, "age")) fmt.Printf(" Created: %s\n", getStringValue(m, "created_at")) fmt.Printf(" Expires: %s\n", getStringValue(m, "expires_at")) fmt.Printf(" Next attempt: %s\n", getStringValue(m, "next_attempt")) if lastAttempt := getStringValue(m, "last_attempt"); lastAttempt != "" { fmt.Printf(" Last attempt: %s\n", lastAttempt) } if lastErr := getStringValue(m, "last_error"); lastErr != "" { fmt.Printf(" Last error: %s\n", lastErr) } } } else { var rows []string rows = append(rows, "DistID | Recipient | Type | Zone | State | Attempts | Age | Error") for _, mRaw := range messages { m, ok := mRaw.(map[string]interface{}) if !ok { continue } distID := getStringValue(m, "distribution_id") if len(distID) > 16 { distID = distID[:16] + "..." } lastErr := getStringValue(m, "last_error") if len(lastErr) > 30 { lastErr = lastErr[:30] + "..." } attempts := "0" if v, ok := m["attempt_count"].(float64); ok { attempts = fmt.Sprintf("%d", int(v)) } rows = append(rows, fmt.Sprintf("%s | %s | %s | %s | %s | %s | %s | %s", distID, getStringValue(m, "recipient_id"), getStringValue(m, "recipient_type"), getStringValue(m, "zone"), getStringValue(m, "state"), attempts, getStringValue(m, "age"), lastErr, )) } if len(rows) > 1 { output := columnize.SimpleFormat(rows) fmt.Println(output) } } }, }
var DebugAgentRegistryCmd = &cobra.Command{ Use: "agentregistry", Short: "Test the agent registry", Run: func(cmd *cobra.Command, args []string) { conf := tdns.Config{ MultiProvider: &tdns.MultiProviderConf{ Identity: "local", }, } ar := conf.NewAgentRegistry() ar.LocateInterval = 10 ar.S.Set("local", &tdns.Agent{ Identity: "local", }) ar.AddRemoteAgent("agent.example.com", &tdns.Agent{ Identity: "agent.example.com", }) ar.AddRemoteAgent("agent.example.org", &tdns.Agent{ Identity: "agent.example.org", }) fmt.Printf("Agent registry:\ntype=%T\n", ar.S) fmt.Printf("Agent registry:\n%d shards\n", ar.S.NumShards()) for item := range ar.S.IterBuffered() { fmt.Printf("Agent registry:\n%s\n", item.Key) fmt.Printf("Agent registry:\n%+v\n", item.Val) } }, }
var DebugAgentResyncCmd = &cobra.Command{ Use: "resync", Short: "Re-send all local changes to combiner and remote agents", Long: `Re-send all locally stored synced data for a zone to the combiner and all remote agents. Use this when the combiner or remote agents have lost state and need to be brought back in sync. Example: tdns-cliv2 agent debug resync --zone whisky.dnslab.`, Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") req := tdns.AgentMgmtPost{ Command: "resync", Zone: tdns.ZoneName(tdns.Globals.Zonename), } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } fmt.Printf("Resync for zone %s:\n", tdns.Globals.Zonename) if amr.Msg != "" { fmt.Printf(" %s\n", amr.Msg) } }, }
var DebugAgentSendNotifyCmd = &cobra.Command{ Use: "send-notify", Short: "Tell agent to send a NOTIFY message to the other agents", Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename", "identity") notifyRRtype = strings.ToUpper(notifyRRtype) if notifyRRtype != "NS" && notifyRRtype != "DNSKEY" { log.Fatalf("Error: RR type must be either NS or DNSKEY (is %q)", notifyRRtype) } if dnsRecord == "" { log.Fatalf("Error: DNS record is required") } var rr dns.RR var err error if rr, err = dns.NewRR(dnsRecord); err != nil { log.Fatalf("Error: Invalid DNS record (did not parse): %v", err) } rrs := []string{rr.String()} rrtype := dns.StringToType[notifyRRtype] if rrtype == 0 { log.Fatalf("Error: Invalid RR type: %s", notifyRRtype) } req := tdns.AgentMgmtPost{ Command: "send-notify", MessageType: tdns.AgentMsgNotify, RRType: rrtype, Zone: tdns.ZoneName(tdns.Globals.Zonename), AgentId: tdns.Globals.AgentId, RRs: rrs, } _, err = SendAgentDebugCmd(req, true) if err != nil { log.Fatalf("Error: %v", err) } }, }
var DebugAgentSendRfiCmd = &cobra.Command{ Use: "send-rfi", Short: "Tell agent to send an RFI message to another agent", Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename", "identity") rfitype = strings.ToUpper(rfitype) validRfiTypes := map[string]bool{"CONFIG": true, "SYNC": true, "AUDIT": true, "EDITS": true} if !validRfiTypes[rfitype] { log.Fatalf("Error: RFI type must be one of CONFIG, SYNC, AUDIT, or EDITS (is %q)", rfitype) } if rfitype == "CONFIG" && rfisubtype == "" { log.Fatalf("Error: CONFIG RFI requires --subtype (upstream, downstream, sig0key)") } rfisubtype = strings.ToLower(rfisubtype) req := tdns.AgentMgmtPost{ Command: "send-rfi", MessageType: tdns.AgentMsgRfi, RfiType: rfitype, RfiSubtype: rfisubtype, Zone: tdns.ZoneName(tdns.Globals.Zonename), AgentId: tdns.Globals.AgentId, } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } fmt.Printf("Result from %s RFI message sent to agent %q:\n", amr.RfiType, amr.Identity) if amr.Msg != "" { fmt.Printf("%s\n", amr.Msg) } if len(amr.RfiResponse) > 0 { switch { case rfitype == "CONFIG" && (rfisubtype == "upstream" || rfisubtype == "downstream"): var out []string if tdns.Globals.ShowHeaders { out = append(out, "Zone|Provider|Where|XFR src|XFR dst|XFR auth") } for aid, rfidata := range amr.RfiResponse { if rfidata.Error { fmt.Printf(" %s: error: %s\n", aid, rfidata.ErrorMsg) continue } if len(rfidata.ZoneXfrSrcs) > 0 { out = append(out, fmt.Sprintf("%s|%s|upstream|%v|%v|%v", tdns.Globals.Zonename, aid, rfidata.ZoneXfrSrcs, "", rfidata.ZoneXfrAuth)) } if len(rfidata.ZoneXfrDsts) > 0 { out = append(out, fmt.Sprintf("%s|%s|downstream|%v|%v|%v", tdns.Globals.Zonename, aid, "", rfidata.ZoneXfrDsts, rfidata.ZoneXfrAuth)) } } if len(out) > 0 { fmt.Printf("%s\n", columnize.SimpleFormat(out)) } case rfitype == "CONFIG": for aid, rfidata := range amr.RfiResponse { if rfidata.Error { fmt.Printf(" %s: error: %s\n", aid, rfidata.ErrorMsg) } else { fmt.Printf(" %s: %s\n", aid, rfidata.Msg) for k, v := range rfidata.ConfigData { fmt.Printf(" %s: %s\n", k, v) } } } case rfitype == "SYNC": for aid, rfidata := range amr.RfiResponse { if rfidata.Error { fmt.Printf(" %s: error: %s\n", aid, rfidata.ErrorMsg) } else { fmt.Printf(" %s: %s\n", aid, rfidata.Msg) } } case rfitype == "AUDIT": for aid, rfidata := range amr.RfiResponse { if rfidata.Error { fmt.Printf(" %s: error: %s\n", aid, rfidata.ErrorMsg) } else { fmt.Printf(" %s: %s\n", aid, rfidata.Msg) if rfidata.AuditData != nil { auditJSON, err := json.MarshalIndent(rfidata.AuditData, " ", " ") if err != nil { fmt.Printf(" Error formatting audit data: %v\n", err) } else { fmt.Printf(" Audit data:\n %s\n", string(auditJSON)) } } } } case rfitype == "EDITS": for aid, rfidata := range amr.RfiResponse { if rfidata.Error { fmt.Printf(" %s: error: %s\n", aid, rfidata.ErrorMsg) } else { fmt.Printf(" %s: %s\n", aid, rfidata.Msg) } } } } else { fmt.Printf("No RFI data in response from agent %q\n", amr.Identity) } }, }
var DebugAgentSendSyncToCmd = &cobra.Command{ Use: "send-sync-to <RR> [<RR>...]", Short: "Send a SYNC message to a remote agent (real transport)", Long: `Create and send a real SYNC message to a specified remote agent. Uses the actual transport (CHUNK NOTIFY + fallback). RRs are validated before sending. Example: tdns-cliv2 debug agent send-sync-to \ --to agent.provider-b.example.com. \ --zone example.com. \ "example.com. 3600 IN NS ns1.provider-a.example.com." \ "example.com. 3600 IN NS ns2.provider-a.example.com."`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { toAgent, _ := cmd.Flags().GetString("to") if toAgent == "" { log.Fatalf("Error: --to agent ID is required") } zone, _ := cmd.Flags().GetString("zone") if zone == "" { log.Fatalf("Error: --zone is required") } // Validate all RRs by parsing them var validRRs []string for _, rrStr := range args { rr, err := dns.NewRR(rrStr) if err != nil { log.Fatalf("Error: Invalid DNS record %q: %v", rrStr, err) } validRRs = append(validRRs, rr.String()) } req := tdns.AgentMgmtPost{ Command: "send-sync-to", Zone: tdns.ZoneName(zone), AgentId: tdns.AgentId(toAgent), RRs: validRRs, } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } fmt.Printf("SYNC sent successfully:\n") fmt.Printf(" To: %s\n", toAgent) fmt.Printf(" Zone: %s\n", zone) fmt.Printf(" Records: %d\n", len(validRRs)) fmt.Printf("\n%s\n", amr.Msg) if amr.Data != nil { if dataMap, ok := amr.Data.(map[string]interface{}); ok { if corrID, ok := dataMap["distribution_id"]; ok { fmt.Printf(" Distribution ID: %v\n", corrID) } if status, ok := dataMap["status"]; ok { fmt.Printf(" Status: %v\n", status) } } } }, }
var DebugAgentShowCombinerDataCmd = &cobra.Command{ Use: "show-combiner-data", Short: "Show combiner's local modifications store", Long: `Display the combiner's stored local modifications that are applied to zones. Data is sorted by: zone → RRtype → RRs Example: tdns-cliv2 debug agent show-combiner-data tdns-cliv2 debug agent show-combiner-data --zone example.com`, Run: func(cmd *cobra.Command, args []string) { zone, _ := cmd.Flags().GetString("zone") req := tdns.AgentMgmtPost{ Command: "show-combiner-data", } if zone != "" { req.Zone = tdns.ZoneName(zone) } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } if amr.Data == nil { fmt.Printf("No combiner data available\n") return } dataMap, ok := amr.Data.(map[string]interface{}) if !ok { fmt.Printf("Invalid combiner data format\n") return } combinerData, ok := dataMap["combiner_data"].(map[string]interface{}) if !ok || len(combinerData) == 0 { fmt.Printf("No local modifications stored in combiner\n") return } fmt.Printf("Combiner Local Modifications\n") fmt.Printf("=============================\n\n") for zoneName, ownerMapInterface := range combinerData { fmt.Printf("Zone: %s\n", zoneName) fmt.Printf("────────────────────────────────────────\n") ownerMap, ok := ownerMapInterface.(map[string]interface{}) if !ok || len(ownerMap) == 0 { fmt.Printf(" (no modifications)\n\n") continue } for ownerName, rrTypeMapInterface := range ownerMap { fmt.Printf(" Owner: %s\n", ownerName) rrTypeMap, ok := rrTypeMapInterface.(map[string]interface{}) if !ok || len(rrTypeMap) == 0 { fmt.Printf(" (no RRsets)\n") continue } for rrTypeStr, rrStringsInterface := range rrTypeMap { rrStrings, ok := rrStringsInterface.([]interface{}) if !ok { continue } fmt.Printf(" %s (%d records):\n", rrTypeStr, len(rrStrings)) for _, rrInterface := range rrStrings { if rrStr, ok := rrInterface.(string); ok { fmt.Printf(" %s\n", rrStr) } } } fmt.Printf("\n") } } }, }
var DebugAgentShowKeyInventoryCmd = &cobra.Command{ Use: "show-key-inventory", Short: "Show DNSKEY inventory received from signer (KEYSTATE)", Long: `Display the last KEYSTATE inventory received from the signer for a zone. Shows all keys reported by the signer's KeyDB with their state (created, published, standby, active, retired, foreign). Keys marked "foreign" are from other providers' signers. Example: tdns-cliv2 agent debug show-key-inventory --zone whisky.dnslab.`, Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") req := tdns.AgentMgmtPost{ Command: "show-key-inventory", Zone: tdns.ZoneName(tdns.Globals.Zonename), } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } if amr.Data == nil { fmt.Println(amr.Msg) return } dataBytes, err := json.Marshal(amr.Data) if err != nil { log.Fatalf("Failed to marshal inventory data: %v", err) } var snapshot tdns.KeyInventorySnapshot if err := json.Unmarshal(dataBytes, &snapshot); err != nil { log.Fatalf("Failed to parse inventory data: %v", err) } fmt.Printf("DNSKEY Inventory for zone %s\n", snapshot.Zone) fmt.Printf("Received: %s from %s\n", snapshot.Received.Format("2006-01-02 15:04:05"), snapshot.SenderID) fmt.Printf("────────────────────────────────────────\n") if len(snapshot.Inventory) == 0 { fmt.Printf(" (no keys)\n") return } out := []string{"KeyTag|Algorithm|Flags|State|Role|Key data"} for _, entry := range snapshot.Inventory { role := "local" if entry.State == "foreign" { role = "REMOTE" } algStr := dns.AlgorithmToString[entry.Algorithm] if algStr == "" { algStr = fmt.Sprintf("ALG%d", entry.Algorithm) } flagDesc := "ZSK" if entry.Flags&0x0001 != 0 { flagDesc = "KSK" } keyData := truncatePubKey(entry.KeyRR) out = append(out, fmt.Sprintf("%d|%s|%d (%s)|%s|%s|%s", entry.KeyTag, algStr, entry.Flags, flagDesc, entry.State, role, keyData)) } fmt.Println(columnize.SimpleFormat(out)) }, }
var DebugAgentShowSyncedDataCmd = &cobra.Command{ Use: "show-synced-data", Short: "Show synchronized data from peer agents (moved to: agent zone edits list)", Run: func(cmd *cobra.Command, args []string) { fmt.Println("This command has moved to: agent zone edits list") fmt.Println("Usage: tdns-cliv2 agent zone edits list [--zone <zone>]") }, }
var DebugAgentSyncStateCmd = &cobra.Command{ Use: "sync-state", Short: "Show sync state for a zone", Long: `Display the current synchronization state for a zone. Example: tdns-cliv2 debug agent sync-state --zone example.com`, Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") req := tdns.AgentMgmtPost{ Command: "hsync-sync-state", Zone: tdns.ZoneName(tdns.Globals.Zonename), } amr, err := SendAgentDebugCmd(req, false) if err != nil { log.Fatalf("Error: %v", err) } if amr.Error { log.Fatalf("Error: %s", amr.ErrorMsg) } fmt.Printf("Sync State for zone %s:\n", tdns.Globals.Zonename) fmt.Printf("%s\n", amr.Msg) if amr.Data != nil { if dataMap, ok := amr.Data.(map[string]interface{}); ok { if zdr, ok := dataMap["zone_data_repo"]; ok { dump.P(zdr) } } } }, }
var DebugCmd = &cobra.Command{ Use: "debug", Short: "A brief description of your command", Run: func(cmd *cobra.Command, args []string) { fmt.Println("debug called") }, }
var DebugHsyncChunkRecvCmd = &cobra.Command{ Use: "chunk-recv", Short: "Show recently received CHUNKs", Long: `Display CHUNK messages received via DNS transport.`, Run: func(cmd *cobra.Command, args []string) { parent, _ := getCommandContext("debug") api, err := getApiClient(parent, true) if err != nil { log.Fatalf("Error getting API client: %v", err) } req := tdns.AgentMgmtPost{ Command: "hsync-chunk-recv", } _, buf, err := api.RequestNG("POST", "/agent/debug", req, true) if err != nil { log.Fatalf("API request failed: %v", err) } var resp tdns.AgentMgmtResponse if err := json.Unmarshal(buf, &resp); err != nil { log.Fatalf("Failed to parse response: %v", err) } if resp.Error { log.Fatalf("API error: %s", resp.ErrorMsg) } fmt.Println("Recently received CHUNKs:") fmt.Println(resp.Msg) }, }
DebugHsyncChunkRecvCmd shows received CHUNKs
var DebugHsyncChunkSendCmd = &cobra.Command{ Use: "chunk-send", Short: "Send a test CHUNK to a peer", Long: `Manually send a CHUNK message to a peer for testing. This is useful for testing DNS transport without full sync operations.`, Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") if hsyncPeerID == "" { log.Fatalf("Error: --peer is required") } parent, _ := getCommandContext("debug") api, err := getApiClient(parent, true) if err != nil { log.Fatalf("Error getting API client: %v", err) } req := tdns.AgentMgmtPost{ Command: "hsync-chunk-send", Zone: tdns.ZoneName(tdns.Globals.Zonename), AgentId: tdns.AgentId(hsyncPeerID), } _, buf, err := api.RequestNG("POST", "/agent/debug", req, true) if err != nil { log.Fatalf("API request failed: %v", err) } var resp tdns.AgentMgmtResponse if err := json.Unmarshal(buf, &resp); err != nil { log.Fatalf("Failed to parse response: %v", err) } if resp.Error { log.Fatalf("API error: %s", resp.ErrorMsg) } fmt.Printf("CHUNK sent successfully to peer %s\n", hsyncPeerID) fmt.Printf("Distribution ID: %s\n", resp.Msg) }, }
DebugHsyncChunkSendCmd sends a test CHUNK
var DebugHsyncInitDbCmd = &cobra.Command{ Use: "init-db", Short: "Initialize HSYNC database tables", Long: `Create or verify the HSYNC database tables exist.`, Run: func(cmd *cobra.Command, args []string) { parent, _ := getCommandContext("debug") api, err := getApiClient(parent, true) if err != nil { log.Fatalf("Error getting API client: %v", err) } req := tdns.AgentMgmtPost{ Command: "hsync-init-db", } _, buf, err := api.RequestNG("POST", "/agent/debug", req, true) if err != nil { log.Fatalf("API request failed: %v", err) } var resp tdns.AgentMgmtResponse if err := json.Unmarshal(buf, &resp); err != nil { log.Fatalf("Failed to parse response: %v", err) } if resp.Error { log.Fatalf("API error: %s", resp.ErrorMsg) } fmt.Println("HSYNC database tables initialized successfully") }, }
DebugHsyncInitDbCmd initializes HSYNC database tables
var DelCmd = &cobra.Command{
Use: "del",
Short: "Delegation prefix command. Only usable via sub-commands.",
}
var DeleteCmd = &cobra.Command{ Use: "delete [job-id]", Short: "Delete scan job(s)", Long: `Delete a specific scan job by job ID, or all jobs if --all is used`, Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { api, err := getApiClient("scanner", true) if err != nil { log.Fatalf("Error getting API client for scanner: %v", err) } deleteAll, _ := cmd.Flags().GetBool("all") var endpoint string if deleteAll { endpoint = "/scanner/delete?all=true" } else if len(args) > 0 { endpoint = fmt.Sprintf("/scanner/delete?job_id=%s", args[0]) } else { log.Fatal("Error: either specify a job ID or use --all flag") } status, buf, err := api.RequestNG("DELETE", endpoint, nil, true) if err != nil { log.Fatalf("Error from scanner API: %v", err) } if status == http.StatusNotFound { log.Fatalf("Job not found") } if status == http.StatusBadRequest { log.Fatalf("Bad request: %s", string(buf)) } if status != http.StatusOK { log.Fatalf("Unexpected status code: %d", status) } var resp tdns.ScannerResponse err = json.Unmarshal(buf, &resp) if err != nil { log.Fatalf("Error unmarshaling response: %v", err) } if resp.Error { log.Fatalf("Error: %s", resp.ErrorMsg) } fmt.Printf("%s\n", resp.Msg) }, }
var DsyncDiscoveryCmd = &cobra.Command{ Use: "dsync-query", Short: "Send a DNS query for 'zone. DSYNC' and present the result.", Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") tdns.Globals.Zonename = dns.Fqdn(tdns.Globals.Zonename) ctx, cancel, imr, err := StartImrForCli("") if err != nil { log.Fatalf("Error: %v", err) } defer cancel() dsync_res, err := imr.DsyncDiscovery(ctx, tdns.Globals.Zonename, tdns.Globals.Verbose) if err != nil { log.Fatalf("Error: %v", err) } fmt.Printf("Parent: %s\n", dsync_res.Parent) if len(dsync_res.Rdata) == 0 { fmt.Printf("No DSYNC record associated with '%s'\n", tdns.Globals.Zonename) } else { for _, nr := range dsync_res.Rdata { fmt.Printf("%s\tIN\tDSYNC\t%s\n", dsync_res.Qname, nr.String()) } } }, }
var ExitCmd = &cobra.Command{ Use: "exit", Short: "Exit the interactive shell", Run: func(cmd *cobra.Command, args []string) { Terminate() }, }
exitCmd represents the exit command
var (
GenerateCmd = &cobra.Command{
Use: "generate",
Short: "Generate DNS records or encodings",
}
)
var ImrCmd = &cobra.Command{
Use: "imr",
Short: "Interact with tdns-imr via API",
}
ImrCmd is the parent command for all IMR-related commands
var ImrDumpCmd = &cobra.Command{ Use: "dump", Short: "List records in the RRsetCache", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("Listing records in the RRsetCache\n") if Conf.Internal.RRsetCache == nil { fmt.Println("RRsetCache is nil") return } items := []core.Tuple[string, cache.CachedRRset]{} for item := range Conf.Internal.RRsetCache.RRsets.IterBuffered() { items = append(items, item) } sort.Slice(items, func(i, j int) bool { return lessByReverseLabels(items[i].Val.Name, items[j].Val.Name) }) for _, it := range items { PrintCacheItem(it, ".") } }, }
var ImrFlushCmd = &cobra.Command{
Use: "flush",
Short: "Flush cached data",
}
var ImrQueryCmd = &cobra.Command{ Use: "query [name] [type]", Short: "Query DNS records", Long: `Query DNS records for a given name and type`, Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { fmt.Println("Error: both name and type are required.") _ = cmd.Usage() return } fmt.Printf("Querying %s for %s records (verbose mode: %t)\n", args[0], args[1], tdns.Globals.Verbose) qname := dns.Fqdn(args[0]) if _, ok := dns.IsDomainName(qname); !ok { fmt.Printf("Not a valid domain name: '%s'\n", qname) return } qtype, exist := dns.StringToType[strings.ToUpper(args[1])] if !exist { fmt.Printf("Not a valid DNS RR type: '%s'\n", args[1]) return } if Conf.Internal.RecursorCh == nil { fmt.Printf("No active channel to RecursorEngine. Terminating.\n") return } resp := make(chan tdns.ImrResponse, 1) Conf.Internal.RecursorCh <- tdns.ImrRequest{ Qname: qname, Qclass: dns.ClassINET, Qtype: qtype, ResponseCh: resp, } select { case r := <-resp: // Check cache entry to determine if this is a negative response var cached *cache.CachedRRset if Conf.Internal.RRsetCache != nil { cached = Conf.Internal.RRsetCache.Get(qname, qtype) } if cached != nil && (cached.Context == cache.ContextNXDOMAIN || cached.Context == cache.ContextNoErrNoAns) { vstate := cached.State stateStr := cache.ValidationStateToString[vstate] ctxStr := cache.CacheContextToString[cached.Context] fmt.Printf("%s %s (state: %s)\n", qname, ctxStr, stateStr) if tdns.Globals.Verbose { if vstate == cache.ValidationStateIndeterminate { fmt.Printf("Proof: not possible for zone in state=indeterminate\n") } else if len(cached.NegAuthority) > 0 { fmt.Printf("Proof:\n") for _, negRRset := range cached.NegAuthority { if negRRset != nil { for _, rr := range negRRset.RRs { fmt.Printf(" %s\n", rr.String()) } for _, rr := range negRRset.RRSIGs { fmt.Printf(" %s\n", rr.String()) } } } } else if cached.RRset != nil { for _, rr := range cached.RRset.RRs { if rr.Header().Rrtype == dns.TypeSOA { fmt.Printf(" %s\n", rr.String()) } } } } else { fmt.Printf("Proof only presented in verbose mode\n") } } else if r.RRset != nil { vstate := cache.ValidationStateNone if cached != nil { vstate = cached.State } suffix := fmt.Sprintf(" (state: %s)", cache.ValidationStateToString[vstate]) for _, rr := range r.RRset.RRs { switch rr.Header().Rrtype { case qtype, dns.TypeCNAME: fmt.Printf("%s%s\n", rr.String(), suffix) default: fmt.Printf("Not printing: %q\n", rr.String()) } } for _, rr := range r.RRset.RRSIGs { fmt.Printf("%s\n", rr.String()) } } else if r.Error { fmt.Printf("Error: %s\n", r.ErrorMsg) } else { fmt.Printf("No records found: %s\n", r.Msg) } case <-time.After(3 * time.Second): fmt.Println("Timeout waiting for response") return } }, }
Query command - takes name and type
var ImrSetCmd = &cobra.Command{
Use: "set",
Short: "Set IMR runtime parameters",
}
var ImrShowCmd = &cobra.Command{
Use: "show",
Short: "Show IMR state",
}
var ImrStatsCmd = &cobra.Command{ Use: "stats", Short: "Show statistics", Long: `Show DNS query statistics`, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Showing statistics") }, }
Stats command - no arguments
var ImrZoneCmd = &cobra.Command{
Use: "zone",
Short: "prefix command for zone operations",
Long: `prefix command for zone operations`,
}
Zone command - takes zone name
var JwtCmd = &cobra.Command{
Use: "jwt",
Short: "JWT inspection and manipulation commands",
}
var KeysCmd = &cobra.Command{
Use: "keys",
Short: "JOSE keypair for secure CHUNK (generate, show)",
Long: `Generate a JOSE keypair or display the public key. Uses the server config file (agent or combiner) to get long_term_jose_priv_key path, or --server-config.`,
}
var KeystoreCmd = &cobra.Command{ Use: "keystore", Short: "Prefix command to access different features of tdns-auth truststore", Long: `The TDNS-AUTH keystore is where SIG(0) key pairs for zones are kept. The CLI contains functions for listing SIG(0) key pairs, adding and deleting keys.`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("keystore called. This is likely a mistake, sub command needed") }, }
var MaxWait int
var NewState string
var NotifyCmd = &cobra.Command{
Use: "notify",
Short: "The 'notify' command is only usable via defined sub-commands",
}
var PingCmd = &cobra.Command{ Use: "ping", Short: "Send an API ping request and present the response", Run: func(cmd *cobra.Command, args []string) { prefixcmd, _ := getCommandContext("ping") if len(args) != 0 { log.Fatal("ping must have no arguments") } api, err := getApiClient(prefixcmd, true) if err != nil { log.Fatalf("Error getting API client for %s: %v", prefixcmd, err) } pr, err := api.SendPing(tdns.Globals.PingCount, false) if err != nil { if strings.Contains(err.Error(), "connection refused") { fmt.Printf("Error: connection refused. Most likely the daemon is not running\n") os.Exit(1) } else { log.Fatalf("Error from SendPing: %v", err) } } uptime := time.Since(pr.BootTime).Truncate(time.Second) weeks := uptime / (7 * 24 * time.Hour) uptime %= 7 * 24 * time.Hour days := uptime / (24 * time.Hour) uptime %= 24 * time.Hour hours := uptime / time.Hour uptime %= time.Hour minutes := uptime / time.Minute uptime %= time.Minute seconds := uptime / time.Second var uptimeStr string if weeks > 0 { uptimeStr = fmt.Sprintf("%dw%dd", weeks, days) } else if days > 0 { uptimeStr = fmt.Sprintf("%dd%dh", days, hours) } else if hours > 0 { uptimeStr = fmt.Sprintf("%dh%dm", hours, minutes) } else { uptimeStr = fmt.Sprintf("%dm%ds", minutes, seconds) } if tdns.Globals.Verbose { fmt.Printf("%s (version %s): pings: %d, pongs: %d, uptime: %s, time: %s, client: %s\n", pr.Msg, pr.Version, pr.Pings, pr.Pongs, uptimeStr, pr.Time.Format(timelayout), pr.Client) } else { fmt.Printf("%s: pings: %d, pongs: %d, uptime: %s, time: %s\n", pr.Msg, pr.Pings, pr.Pongs, uptimeStr, pr.Time.Format(timelayout)) } }, }
var QuitCmd = &cobra.Command{ Use: "quit", Short: "Exit the interactive shell", Long: `Exit the interactive shell`, Hidden: true, Run: func(cmd *cobra.Command, args []string) { Terminate() }, }
quitCmd represents the quit command
var ReportCmd = &cobra.Command{ Use: "report <qname>", Short: "Send a report and (optionally) discover DSYNC via the internal resolver (imr)", Run: func(cmd *cobra.Command, args []string) { PrepArgs("zonename") dsyncLookup := true if dsyncTarget != "" && dsyncPort != 0 { dsyncLookup = false } ctx, cancel := context.WithCancel(context.Background()) defer cancel() tdns.Globals.App.Type = tdns.AppTypeCli if tdns.Globals.Debug { fmt.Printf("ReportCmd: Calling Conf.MainInit(%q)\n", tdns.DefaultCliCfgFile) } if err := Conf.MainInit(ctx, tdns.DefaultCliCfgFile); err != nil { tdns.Shutdowner(&Conf, fmt.Sprintf("Error initializing tdns-cli: %v", err)) } if reportSender == "" { fmt.Printf("Error: sender not specified\n") return } if dsyncLookup { _, cancel, imr, err := StartImrForCli("") if err != nil { log.Fatalf("Error initializing IMR: %v", err) } defer cancel() log.Printf("ReportCmd: Discovering DSYNC via IMR for %s", tdns.Globals.Zonename) dsyncRes, derr := imr.DsyncDiscovery(ctx, tdns.Globals.Zonename, tdns.Globals.Verbose) if derr != nil { log.Printf("ReportCmd: DSYNC discovery error: %v", derr) return } // Find DSYNC record with REPORT scheme var reportDSYNC *core.DSYNC for _, ds := range dsyncRes.Rdata { if ds.Scheme == core.SchemeReport { reportDSYNC = ds break } } if reportDSYNC == nil { log.Printf("ReportCmd: no DSYNC REPORT found for %s, aborting report", tdns.Globals.Zonename) return } targetIP = reportDSYNC.Target port = strconv.Itoa(int(reportDSYNC.Port)) } else { targetIP = dsyncTarget port = strconv.Itoa(dsyncPort) } if edeCode == 0 { edeCode = int(edns0.EDEMPZoneXfrFailure) } m := new(dns.Msg) m.SetNotify(tdns.Globals.Zonename) err := edns0.AddReportOptionToMessage(m, &edns0.ReportOption{ ZoneName: tdns.Globals.Zonename, EDECode: uint16(edeCode), Severity: 17, Sender: reportSender, Details: reportDetails, }) if err != nil { log.Printf("ReportCmd: failed to build report EDNS0 option: %v", err) return } log.Printf("ReportCmd: sending report to %s:%s (from DSYNC REPORT)", targetIP, port) c := core.NewDNSClient(core.TransportDo53, port, nil) if reportTsig { tsig := tdns.Globals.TsigKeys[reportSender+".key."] if tsig == nil { fmt.Printf("Error: tsig key not found for sender: %s\n", reportSender) return } // There is no built-in map or function in miekg/dns for this, so we use a switch. var alg string switch strings.ToLower(tsig.Algorithm) { case "hmac-sha1": alg = dns.HmacSHA1 case "hmac-sha256": alg = dns.HmacSHA256 case "hmac-sha384": alg = dns.HmacSHA384 case "hmac-sha512": alg = dns.HmacSHA512 default: alg = tsig.Algorithm } if tdns.Globals.Debug { fmt.Printf("TSIG signing the report with %s\n", tsig.Name) } tsigMap := map[string]string{tsig.Name: tsig.Secret} if c.DNSClientUDP != nil { c.DNSClientUDP.TsigSecret = tsigMap } if c.DNSClientTCP != nil { c.DNSClientTCP.TsigSecret = tsigMap } if c.DNSClientTLS != nil { c.DNSClientTLS.TsigSecret = tsigMap } m.SetTsig(tsig.Name, alg, 300, time.Now().Unix()) } if tdns.Globals.Debug { fmt.Printf("%s\n", m.String()) } resp, _, err := c.Exchange(m, targetIP, false) if err != nil { fmt.Printf("ReportCmd: error sending report: %v\n", err) os.Exit(1) } rcode := resp.Rcode if rcode == dns.RcodeSuccess { fmt.Printf("Report accepted (rcode: %s)\n", dns.RcodeToString[rcode]) } else { hasede, edecode, edemsg := edns0.ExtractEDEFromMsg(resp) if hasede { fmt.Printf("Error: rcode: %s, EDE Message: %s (EDE code: %d)\n", dns.RcodeToString[rcode], edemsg, edecode) } else { fmt.Printf("Error: rcode: %s\n", dns.RcodeToString[rcode]) fmt.Printf("Response msg:\n%s\n", resp.String()) } os.Exit(1) } }, }
var ResultsCmd = &cobra.Command{ Use: "results [job-id]", Short: "Get results of a completed scan job", Long: `Get detailed results of a completed scan job by job ID. Use --delete to delete the job after retrieving results.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { api, err := getApiClient("scanner", true) if err != nil { log.Fatalf("Error getting API client for scanner: %v", err) } jobID := args[0] endpoint := fmt.Sprintf("/scanner/status?job_id=%s", jobID) status, buf, err := api.RequestNG("GET", endpoint, nil, true) if err != nil { log.Fatalf("Error from scanner API: %v", err) } if status == http.StatusNotFound { log.Fatalf("Job not found: %s", jobID) } if status != http.StatusOK { log.Fatalf("Unexpected status code: %d", status) } var job tdns.ScanJobStatus err = json.Unmarshal(buf, &job) if err != nil { log.Fatalf("Error unmarshaling job status: %v", err) } if job.Status != "completed" { fmt.Printf("Job %s is not completed yet. Status: %s\n", jobID, job.Status) fmt.Printf("Progress: %d/%d tuples processed\n", job.ProcessedTuples, job.TotalTuples) return } if job.Error { fmt.Printf("Job %s completed with error: %s\n", jobID, job.ErrorMsg) return } deleteFlag, _ := cmd.Flags().GetBool("delete") if deleteFlag { deleteEndpoint := fmt.Sprintf("/scanner/delete?job_id=%s", jobID) delStatus, delBuf, err := api.RequestNG("DELETE", deleteEndpoint, nil, true) if err != nil { log.Printf("Warning: Error deleting job %s: %v", jobID, err) } else if delStatus == http.StatusOK { fmt.Printf("Job %s deleted successfully\n", jobID) } else { log.Printf("Warning: Failed to delete job %s: status %d, response: %s", jobID, delStatus, string(delBuf)) } } if tdns.Globals.Verbose { // Pretty print JSON var prettyJSON bytes.Buffer json.Indent(&prettyJSON, buf, "", " ") fmt.Println(prettyJSON.String()) } else { fmt.Printf("Job ID: %s\n", job.JobID) fmt.Printf("Status: %s\n", job.Status) fmt.Printf("Total Responses: %d\n\n", len(job.Responses)) for i, resp := range job.Responses { fmt.Printf("Response %d: %s (%s)\n", i+1, resp.Qname, tdns.ScanTypeToString[resp.ScanType]) if resp.DataChanged { fmt.Printf(" Data changed: Yes\n") } else { fmt.Printf(" Data changed: No\n") } if resp.AllNSInSync { fmt.Printf(" All NS in sync: Yes\n") } else if len(resp.Options) > 0 { for _, opt := range resp.Options { if opt == "all-ns" { fmt.Printf(" All NS in sync: No\n") break } } } if resp.Error { fmt.Printf(" Error: %s\n", resp.ErrorMsg) } fmt.Println() } } }, }
var RootKeysCmd = &cobra.Command{
Use: "keys",
Short: "Generate long-term keypairs for agent/combiner (JOSE)",
Long: `Generate JOSE keypairs used by tdns-agentv2 and tdns-combinerv2 for
authenticated NOTIFY(CHUNK) and API traffic. Use one keypair per party:
- Agent: agent.jose.private (+ optional agent.jose.pub for combiner config)
- Combiner: combiner.jose.private (+ optional combiner.jose.pub for agent config)
`,
}
RootKeysCmd is the root-level "keys" command (e.g. tdns-cli keys generate). It does not require tdns-cli config or API; use for generating JOSE keypairs on disk.
var ScanCdsCmd = &cobra.Command{ Use: "cds [zone...]", Short: "Send CDS scan request with ScanTuple data to tdns-scanner", Long: `Send CDS scan request for one or more zones. Zones can be specified as arguments or via --zone flag.`, Args: cobra.MinimumNArgs(0), Run: func(cmd *cobra.Command, args []string) { api, err := getApiClient("scanner", true) if err != nil { log.Fatalf("Error getting API client for scanner: %v", err) } // Get zones from command arguments var zones []string if len(args) > 0 { for _, arg := range args { zone := strings.TrimSpace(arg) if zone != "" { zones = append(zones, zone) } } } if len(zones) == 0 { if tdns.Globals.Zonename == "" { log.Fatal("Error: specify zones as arguments or use --zone flag") } zones = []string{tdns.Globals.Zonename} } if tdns.Globals.Verbose { fmt.Printf("Scanning %d zones: %v\n", len(zones), zones) } scanTuples := make([]tdns.ScanTuple, 0, len(zones)) for _, zone := range zones { zone = dns.Fqdn(zone) scanTuple := tdns.ScanTuple{ Zone: zone, CurrentData: tdns.CurrentScanData{ CDS: nil, }, } scanTuples = append(scanTuples, scanTuple) } post := tdns.ScannerPost{ Command: "SCAN", ScanType: tdns.ScanCDS, ScanTuples: scanTuples, } status, buf, err := api.RequestNG("POST", "/scanner", post, true) if err != nil { log.Fatalf("Error from scanner API: %v", err) } if tdns.Globals.Verbose { fmt.Printf("Status: %d\n", status) } var resp tdns.ScannerResponse err = json.Unmarshal(buf, &resp) if err != nil { log.Fatalf("Error unmarshaling response: %v", err) } if resp.Error { log.Fatalf("Error from scanner: %s", resp.ErrorMsg) } fmt.Printf("Scanner response: %s\n", resp.Msg) if resp.Status != "" { fmt.Printf("Status: %s\n", resp.Status) } if resp.JobID != "" { fmt.Printf("Job ID: %s\n", resp.JobID) fmt.Printf("Use 'tdns-cli scanner status %s' to check job status\n", resp.JobID) } }, }
var ScanCmd = &cobra.Command{
Use: "scan",
Short: "Send scan requests to tdns-scanner",
}
var ScannerCmd = &cobra.Command{
Use: "scanner",
Short: "Interact with tdns-scanner via API",
}
var ServerName string = "PLACEHOLDER"
var StatusCmd = &cobra.Command{ Use: "status [job-id]", Short: "Get status of scan job(s)", Long: `Get status of a specific scan job by job ID, or list all jobs if no job ID is provided`, Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { api, err := getApiClient("scanner", true) if err != nil { log.Fatalf("Error getting API client for scanner: %v", err) } var endpoint string if len(args) > 0 { endpoint = fmt.Sprintf("/scanner/status?job_id=%s", args[0]) } else { endpoint = "/scanner/status" } status, buf, err := api.RequestNG("GET", endpoint, nil, true) if err != nil { log.Fatalf("Error from scanner API: %v", err) } if status == http.StatusNotFound { log.Fatalf("Job not found") } if status == http.StatusBadRequest { log.Fatalf("Bad request: %s", string(buf)) } if status != http.StatusOK { log.Fatalf("Unexpected status code: %d", status) } if len(args) > 0 { // Single job - show detailed status var job tdns.ScanJobStatus err = json.Unmarshal(buf, &job) if err != nil { log.Fatalf("Error unmarshaling job status: %v", err) } fmt.Printf("Job ID: %s\n", job.JobID) fmt.Printf("Status: %s\n", job.Status) fmt.Printf("Created: %s\n", job.CreatedAt.Format("2006-01-02 15:04:05")) if job.StartedAt != nil { fmt.Printf("Started: %s\n", job.StartedAt.Format("2006-01-02 15:04:05")) } if job.CompletedAt != nil { fmt.Printf("Completed: %s\n", job.CompletedAt.Format("2006-01-02 15:04:05")) } fmt.Printf("Progress: %d/%d tuples processed\n", job.ProcessedTuples, job.TotalTuples) if job.Error { fmt.Printf("Error: %s\n", job.ErrorMsg) } if len(job.Responses) > 0 { fmt.Printf("\nResults (%d responses):\n", len(job.Responses)) for i, resp := range job.Responses { fmt.Printf("\n Response %d:\n", i+1) fmt.Printf(" Qname: %s\n", resp.Qname) fmt.Printf(" Scan Type: %s\n", tdns.ScanTypeToString[resp.ScanType]) fmt.Printf(" Data Changed: %t\n", resp.DataChanged) if resp.AllNSInSync { fmt.Printf(" All NS In Sync: true\n") } else if len(resp.Options) > 0 { for _, opt := range resp.Options { if opt == "all-ns" { fmt.Printf(" All NS In Sync: false\n") break } } } if resp.Error { fmt.Printf(" Error: %s\n", resp.ErrorMsg) } } } } else { // All jobs - show summary table var jobs []*tdns.ScanJobStatus err = json.Unmarshal(buf, &jobs) if err != nil { log.Fatalf("Error unmarshaling job list: %v", err) } if len(jobs) == 0 { fmt.Println("No jobs found") return } sort.Slice(jobs, func(i, j int) bool { return jobs[i].CreatedAt.After(jobs[j].CreatedAt) }) t := acidtab.New("JOB ID", "STATUS", "CREATED", "PROGRESS", "ERROR") for _, job := range jobs { progress := fmt.Sprintf("%d/%d", job.ProcessedTuples, job.TotalTuples) errorStr := "" if job.Error { errorStr = job.ErrorMsg if len(errorStr) > 30 { errorStr = errorStr[:27] + "..." } } created := job.CreatedAt.Format("2006-01-02 15:04:05") t.Row(job.JobID, job.Status, created, progress, errorStr) } fmt.Println(t.String()) } }, }
var StopCmd = &cobra.Command{ Use: "stop", Short: "Send stop command to tdns-auth / tdns-agent", Run: func(cmd *cobra.Command, args []string) { SendCommand("stop", ".") }, }
var TheAgentId string
var ToRFC3597Cmd = &cobra.Command{ Use: "rfc3597", Short: "Generate the RFC 3597 representation of a DNS record", Run: func(cmd *cobra.Command, args []string) { if rrstr == "" { log.Fatalf("Record to generate RFC 3597 representation for not specified.") } rr, err := dns.NewRR(rrstr) if err != nil { log.Fatalf("Could not parse record \"%s\": %v", rrstr, err) } fmt.Printf("Normal (len=%d): \"%s\"\n", dns.Len(rr), rr.String()) u := new(dns.RFC3597) u.ToRFC3597(rr) fmt.Printf("RFC 3597 (len=%d): \"%s\"\n", dns.Len(u), u.String()) }, }
var TruststoreCmd = &cobra.Command{
Use: "truststore",
Short: "Prefix command to access different features of tdns-auth truststore",
Long: `The TDNS-AUTH truststore is where SIG(0) public keys for child zones are kept.
The CLI contains functions for listing trusted SIG(0) keys, adding and
deleting child keys as well as changing the trust state of individual keys.`,
}
var UpdateCmd = &cobra.Command{
Use: "update",
Short: "[OBE] Create and ultimately send a DNS UPDATE msg for zone auth data",
}
var VersionCmd = &cobra.Command{ Use: "version", Short: "Print the version of the app, more or less verbosely", Run: func(cmd *cobra.Command, args []string) { fmt.Printf("This is %s, version %s, compiled on %v\n", tdns.Globals.App.Name, tdns.Globals.App.Version, tdns.Globals.App.Date) }, }
var ZoneCmd = &cobra.Command{
Use: "zone",
Short: "Prefix command, not usable by itself",
}
Functions ¶
func CreateUpdate ¶
func CreateUpdate(updateType string)
CreateUpdate starts an interactive CLI for composing, signing, and sending DNS UPDATEs.
It initializes the keystore and signing state, then enters a prompt loop that lets the user add or delete RRs, view the pending update, sign it with SIG(0) keys (from a keyfile or the keystore), select the target server, and send the update. The function updates package-level globals such as `zone`, `signer`, and `server` as needed. It may call os.Exit on fatal initialization or signing errors.
The updateType parameter selects the operational context used to initialize the interactive session (for example "child" or "zone") and does not affect the format of the DNS UPDATE messages produced.
func DnssecGenDS ¶
func DnssecGenDS() error
func DnssecKeyMgmt ¶
func ListMPZones ¶
func ListMPZones(cr tdns.ZoneResponse)
ListMPZones displays multi-provider zone details in tabular format.
func ListZones ¶
func ListZones(cr tdns.ZoneResponse)
func PrepArgs ¶
func PrepArgs(args ...interface{})
PrepArgs validates and normalizes CLI parameters. It can work in two modes: 1. Legacy mode: reads from global variables (tdns.Globals.*) 2. Flag mode: reads from cobra.Command flags (pass cmd as first argument)
Usage:
PrepArgs("zonename") // reads from tdns.Globals.Zonename
PrepArgs(cmd, "zonename") // reads from --zone flag
PrepArgs(cmd, "zonename", "service") // reads from --zone and --service flags
func PrintCacheItem ¶
func PrintHsyncRRs ¶
func PrintUpdateResult ¶
func PrintUpdateResult(ur tdns.UpdateResult)
func ReadZoneData ¶
func SendAgentDebugCmd ¶
func SendAgentDebugCmd(req tdns.AgentMgmtPost, printJson bool) (*tdns.AgentMgmtResponse, error)
func SendAgentMgmtCmd ¶
func SendAgentMgmtCmd(req *tdns.AgentMgmtPost, prefix string) (*tdns.AgentMgmtResponse, error)
func SendAuthPeerCmd ¶
func SendAuthPeerCmd(req tdns.AuthPeerPost) (*tdns.AuthPeerResponse, error)
SendAuthPeerCmd sends a peer command to the auth server's /auth/peer endpoint.
func SendCatalogCommand ¶
func SendCatalogCommand(api *tdns.ApiClient, data tdns.CatalogPost) (*tdns.CatalogResponse, error)
SendCatalogCommand sends a catalog command to the API
func SendCombinerDebugCmd ¶
func SendCombinerDebugCmd(req tdns.CombinerDebugPost) (*tdns.CombinerDebugResponse, error)
func SendCombinerEditCmd ¶
func SendCombinerEditCmd(req tdns.CombinerEditPost) (*tdns.CombinerEditResponse, error)
SendCombinerEditCmd sends a combiner edit management request to the combiner API.
func SendCommand ¶
func SendCommandNG ¶
func SendCommandNG(api *tdns.ApiClient, data tdns.CommandPost) (tdns.CommandResponse, error)
func SendConfigCommand ¶
func SendConfigCommand(api *tdns.ApiClient, data tdns.ConfigPost) (tdns.ConfigResponse, error)
func SendDelegationCmd ¶
func SendDelegationCmd(api *tdns.ApiClient, data tdns.DelegationPost) (tdns.DelegationResponse, error)
func SendDsyncCommand ¶
func SendDsyncCommand(api *tdns.ApiClient, data tdns.ZoneDsyncPost) (tdns.ZoneDsyncResponse, error)
func SendKeystoreCmd ¶
func SendKeystoreCmd(api *tdns.ApiClient, data tdns.KeystorePost) (tdns.KeystoreResponse, error)
func SendNotify ¶
func SendTruststore ¶
func SendTruststore(api *tdns.ApiClient, data tdns.TruststorePost) (tdns.TruststoreResponse, error)
func SendZoneCommand ¶
func SetRootCommand ¶
SetRootCommand allows the root package to provide the root command reference
func Sig0KeyMgmt ¶
func Sig0TrustMgmt ¶
func StartImrForCli ¶
StartImrForCli initializes and starts the internal IMR for CLI commands. It sets up the minimal config, starts RecursorEngine, and waits for initialization. Returns the context, cancel function, and the Imr instance, or an error if initialization fails.
func StartInteractiveMode ¶
func StartInteractiveMode()
func ValidateBySection ¶
func VerboseListZone ¶
func VerboseListZone(cr tdns.ZoneResponse)
Types ¶
type CommandNode ¶
type CommandNode struct {
Name string // Command name
Command *cobra.Command // Reference to original Cobra command
SubCommands map[string]*CommandNode // Child commands
Parent *CommandNode // Parent command (nil for root)
Args []string // Expected arguments from Use field
Guide string // Guide text for this command
}
CommandNode represents a node in our command tree
func BuildCommandTree ¶
func BuildCommandTree(cmd *cobra.Command, parent *CommandNode) *CommandNode
BuildCommandTree creates a tree structure from Cobra commands
func (*CommandNode) DebugPrint ¶
func (n *CommandNode) DebugPrint(indent string)
Debug function to print the tree structure
Source Files
¶
- agent_cmds.go
- agent_debug_cmds.go
- agent_edits_cmds.go
- agent_gossip_cmds.go
- agent_imr_cmds.go
- agent_router_cmds.go
- agent_zone_cmds.go
- auth_cmds.go
- auth_peer_cmds.go
- base32_cmds.go
- catalog_cmds.go
- combiner_cmds.go
- combiner_debug_cmds.go
- combiner_edits_cmds.go
- combiner_peer_cmds.go
- commands.go
- config.go
- config_cmds.go
- daemon_cmds.go
- db_cmds.go
- ddns_cmds.go
- debug_cmds.go
- distrib_cmds.go
- dsync_cmds.go
- fakezone.go
- generate_cmds.go
- hsync_cmds.go
- hsync_debug_cmds.go
- imr_cmds.go
- imr_dump_cmds.go
- imr_set_cmds.go
- interactive.go
- jose_keys_cmds.go
- jwt_cmds.go
- keys_generate_cmds.go
- keystore_cmds.go
- notify_cmds.go
- parentsync_cmds.go
- ping.go
- prepargs.go
- readline.go
- report_cmds.go
- rfc3597_cmds.go
- scanner_cmds.go
- transaction_cmds.go
- truststore_cmds.go
- update.go
- version_cmd.go
- zone_cmds.go
- zone_dsync_cmds.go