Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
07d82f7
deps: point libconnect at local checkout for phase-1 (localdns) dev
scott-cotton Jun 8, 2026
f74f615
local: add --local-dns flag and EnableLocalDNS config
scott-cotton Jun 8, 2026
d8ae0df
locald/rootmanager: run localdns in place of etchosts when --local-dns
scott-cotton Jun 8, 2026
b3b7cd0
local: surface localdns in `local status`
scott-cotton Jun 8, 2026
bd89789
local: reject --local-dns with --unprivileged
scott-cotton Jun 8, 2026
77f5963
local: fix --local-dns connect health check
scott-cotton Jun 9, 2026
79ec064
deps: point libconnect at feat/localdns-1f branch
scott-cotton Jun 9, 2026
44b9102
Merge remote-tracking branch 'origin/main' into feat/local-dns
scott-cotton Jun 30, 2026
0e6c121
add `signadot local hosts`
scott-cotton Jul 6, 2026
7d75d85
deps: bump libconnect for localdns.Recover and resolver config
scott-cotton Jul 10, 2026
ce126a7
local: reap sandboxmanager on error and recover DNS in all modes
scott-cotton Jul 10, 2026
9429803
local: fix --local-dns tunnel-proxy restart loop
scott-cotton Jul 10, 2026
04f6c8b
local: opt into local DNS via connection resolver config
scott-cotton Jul 10, 2026
49aac83
local hosts: single ip per host, sdtab table output
scott-cotton Jul 10, 2026
9a4d687
local: omit hosts status under --local-dns, clearer GetHosts error, g…
scott-cotton Jul 10, 2026
48a594e
deps: bump libconnect for localdns bind-address probe
scott-cotton Jul 10, 2026
587761e
deps: bump libconnect for localdns ndots:5 fix
scott-cotton Jul 10, 2026
f2ebe46
deps: bump libconnect for Linux short-name synthesis
scott-cotton Jul 10, 2026
ce4f10d
local: label the local-DNS status count as "names", not "cluster names"
scott-cotton Jul 21, 2026
c4e43a4
deps: bump libconnect for local-DNS self-forward + warning fixes
scott-cotton Jul 21, 2026
99f6e9d
local: show distinct host count alongside name count in DNS status
scott-cotton Jul 21, 2026
3677834
deps: bump libconnect (drop obsolete macOS >10 resolver-files warning)
scott-cotton Jul 21, 2026
ac86f05
deps: bump libconnect (probe skips resolv.conf addresses)
scott-cotton Jul 21, 2026
c3a8932
deps: bump libconnect (resolv.conf recovery hardening + server de-race)
scott-cotton Jul 21, 2026
5a5e17c
local: handle new-CLI/old-daemon skew in local-DNS status line
scott-cotton Jul 21, 2026
df4cc3d
deps: bump libconnect (PR #163 review fixes)
scott-cotton Jul 28, 2026
be91305
Merge remote-tracking branch 'origin/main' into feat/local-dns
scott-cotton Jul 28, 2026
b1dc989
Merge branch 'main' into feat/local-dns
scott-cotton Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/oklog/run v1.1.0
github.com/panta/machineid v1.0.2
github.com/signadot/go-sdk v0.3.8-0.20260729140747-c2e52c424d95
github.com/signadot/libconnect v0.1.1-0.20260527212426-31fdd269494f
github.com/signadot/libconnect v0.1.1-0.20260730200225-ae66f07f8f04
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.11.0
github.com/theckman/yacspin v0.13.12
Expand Down Expand Up @@ -87,6 +87,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
Expand All @@ -110,8 +111,10 @@ require (
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/tools v0.47.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkEN
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
Expand Down Expand Up @@ -456,8 +458,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/signadot/go-sdk v0.3.8-0.20260729140747-c2e52c424d95 h1:wM5K//1RyBhd1K+9qHqTTzdZvCoGZeV2lS/0apJZqqY=
github.com/signadot/go-sdk v0.3.8-0.20260729140747-c2e52c424d95/go.mod h1:P9bcwyPeom4W8XToYDxNpXJ4w0EeKaM5lF+GkoeY3Aw=
github.com/signadot/libconnect v0.1.1-0.20260527212426-31fdd269494f h1:b8ZzCjMOat1IJ9Y5rTRoy7TjD0Ch/QDu8P5lHuSJoBQ=
github.com/signadot/libconnect v0.1.1-0.20260527212426-31fdd269494f/go.mod h1:5/CHotq3FQnSv9XQUy3sQkJz240Al1MsJ2wm1wf3usE=
github.com/signadot/libconnect v0.1.1-0.20260730200225-ae66f07f8f04 h1:CzSUlnyjhmBY+B7hTIedm/BoqweAFoJaaskJc+W+33M=
github.com/signadot/libconnect v0.1.1-0.20260730200225-ae66f07f8f04/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
Expand Down Expand Up @@ -581,6 +583,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down
27 changes: 25 additions & 2 deletions internal/command/local/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg
return err
}

// Resolve the effective name-resolution mode. The connection's `resolver:`
// config field is the encouraged way to opt in; the --local-dns flag is a
// per-invocation override that wins only when explicitly set (so
// --local-dns=false can also force /etc/hosts over a LocalDNS config).
enableLocalDNS := connConfig.Resolver == connectcfg.LocalDNSResolver
localDNSFromFlag := cmd.Flags().Changed("local-dns")
if localDNSFromFlag {
enableLocalDNS = cfg.LocalDNS
}
if enableLocalDNS && !withRootManager {
source := "the --local-dns flag"
if !localDNSFromFlag {
source = fmt.Sprintf("resolver: LocalDNS in the config for cluster %q", connConfig.Cluster)
}
return fmt.Errorf("%s manages the system DNS resolver and requires root; "+
"it cannot be used with --unprivileged", source)
}

// Get devbox claim and session
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
Expand Down Expand Up @@ -162,6 +180,7 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg
},
Env: os.Environ(),
ConnectionConfig: connConfig,
EnableLocalDNS: enableLocalDNS,
ProxyURL: cfg.ProxyURL,
APIURL: cfg.API.APIURL,
APIKey: cfg.GetAPIKey(),
Expand Down Expand Up @@ -209,9 +228,13 @@ func runConnectImpl(out, errOut io.Writer, log *slog.Logger, localConfig *config
var cmd *exec.Cmd
if ciConfig.WithRootManager {
if os.Geteuid() != 0 {
dnsLine := "- updating /etc/hosts with cluster service names"
if ciConfig.EnableLocalDNS {
dnsLine = "- configuring the system DNS resolver for cluster service names"
}
fmt.Fprintf(out, "signadot local connect needs root privileges for:\n\t"+
"- updating /etc/hosts with cluster service names\n\t"+
"- configuring networking to direct local traffic to the cluster\n")
"%s\n\t"+
"- configuring networking to direct local traffic to the cluster\n", dnsLine)
}
// run the root-manager
args := []string{
Expand Down
78 changes: 78 additions & 0 deletions internal/command/local/hosts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package local

import (
"fmt"
"io"

"github.com/signadot/cli/internal/config"
sbmgr "github.com/signadot/cli/internal/locald/sandboxmanager"
"github.com/signadot/cli/internal/print"
"github.com/signadot/cli/internal/sdtab"
"github.com/spf13/cobra"
)

func newHosts(localConfig *config.Local) *cobra.Command {
cfg := &config.LocalHosts{Local: localConfig}

cmd := &cobra.Command{
Use: "hosts",
Short: "List the cluster hosts resolvable from the local machine and their IP addresses",
RunE: func(cmd *cobra.Command, args []string) error {
return runHosts(cfg, cmd.OutOrStdout(), args)
},
}
cfg.AddFlags(cmd)

return cmd
}

// printableHost is the JSON/YAML shape of a single resolvable host. Each host
// carries exactly one address: the root controller assigns a single virtual
// address per name and reports it as HostEntry.Ip (IPv4-preferred).
type printableHost struct {
Name string `json:"name"`
IP string `json:"ip"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schema forward-compat: this bakes a single ip into the -o json/-o yaml output, while the proto already carries repeated ips and this PR lays IPv6 dual-stack groundwork. When AAAA support lands, either ip gets a breaking rename to ips for script consumers, or it stays and silently under-reports dual-stack hosts. Emitting "ips": [...] from day one avoids the breaking change; the human-readable view can keep showing one address.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change the proto to be singleton ip. This doesn't affect ipv6/dual-stack forward compatibility and is simpler, following the table below:

ipv4 dual-stack ipv6
use ipv4 use ipv4 (both virtual addrs reach the service; v4 by preference) use ipv6

the ipMap representation retains []ip as it is the backing for the resolver which needs to answer AAAA and A record requests.

}

func runHosts(cfg *config.LocalHosts, out io.Writer, args []string) error {
if err := cfg.InitLocalConfig(); err != nil {
return err
}
resp, err := sbmgr.GetHosts()
if err != nil {
return err
}

hosts := make([]printableHost, 0, len(resp.Entries))
for _, e := range resp.Entries {
hosts = append(hosts, printableHost{Name: e.Name, IP: e.Ip})
}

switch cfg.OutputFormat {
case config.OutputFormatDefault:
return printHosts(out, hosts)
case config.OutputFormatJSON:
return print.RawJSON(out, hosts)
case config.OutputFormatYAML:
return print.RawK8SYAML(out, hosts)
default:
return fmt.Errorf("unsupported output format: %q", cfg.OutputFormat)
}
}

type hostRow struct {
Name string `sdtab:"NAME"`
IP string `sdtab:"IP"`
}

// printHosts renders the default aligned NAME/IP table, consistent with the
// other list commands (cluster, routegroup, ...). The entries arrive already
// sorted by name from the root controller (see rootServer.GetHosts).
func printHosts(out io.Writer, hosts []printableHost) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default output should use internal/sdtab like every other list command (cluster, routegroup, resourceplugin, ...), so humans get an aligned NAME/IP table with a header, and scripts use -o json. The bare "%s %s" lines are also not hosts-file format (that's <ip> <name>, reversed), so there's no copy-paste argument for keeping them. Following the pattern from cluster/printers.go:

type hostRow struct {
	Name string `sdtab:"NAME"`
	IP   string `sdtab:"IP"`
}

func printHosts(out io.Writer, hosts []printableHost) error {
	t := sdtab.New[hostRow](out)
	t.AddHeader()
	for _, h := range hosts {
		t.AddRow(hostRow{Name: h.Name, IP: h.IP})
	}
	return t.Flush()
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning for the standard output was that this command is different because it is likely to get many many more outputs than another list using sdtab. So, the headers basically are likely to be invisible, off screen in any case -- why force the acrobatics and extra machinery of json or header filtering on consumers?

More generally, removing the headers is a basic UX concern regarding standard unix pipelining.

But, since it seems this reasoning only subtly justifies deviating from the unusable-in-pipelines UX elsewhere, I'll add the sdtab.

t := sdtab.New[hostRow](out)
t.AddHeader()
for _, h := range hosts {
t.AddRow(hostRow{Name: h.Name, IP: h.IP})
}
return t.Flush()
}
44 changes: 44 additions & 0 deletions internal/command/local/hosts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package local

import (
"bytes"
"strings"
"testing"
)

// TestPrintHosts checks the default table has a header row and one aligned
// NAME/IP row per host, in the order given (already name-sorted by the daemon).
func TestPrintHosts(t *testing.T) {
var b bytes.Buffer
err := printHosts(&b, []printableHost{
{Name: "a.myns.svc.cluster.local", IP: "242.242.0.3"},
{Name: "b.other.svc.cluster.local", IP: "242.242.0.4"},
})
if err != nil {
t.Fatal(err)
}
lines := strings.Split(strings.TrimRight(b.String(), "\n"), "\n")
if len(lines) != 3 {
t.Fatalf("got %d lines, want 3 (header + 2 rows):\n%s", len(lines), b.String())
}
if !strings.Contains(lines[0], "NAME") || !strings.Contains(lines[0], "IP") {
t.Errorf("header line missing NAME/IP: %q", lines[0])
}
if !strings.HasPrefix(lines[1], "a.myns.svc.cluster.local") || !strings.Contains(lines[1], "242.242.0.3") {
t.Errorf("row 1 unexpected: %q", lines[1])
}
if !strings.HasPrefix(lines[2], "b.other.svc.cluster.local") || !strings.Contains(lines[2], "242.242.0.4") {
t.Errorf("row 2 unexpected: %q", lines[2])
}
}

// TestPrintHostsEmpty: no hosts still emits just the header (and no panic).
func TestPrintHostsEmpty(t *testing.T) {
var b bytes.Buffer
if err := printHosts(&b, nil); err != nil {
t.Fatal(err)
}
if !strings.Contains(b.String(), "NAME") {
t.Errorf("empty output missing header: %q", b.String())
}
}
1 change: 1 addition & 0 deletions internal/command/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func New(api *config.API) *cobra.Command {
newStatus(cfg),
newDisconnect(cfg),
newProxy(cfg),
newHosts(cfg),
override.New(cfg),
)

Expand Down
102 changes: 101 additions & 1 deletion internal/command/local/printers.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func printRawStatus(cfg *config.LocalStatus, out io.Writer, printer func(out io.
OperatorInfo any `json:"operatorInfo,omitempty"`
Localnet any `json:"localnet,omitempty"`
Hosts any `json:"hosts,omitempty"`
LocalDNS any `json:"localDNS,omitempty"`
Portforward any `json:"portforward,omitempty"`
ControlPlaneProxy any `json:"controlPlaneProxy,omitempty"`
SandboxesWatcher any `json:"sandboxesWatcher,omitempty"`
Expand All @@ -45,6 +46,7 @@ func printRawStatus(cfg *config.LocalStatus, out io.Writer, printer func(out io.
OperatorInfo: getRawOperatorInfo(cfg, status.OperatorInfo),
Localnet: getRawLocalnet(cfg, ciConfig, status.Localnet, statusMap),
Hosts: getRawHosts(cfg, ciConfig, status.Hosts, statusMap),
LocalDNS: getRawLocalDNS(cfg, ciConfig, status.LocalDns, statusMap),
Portforward: getRawPortforward(cfg, ciConfig, status.Portforward, statusMap),
ControlPlaneProxy: getRawControlPlaneProxy(cfg, ciConfig, status.ControlPlaneProxy, statusMap),
SandboxesWatcher: getRawWatcher(cfg, status.Watcher, statusMap),
Expand Down Expand Up @@ -186,6 +188,13 @@ func getRawHosts(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConf
if !ciConfig.WithRootManager {
return hosts
}
if ciConfig.EnableLocalDNS {
// In --local-dns mode /etc/hosts management is intentionally not
// running, so the root manager reports no hosts status. Omit the section
// (mirroring getRawLocalDNS when local DNS is disabled) rather than
// emitting a bogus {"healthy": false, "numHosts": 0}.
return nil
}

if cfg.Details {
// Details view
Expand Down Expand Up @@ -219,6 +228,48 @@ func getRawHosts(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConf
return result
}

func getRawLocalDNS(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConfig,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inverse gating is missing on getRawHosts: in --local-dns mode the root manager reports Hosts == nil, so the non-details JSON/YAML status output still emits "hosts": {"healthy": false, "numHosts": 0}, which reads as a failure even though /etc/hosts management is intentionally not running. Suggest getRawHosts return nil when ciConfig.EnableLocalDNS, mirroring how this function returns nil when local DNS is disabled. (The human-readable view is already handled by the printSuccess branch.)

ldns *commonapi.LocalDNSStatus, statusMap map[string]any) any {
if !ciConfig.WithRootManager || !ciConfig.EnableLocalDNS {
return nil
}

if cfg.Details {
// Details view
return statusMap["localDns"]
}

// Standard view
type PrintableLocalDNS struct {
Healthy bool `json:"healthy"`
RecordCount uint32 `json:"recordCount,omitempty"`
HostCount uint32 `json:"hostCount,omitempty"`
BindAddr string `json:"bindAddr,omitempty"`
Warning string `json:"warning,omitempty"`
LastErrorReason string `json:"lastErrorReason,omitempty"`
}

result := &PrintableLocalDNS{Healthy: false}
if ldns == nil || ldns.Health == nil {
return result
}
if ldns.Health.Healthy {
result = &PrintableLocalDNS{
Healthy: true,
RecordCount: ldns.RecordCount,
HostCount: ldns.HostCount,
BindAddr: ldns.BindAddr,
Warning: ldns.Warning,
}
} else {
result = &PrintableLocalDNS{
Healthy: false,
LastErrorReason: ldns.Health.LastErrorReason,
}
}
return result
}

func getRawPortforward(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConfig,
portforward *commonapi.PortForwardStatus, statusMap map[string]any) any {
if ciConfig.ConnectionConfig.Type != connectcfg.PortForwardLinkType {
Expand Down Expand Up @@ -437,7 +488,11 @@ func (p *statusPrinter) printSuccess() {
}
if p.ciConfig.WithRootManager {
p.printLocalnetStatus()
p.printHostsStatus()
if p.ciConfig.EnableLocalDNS {
p.printLocalDNSStatus()
} else {
p.printHostsStatus()
}
}
p.printSandboxesWatcherStatus()
p.printSandboxStatus()
Expand Down Expand Up @@ -505,6 +560,51 @@ func (p *statusPrinter) printHostsStatus() {
p.printLine(p.out, 1, fmt.Sprintf("%d hosts accessible via /etc/hosts", p.status.Hosts.NumHosts), "*")
}

func (p *statusPrinter) printLocalDNSStatus() {
ldns := p.status.LocalDns
if ldns == nil || ldns.Health == nil {
p.printLine(p.out, 1, "local DNS resolver is not running", "*")
return
}
if ldns.Health.Healthy {
// RecordCount is the total resolvable DNS names, which includes the
// synthesized short forms (e.g. <svc>.<ns>, <svc>.<ns>.svc); HostCount is
// the distinct hosts `local hosts` lists. Show both so the larger name
// count doesn't read as a discrepancy against `local hosts` — but omit the
// host count when it's absent (0), which is what an older daemon that
// predates the host_count field reports (a new CLI vs old locald).
msg := fmt.Sprintf("%d names resolvable via local DNS (%s)", ldns.RecordCount, ldns.BindAddr)
if ldns.HostCount > 0 {
msg = fmt.Sprintf("%d names (%d hosts) resolvable via local DNS (%s)",
ldns.RecordCount, ldns.HostCount, ldns.BindAddr)
}
p.printLine(p.out, 1, msg, "*")
} else {
p.printLine(p.out, 1, fmt.Sprintf("local DNS resolver not healthy (%q)",
ldns.Health.LastErrorReason), "*")
}
if ldns.Warning != "" {
p.printLine(p.out, 2, "warning: "+ldns.Warning, p.red("!"))
}
if p.cfg.Details {
if ldns.Mode != "" {
p.printLine(p.out, 2, "mode: "+ldns.Mode, "*")
}
if len(ldns.Suffixes) > 0 {
p.printLine(p.out, 2, "Suffixes:", "*")
for _, s := range ldns.Suffixes {
p.printLine(p.out, 3, s, "-")
}
}
if len(ldns.Upstreams) > 0 {
p.printLine(p.out, 2, "Upstreams:", "*")
for _, u := range ldns.Upstreams {
p.printLine(p.out, 3, u, "-")
}
}
}
}

func (p *statusPrinter) printSandboxesWatcherStatus() {
msg := "sandboxes watcher is not running"
if p.status.Watcher != nil && p.status.Watcher.Health != nil {
Expand Down
Loading