From 07d82f76719ff1f4abdc95db1758cc31f8512a36 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 8 Jun 2026 10:15:10 +0200 Subject: [PATCH 01/25] deps: point libconnect at local checkout for phase-1 (localdns) dev DEV-ONLY: enables the replace directive so the CLI builds against the local libconnect phase-1 branch (localdns). Revert and bump the pinned libconnect version before this lands. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 06f862a..22386ab 100644 --- a/go.mod +++ b/go.mod @@ -183,6 +183,6 @@ require ( ) // Used for local dev -// replace github.com/signadot/libconnect => ../libconnect/ +replace github.com/signadot/libconnect => ../libconnect/ //replace github.com/signadot/go-sdk => ../go-sdk diff --git a/go.sum b/go.sum index 009dda5..a29b3c1 100644 --- a/go.sum +++ b/go.sum @@ -456,8 +456,6 @@ 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.20260521222700-e386afcf25b5 h1:k3SBmNm0VsniuO89/FOfEsIefbgSP/1MoKaECmz/xWg= github.com/signadot/go-sdk v0.3.8-0.20260521222700-e386afcf25b5/go.mod h1:l7XHS9snZXC+dy11NwXd1Ef+Z+K0SBhBUm4LAiFBbFU= -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/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= From f74f615432e68ceaeff1b912d4898da52dbccc9b Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 8 Jun 2026 10:18:36 +0200 Subject: [PATCH 02/25] local: add --local-dns flag and EnableLocalDNS config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the opt-in `--local-dns` flag to `signadot local connect` and threads it through ConnectInvocationConfig.EnableLocalDNS (the JSON config that crosses the sudo boundary to the root-manager). The root-privilege rationale message reflects DNS-resolver configuration when the flag is set. No behavior yet — the root-manager wiring lands next. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/connect.go | 9 +++++++-- internal/config/local.go | 3 +++ internal/config/locald.go | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/command/local/connect.go b/internal/command/local/connect.go index 17d3b55..8183116 100644 --- a/internal/command/local/connect.go +++ b/internal/command/local/connect.go @@ -162,6 +162,7 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg }, Env: os.Environ(), ConnectionConfig: connConfig, + EnableLocalDNS: cfg.LocalDNS, ProxyURL: cfg.ProxyURL, APIURL: cfg.API.APIURL, APIKey: cfg.GetAPIKey(), @@ -209,9 +210,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{ diff --git a/internal/config/local.go b/internal/config/local.go index 2d4be2f..425f13f 100644 --- a/internal/config/local.go +++ b/internal/config/local.go @@ -102,6 +102,7 @@ type LocalConnect struct { // Flags Cluster string Unprivileged bool + LocalDNS bool Wait ConnectWait WaitTimeout time.Duration @@ -115,6 +116,8 @@ func (c *LocalConnect) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&c.Cluster, "cluster", "", "specify cluster connection config") cmd.Flags().BoolVar(&c.Unprivileged, "unprivileged", false, "run without root privileges") + cmd.Flags().BoolVar(&c.LocalDNS, "local-dns", false, + "resolve cluster service names with a local DNS resolver instead of /etc/hosts") cmd.Flags().Var(&c.Wait, "wait", "status to wait for while connecting {none,connect,sandboxes}") cmd.Flags().DurationVar(&c.WaitTimeout, "wait-timeout", 10*time.Second, "timeout to wait") diff --git a/internal/config/locald.go b/internal/config/locald.go index b54b61a..21a0fcf 100644 --- a/internal/config/locald.go +++ b/internal/config/locald.go @@ -76,6 +76,10 @@ type ConnectInvocationConfig struct { Env []string `json:"env"` VirtualIPNet string `json:"virtualIPNet"` ConnectionConfig *connectcfg.ConnectionConfig `json:"connectionConfig"` + + // EnableLocalDNS runs the libconnect localdns resolver (managing the OS + // resolver config) in place of the /etc/hosts mechanism for cluster names. + EnableLocalDNS bool `json:"enableLocalDNS,omitempty"` ProxyURL string `json:"proxyURL"` APIURL string `json:"apiURL"` APIKey string `json:"apiKey"` From d8ae0dfa7b623212cb6adb6f133ca7146679185d Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 8 Jun 2026 10:23:03 +0200 Subject: [PATCH 03/25] locald/rootmanager: run localdns in place of etchosts when --local-dns Wires the libconnect localdns resolver into the root-manager as the cluster-name resolution service, selected by ConnectInvocationConfig. EnableLocalDNS: - runNameService/stopNameService dispatch to localdns (ManageResolver + Forward) or the legacy etchosts based on the flag, used at both start sites (direct-SOCKS5 path and the tunnel-proxy monitor restart). - runLocalDNSService creates the injected tunnel-api client (CLI-owned) and starts localdns.New; stopLocalDNSService closes the service then the client. A start failure tears down localnet and fails the connect. - Teardown reorders name-resolution-before-NAT so localdns restores the OS resolver path before the cluster goes unreachable. - rootServer holds the localdns service + its apiclient alongside the existing localnet/etchosts slots. Status rendering for localdns lands separately. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 3 + go.sum | 4 ++ internal/locald/rootmanager/root_manager.go | 69 ++++++++++++++++++++- internal/locald/rootmanager/root_server.go | 21 ++++++- internal/locald/rootmanager/tp_monitor.go | 10 +-- 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 22386ab..9afbf60 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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.4 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect + golang.org/x/tools v0.44.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 diff --git a/go.sum b/go.sum index a29b3c1..b73ac97 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -579,6 +581,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.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= 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= diff --git a/internal/locald/rootmanager/root_manager.go b/internal/locald/rootmanager/root_manager.go index 52614ff..bcbca1d 100644 --- a/internal/locald/rootmanager/root_manager.go +++ b/internal/locald/rootmanager/root_manager.go @@ -15,9 +15,11 @@ import ( "github.com/signadot/cli/internal/config" rootapi "github.com/signadot/cli/internal/locald/api/rootmanager" "github.com/signadot/libconnect/apiv1" + "github.com/signadot/libconnect/common/apiclient" connectcfg "github.com/signadot/libconnect/config" "github.com/signadot/libconnect/fwdtun/etchosts" "github.com/signadot/libconnect/fwdtun/ipmap" + "github.com/signadot/libconnect/fwdtun/localdns" "github.com/signadot/libconnect/fwdtun/localnet" "google.golang.org/grpc" ) @@ -77,9 +79,12 @@ func (m *rootManager) Run(ctx context.Context) error { // starting/restarting the localnet and etchost services m.tpMonitor = NewTunnelProxyMonitor(ctx, m, ipMap) default: - // Start localnet and etchost services + // Start localnet and the name-resolution service (localdns or etchosts) m.runLocalnetService(ctx, m.ciConfig.ConnectionConfig.ProxyAddress, ipMap) - m.runEtcHostsService(ctx, m.ciConfig.ConnectionConfig.ProxyAddress, ipMap) + if err := m.runNameService(ctx, m.ciConfig.ConnectionConfig.ProxyAddress, ipMap); err != nil { + _ = m.stopLocalnetService() + return fmt.Errorf("error starting name resolution: %w", err) + } } // Wait until termination @@ -96,8 +101,10 @@ func (m *rootManager) Run(ctx context.Context) error { m.tpMonitor.Stop() } me = multierror.Append(me, m.sbmMonitor.stop()) + // Name resolution down before NAT: localdns restores the OS resolver path + // before the cluster becomes unreachable. (Harmless ordering for etchosts.) + me = multierror.Append(me, m.stopNameService()) me = multierror.Append(me, m.stopLocalnetService()) - me = multierror.Append(me, m.stopEtcHostsService()) return me.ErrorOrNil() } @@ -142,6 +149,62 @@ func (m *rootManager) runEtcHostsService(ctx context.Context, socks5Addr string, m.root.setEtcHostsService(etcHostsSVC) } +// runNameService starts cluster-name resolution: the localdns resolver when +// --local-dns is set, otherwise the legacy /etc/hosts mechanism. +func (m *rootManager) runNameService(ctx context.Context, socks5Addr string, ipMap *ipmap.IPMap) error { + if m.ciConfig.EnableLocalDNS { + return m.runLocalDNSService(ctx, socks5Addr, ipMap) + } + m.runEtcHostsService(ctx, socks5Addr, ipMap) + return nil +} + +// stopNameService stops whichever name-resolution service is running. Both stop +// helpers are no-ops when their service was never started. +func (m *rootManager) stopNameService() error { + var me *multierror.Error + me = multierror.Append(me, m.stopLocalDNSService()) + me = multierror.Append(me, m.stopEtcHostsService()) + return me.ErrorOrNil() +} + +func (m *rootManager) runLocalDNSService(ctx context.Context, socks5Addr string, ipMap *ipmap.IPMap) error { + // localdns takes an injected tunnel-api client (unlike etchosts, which + // builds its own); the CLI owns it and closes it on teardown. + apiClient, err := apiclient.NewClient(socks5Addr, apiclient.DefaultClientKeepaliveParams()) + if err != nil { + return fmt.Errorf("creating tunnel-api client for localdns: %w", err) + } + svc, err := localdns.New(localdns.Config{ + ManageResolver: true, // install OS resolver config (we are root) + Forward: true, // forward non-cluster queries to captured upstreams + LocalNetPath: m.ciConfig.LocalNetPath, + Log: m.log, + // BindIP/BindPort left zero: localdns picks 127.0.0.54:53 on Linux, + // an ephemeral 127.0.0.1 port on macOS. + }, apiClient, ipMap) + if err != nil { + _ = apiClient.Close() + return fmt.Errorf("starting localdns: %w", err) + } + m.root.setLocalDNSService(svc, apiClient) + return nil +} + +func (m *rootManager) stopLocalDNSService() error { + svc, apiClient := m.root.getLocalDNSService() + if svc == nil { + return nil + } + m.root.setLocalDNSService(nil, nil) + var me *multierror.Error + me = multierror.Append(me, svc.Close()) + if apiClient != nil { + me = multierror.Append(me, apiClient.Close()) + } + return me.ErrorOrNil() +} + func (m *rootManager) stopEtcHostsService() error { etcHostsSVC := m.root.getEtcHostsService() if etcHostsSVC != nil { diff --git a/internal/locald/rootmanager/root_server.go b/internal/locald/rootmanager/root_server.go index 5268bd8..77d8bcb 100644 --- a/internal/locald/rootmanager/root_server.go +++ b/internal/locald/rootmanager/root_server.go @@ -6,7 +6,9 @@ import ( commonapi "github.com/signadot/cli/internal/locald/api" rootapi "github.com/signadot/cli/internal/locald/api/rootmanager" + "github.com/signadot/libconnect/common/apiclient" "github.com/signadot/libconnect/fwdtun/etchosts" + "github.com/signadot/libconnect/fwdtun/localdns" "github.com/signadot/libconnect/fwdtun/localnet" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -17,7 +19,11 @@ type rootServer struct { mu sync.RWMutex localnetSVC *localnet.Service etcHostsSVC *etchosts.EtcHosts - shutdownCh chan struct{} + // localDNSSVC is the alternative to etcHostsSVC, selected by --local-dns. + // The CLI owns the injected tunnel-api client and closes it on teardown. + localDNSSVC *localdns.Service + localDNSAPIClient apiclient.Client + shutdownCh chan struct{} } var _ rootapi.RootManagerAPIServer = &rootServer{} @@ -116,3 +122,16 @@ func (s *rootServer) getEtcHostsService() *etchosts.EtcHosts { defer s.mu.RUnlock() return s.etcHostsSVC } + +func (s *rootServer) setLocalDNSService(svc *localdns.Service, apiClient apiclient.Client) { + s.mu.Lock() + defer s.mu.Unlock() + s.localDNSSVC = svc + s.localDNSAPIClient = apiClient +} + +func (s *rootServer) getLocalDNSService() (*localdns.Service, apiclient.Client) { + s.mu.RLock() + defer s.mu.RUnlock() + return s.localDNSSVC, s.localDNSAPIClient +} diff --git a/internal/locald/rootmanager/tp_monitor.go b/internal/locald/rootmanager/tp_monitor.go index 868f976..bd16070 100644 --- a/internal/locald/rootmanager/tp_monitor.go +++ b/internal/locald/rootmanager/tp_monitor.go @@ -160,15 +160,17 @@ func (mon *tpMonitor) checkTunnelProxyAccess(ctx context.Context) (success bool, return true, false } - mon.log.Info("restarting localnet and etchosts services") + mon.log.Info("restarting localnet and name-resolution services") // Restart localnet mon.root.stopLocalnetService() mon.root.runLocalnetService(ctx, mon.tpLocalAddr, mon.ipMap) - // Restart etc hosts - mon.root.stopEtcHostsService() - mon.root.runEtcHostsService(ctx, mon.tpLocalAddr, mon.ipMap) + // Restart name resolution (localdns or etchosts) + mon.root.stopNameService() + if err := mon.root.runNameService(ctx, mon.tpLocalAddr, mon.ipMap); err != nil { + mon.log.Error("error starting name resolution; will retry", "error", err) + } return false, true } From b3b7cd056f571f9f1e354440b69c91252d6d41ef Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 8 Jun 2026 10:37:03 +0200 Subject: [PATCH 04/25] local: surface localdns in `local status` Add a LocalDNSStatus message to common.proto and a field on both the root-manager and sandbox-manager StatusResponses (regenerated). The root-manager populates it from localdns.Service.Status(); the sandbox manager forwards it; the CLI renders it. `local status` shows the local DNS resolver (record count, bind address, and a prominent warning when a system DNS manager may revert resolv.conf) in place of the /etc/hosts line when --local-dns is active, in both the table and JSON/details output. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/printers.go | 83 +++- internal/locald/api/common.pb.go | 433 ++++++++++++------ internal/locald/api/common.proto | 13 + .../api/rootmanager/root_manager_api.pb.go | 82 ++-- .../api/rootmanager/root_manager_api.proto | 1 + .../sandboxmanager/sandbox_manager_api.pb.go | 138 +++--- .../sandboxmanager/sandbox_manager_api.proto | 1 + internal/locald/rootmanager/root_server.go | 32 ++ .../locald/sandboxmanager/sandbox_server.go | 12 +- 9 files changed, 550 insertions(+), 245 deletions(-) diff --git a/internal/command/local/printers.go b/internal/command/local/printers.go index 12b1432..a51ddcc 100644 --- a/internal/command/local/printers.go +++ b/internal/command/local/printers.go @@ -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"` @@ -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), @@ -219,6 +221,46 @@ func getRawHosts(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConf return result } +func getRawLocalDNS(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationConfig, + 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"` + 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, + 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 { @@ -437,7 +479,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() @@ -505,6 +551,41 @@ 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 { + p.printLine(p.out, 1, fmt.Sprintf("%d cluster names resolvable via local DNS (%s)", + ldns.RecordCount, ldns.BindAddr), "*") + } 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 { diff --git a/internal/locald/api/common.pb.go b/internal/locald/api/common.pb.go index fd5a311..fbfd6b4 100644 --- a/internal/locald/api/common.pb.go +++ b/internal/locald/api/common.pb.go @@ -229,6 +229,118 @@ func (x *HostsStatus) GetLastUpdateTime() *timestamppb.Timestamp { return nil } +// LocalDNS status (produced by root controller) +type LocalDNSStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Health *ServiceHealth `protobuf:"bytes,1,opt,name=health,proto3" json:"health,omitempty"` + Mode string `protobuf:"bytes,2,opt,name=mode,proto3" json:"mode,omitempty"` + BindAddr string `protobuf:"bytes,3,opt,name=bind_addr,json=bindAddr,proto3" json:"bind_addr,omitempty"` + Suffixes []string `protobuf:"bytes,4,rep,name=suffixes,proto3" json:"suffixes,omitempty"` + Upstreams []string `protobuf:"bytes,5,rep,name=upstreams,proto3" json:"upstreams,omitempty"` + RecordCount uint32 `protobuf:"varint,6,opt,name=record_count,json=recordCount,proto3" json:"record_count,omitempty"` + LastRefresh *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_refresh,json=lastRefresh,proto3" json:"last_refresh,omitempty"` + ResolvConfManaged bool `protobuf:"varint,8,opt,name=resolv_conf_managed,json=resolvConfManaged,proto3" json:"resolv_conf_managed,omitempty"` + Warning string `protobuf:"bytes,9,opt,name=warning,proto3" json:"warning,omitempty"` +} + +func (x *LocalDNSStatus) Reset() { + *x = LocalDNSStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LocalDNSStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LocalDNSStatus) ProtoMessage() {} + +func (x *LocalDNSStatus) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_common_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LocalDNSStatus.ProtoReflect.Descriptor instead. +func (*LocalDNSStatus) Descriptor() ([]byte, []int) { + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{3} +} + +func (x *LocalDNSStatus) GetHealth() *ServiceHealth { + if x != nil { + return x.Health + } + return nil +} + +func (x *LocalDNSStatus) GetMode() string { + if x != nil { + return x.Mode + } + return "" +} + +func (x *LocalDNSStatus) GetBindAddr() string { + if x != nil { + return x.BindAddr + } + return "" +} + +func (x *LocalDNSStatus) GetSuffixes() []string { + if x != nil { + return x.Suffixes + } + return nil +} + +func (x *LocalDNSStatus) GetUpstreams() []string { + if x != nil { + return x.Upstreams + } + return nil +} + +func (x *LocalDNSStatus) GetRecordCount() uint32 { + if x != nil { + return x.RecordCount + } + return 0 +} + +func (x *LocalDNSStatus) GetLastRefresh() *timestamppb.Timestamp { + if x != nil { + return x.LastRefresh + } + return nil +} + +func (x *LocalDNSStatus) GetResolvConfManaged() bool { + if x != nil { + return x.ResolvConfManaged + } + return false +} + +func (x *LocalDNSStatus) GetWarning() string { + if x != nil { + return x.Warning + } + return "" +} + // PortForward status (produced by local controller) type PortForwardStatus struct { state protoimpl.MessageState @@ -242,7 +354,7 @@ type PortForwardStatus struct { func (x *PortForwardStatus) Reset() { *x = PortForwardStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[3] + mi := &file_internal_locald_api_common_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -255,7 +367,7 @@ func (x *PortForwardStatus) String() string { func (*PortForwardStatus) ProtoMessage() {} func (x *PortForwardStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[3] + mi := &file_internal_locald_api_common_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -268,7 +380,7 @@ func (x *PortForwardStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PortForwardStatus.ProtoReflect.Descriptor instead. func (*PortForwardStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{3} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{4} } func (x *PortForwardStatus) GetHealth() *ServiceHealth { @@ -298,7 +410,7 @@ type ControlPlaneProxyStatus struct { func (x *ControlPlaneProxyStatus) Reset() { *x = ControlPlaneProxyStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[4] + mi := &file_internal_locald_api_common_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -311,7 +423,7 @@ func (x *ControlPlaneProxyStatus) String() string { func (*ControlPlaneProxyStatus) ProtoMessage() {} func (x *ControlPlaneProxyStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[4] + mi := &file_internal_locald_api_common_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -324,7 +436,7 @@ func (x *ControlPlaneProxyStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ControlPlaneProxyStatus.ProtoReflect.Descriptor instead. func (*ControlPlaneProxyStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{4} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{5} } func (x *ControlPlaneProxyStatus) GetHealth() *ServiceHealth { @@ -353,7 +465,7 @@ type WatcherStatus struct { func (x *WatcherStatus) Reset() { *x = WatcherStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[5] + mi := &file_internal_locald_api_common_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -366,7 +478,7 @@ func (x *WatcherStatus) String() string { func (*WatcherStatus) ProtoMessage() {} func (x *WatcherStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[5] + mi := &file_internal_locald_api_common_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -379,7 +491,7 @@ func (x *WatcherStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WatcherStatus.ProtoReflect.Descriptor instead. func (*WatcherStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{5} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6} } func (x *WatcherStatus) GetHealth() *ServiceHealth { @@ -403,7 +515,7 @@ type SandboxStatus struct { func (x *SandboxStatus) Reset() { *x = SandboxStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[6] + mi := &file_internal_locald_api_common_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -416,7 +528,7 @@ func (x *SandboxStatus) String() string { func (*SandboxStatus) ProtoMessage() {} func (x *SandboxStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[6] + mi := &file_internal_locald_api_common_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -429,7 +541,7 @@ func (x *SandboxStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. func (*SandboxStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7} } func (x *SandboxStatus) GetName() string { @@ -466,7 +578,7 @@ type OperatorInfo struct { func (x *OperatorInfo) Reset() { *x = OperatorInfo{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[7] + mi := &file_internal_locald_api_common_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -479,7 +591,7 @@ func (x *OperatorInfo) String() string { func (*OperatorInfo) ProtoMessage() {} func (x *OperatorInfo) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[7] + mi := &file_internal_locald_api_common_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -492,7 +604,7 @@ func (x *OperatorInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OperatorInfo.ProtoReflect.Descriptor instead. func (*OperatorInfo) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8} } func (x *OperatorInfo) GetVersion() string { @@ -537,7 +649,7 @@ type DevboxSessionStatus struct { func (x *DevboxSessionStatus) Reset() { *x = DevboxSessionStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[8] + mi := &file_internal_locald_api_common_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -550,7 +662,7 @@ func (x *DevboxSessionStatus) String() string { func (*DevboxSessionStatus) ProtoMessage() {} func (x *DevboxSessionStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[8] + mi := &file_internal_locald_api_common_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -563,7 +675,7 @@ func (x *DevboxSessionStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use DevboxSessionStatus.ProtoReflect.Descriptor instead. func (*DevboxSessionStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{9} } func (x *DevboxSessionStatus) GetHealthy() bool { @@ -615,7 +727,7 @@ type SandboxStatus_Baseline struct { func (x *SandboxStatus_Baseline) Reset() { *x = SandboxStatus_Baseline{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[9] + mi := &file_internal_locald_api_common_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -628,7 +740,7 @@ func (x *SandboxStatus_Baseline) String() string { func (*SandboxStatus_Baseline) ProtoMessage() {} func (x *SandboxStatus_Baseline) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[9] + mi := &file_internal_locald_api_common_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -641,7 +753,7 @@ func (x *SandboxStatus_Baseline) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_Baseline.ProtoReflect.Descriptor instead. func (*SandboxStatus_Baseline) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6, 0} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 0} } func (x *SandboxStatus_Baseline) GetApiVersion() string { @@ -684,7 +796,7 @@ type SandboxStatus_BaselineToLocal struct { func (x *SandboxStatus_BaselineToLocal) Reset() { *x = SandboxStatus_BaselineToLocal{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[10] + mi := &file_internal_locald_api_common_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -697,7 +809,7 @@ func (x *SandboxStatus_BaselineToLocal) String() string { func (*SandboxStatus_BaselineToLocal) ProtoMessage() {} func (x *SandboxStatus_BaselineToLocal) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[10] + mi := &file_internal_locald_api_common_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -710,7 +822,7 @@ func (x *SandboxStatus_BaselineToLocal) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_BaselineToLocal.ProtoReflect.Descriptor instead. func (*SandboxStatus_BaselineToLocal) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6, 1} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 1} } func (x *SandboxStatus_BaselineToLocal) GetBaselinePort() int32 { @@ -746,7 +858,7 @@ type SandboxStatus_LocalWorkload struct { func (x *SandboxStatus_LocalWorkload) Reset() { *x = SandboxStatus_LocalWorkload{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[11] + mi := &file_internal_locald_api_common_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -759,7 +871,7 @@ func (x *SandboxStatus_LocalWorkload) String() string { func (*SandboxStatus_LocalWorkload) ProtoMessage() {} func (x *SandboxStatus_LocalWorkload) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[11] + mi := &file_internal_locald_api_common_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -772,7 +884,7 @@ func (x *SandboxStatus_LocalWorkload) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_LocalWorkload.ProtoReflect.Descriptor instead. func (*SandboxStatus_LocalWorkload) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6, 2} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 2} } func (x *SandboxStatus_LocalWorkload) GetName() string { @@ -843,87 +955,109 @@ var file_internal_locald_api_common_proto_rawDesc = []byte{ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x22, 0x6a, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, - 0x64, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, - 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, - 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, - 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x41, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x22, 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, - 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, - 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, - 0x0f, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, - 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, - 0x65, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, + 0x6d, 0x65, 0x22, 0xd9, 0x02, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62, + 0x69, 0x6e, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x62, 0x69, 0x6e, 0x64, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x75, 0x66, 0x66, + 0x69, 0x78, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x75, 0x66, 0x66, + 0x69, 0x78, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3d, 0x0a, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x5f, 0x63, + 0x6f, 0x6e, 0x66, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x6a, + 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, - 0x6f, 0x63, 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x3d, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, - 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, - 0x5a, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, - 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, - 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, - 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, - 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, - 0x6e, 0x6e, 0x65, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, - 0x74, 0x65, 0x22, 0xdb, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, - 0x61, 0x6c, 0x74, 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, - 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, - 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, - 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x41, 0x0a, 0x0d, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, + 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x57, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x57, 0x6f, + 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, 0x73, 0x65, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, 0x42, 0x61, 0x73, + 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x72, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x08, + 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, + 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, 0x0a, 0x13, 0x77, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, + 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0xdb, + 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, + 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, + 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x2d, 0x5a, 0x2b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -938,40 +1072,43 @@ func file_internal_locald_api_common_proto_rawDescGZIP() []byte { return file_internal_locald_api_common_proto_rawDescData } -var file_internal_locald_api_common_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_internal_locald_api_common_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_internal_locald_api_common_proto_goTypes = []interface{}{ (*ServiceHealth)(nil), // 0: apicommon.ServiceHealth (*LocalNetStatus)(nil), // 1: apicommon.LocalNetStatus (*HostsStatus)(nil), // 2: apicommon.HostsStatus - (*PortForwardStatus)(nil), // 3: apicommon.PortForwardStatus - (*ControlPlaneProxyStatus)(nil), // 4: apicommon.ControlPlaneProxyStatus - (*WatcherStatus)(nil), // 5: apicommon.WatcherStatus - (*SandboxStatus)(nil), // 6: apicommon.SandboxStatus - (*OperatorInfo)(nil), // 7: apicommon.OperatorInfo - (*DevboxSessionStatus)(nil), // 8: apicommon.DevboxSessionStatus - (*SandboxStatus_Baseline)(nil), // 9: apicommon.SandboxStatus.Baseline - (*SandboxStatus_BaselineToLocal)(nil), // 10: apicommon.SandboxStatus.BaselineToLocal - (*SandboxStatus_LocalWorkload)(nil), // 11: apicommon.SandboxStatus.LocalWorkload - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp + (*LocalDNSStatus)(nil), // 3: apicommon.LocalDNSStatus + (*PortForwardStatus)(nil), // 4: apicommon.PortForwardStatus + (*ControlPlaneProxyStatus)(nil), // 5: apicommon.ControlPlaneProxyStatus + (*WatcherStatus)(nil), // 6: apicommon.WatcherStatus + (*SandboxStatus)(nil), // 7: apicommon.SandboxStatus + (*OperatorInfo)(nil), // 8: apicommon.OperatorInfo + (*DevboxSessionStatus)(nil), // 9: apicommon.DevboxSessionStatus + (*SandboxStatus_Baseline)(nil), // 10: apicommon.SandboxStatus.Baseline + (*SandboxStatus_BaselineToLocal)(nil), // 11: apicommon.SandboxStatus.BaselineToLocal + (*SandboxStatus_LocalWorkload)(nil), // 12: apicommon.SandboxStatus.LocalWorkload + (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp } var file_internal_locald_api_common_proto_depIdxs = []int32{ - 12, // 0: apicommon.ServiceHealth.last_error_time:type_name -> google.protobuf.Timestamp + 13, // 0: apicommon.ServiceHealth.last_error_time:type_name -> google.protobuf.Timestamp 0, // 1: apicommon.LocalNetStatus.health:type_name -> apicommon.ServiceHealth 0, // 2: apicommon.HostsStatus.health:type_name -> apicommon.ServiceHealth - 12, // 3: apicommon.HostsStatus.last_update_time:type_name -> google.protobuf.Timestamp - 0, // 4: apicommon.PortForwardStatus.health:type_name -> apicommon.ServiceHealth - 0, // 5: apicommon.ControlPlaneProxyStatus.health:type_name -> apicommon.ServiceHealth - 0, // 6: apicommon.WatcherStatus.health:type_name -> apicommon.ServiceHealth - 11, // 7: apicommon.SandboxStatus.local_workloads:type_name -> apicommon.SandboxStatus.LocalWorkload - 12, // 8: apicommon.DevboxSessionStatus.last_error_time:type_name -> google.protobuf.Timestamp - 9, // 9: apicommon.SandboxStatus.LocalWorkload.baseline:type_name -> apicommon.SandboxStatus.Baseline - 10, // 10: apicommon.SandboxStatus.LocalWorkload.workloadPortMapping:type_name -> apicommon.SandboxStatus.BaselineToLocal - 0, // 11: apicommon.SandboxStatus.LocalWorkload.tunnel_health:type_name -> apicommon.ServiceHealth - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 13, // 3: apicommon.HostsStatus.last_update_time:type_name -> google.protobuf.Timestamp + 0, // 4: apicommon.LocalDNSStatus.health:type_name -> apicommon.ServiceHealth + 13, // 5: apicommon.LocalDNSStatus.last_refresh:type_name -> google.protobuf.Timestamp + 0, // 6: apicommon.PortForwardStatus.health:type_name -> apicommon.ServiceHealth + 0, // 7: apicommon.ControlPlaneProxyStatus.health:type_name -> apicommon.ServiceHealth + 0, // 8: apicommon.WatcherStatus.health:type_name -> apicommon.ServiceHealth + 12, // 9: apicommon.SandboxStatus.local_workloads:type_name -> apicommon.SandboxStatus.LocalWorkload + 13, // 10: apicommon.DevboxSessionStatus.last_error_time:type_name -> google.protobuf.Timestamp + 10, // 11: apicommon.SandboxStatus.LocalWorkload.baseline:type_name -> apicommon.SandboxStatus.Baseline + 11, // 12: apicommon.SandboxStatus.LocalWorkload.workloadPortMapping:type_name -> apicommon.SandboxStatus.BaselineToLocal + 0, // 13: apicommon.SandboxStatus.LocalWorkload.tunnel_health:type_name -> apicommon.ServiceHealth + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_internal_locald_api_common_proto_init() } @@ -1017,7 +1154,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PortForwardStatus); i { + switch v := v.(*LocalDNSStatus); i { case 0: return &v.state case 1: @@ -1029,7 +1166,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ControlPlaneProxyStatus); i { + switch v := v.(*PortForwardStatus); i { case 0: return &v.state case 1: @@ -1041,7 +1178,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WatcherStatus); i { + switch v := v.(*ControlPlaneProxyStatus); i { case 0: return &v.state case 1: @@ -1053,7 +1190,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus); i { + switch v := v.(*WatcherStatus); i { case 0: return &v.state case 1: @@ -1065,7 +1202,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OperatorInfo); i { + switch v := v.(*SandboxStatus); i { case 0: return &v.state case 1: @@ -1077,7 +1214,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DevboxSessionStatus); i { + switch v := v.(*OperatorInfo); i { case 0: return &v.state case 1: @@ -1089,7 +1226,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus_Baseline); i { + switch v := v.(*DevboxSessionStatus); i { case 0: return &v.state case 1: @@ -1101,7 +1238,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus_BaselineToLocal); i { + switch v := v.(*SandboxStatus_Baseline); i { case 0: return &v.state case 1: @@ -1113,6 +1250,18 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SandboxStatus_BaselineToLocal); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_locald_api_common_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SandboxStatus_LocalWorkload); i { case 0: return &v.state @@ -1131,7 +1280,7 @@ func file_internal_locald_api_common_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_locald_api_common_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 13, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/locald/api/common.proto b/internal/locald/api/common.proto index 9e63fd5..b253d1d 100644 --- a/internal/locald/api/common.proto +++ b/internal/locald/api/common.proto @@ -30,6 +30,19 @@ message HostsStatus { google.protobuf.Timestamp last_update_time = 4; } +// LocalDNS status (produced by root controller) +message LocalDNSStatus { + ServiceHealth health = 1; + string mode = 2; + string bind_addr = 3; + repeated string suffixes = 4; + repeated string upstreams = 5; + uint32 record_count = 6; + google.protobuf.Timestamp last_refresh = 7; + bool resolv_conf_managed = 8; + string warning = 9; +} + // PortForward status (produced by local controller) message PortForwardStatus { ServiceHealth health = 1; diff --git a/internal/locald/api/rootmanager/root_manager_api.pb.go b/internal/locald/api/rootmanager/root_manager_api.pb.go index 585f5c4..3923f29 100644 --- a/internal/locald/api/rootmanager/root_manager_api.pb.go +++ b/internal/locald/api/rootmanager/root_manager_api.pb.go @@ -66,6 +66,7 @@ type StatusResponse struct { Localnet *api.LocalNetStatus `protobuf:"bytes,1,opt,name=localnet,proto3" json:"localnet,omitempty"` Hosts *api.HostsStatus `protobuf:"bytes,2,opt,name=hosts,proto3" json:"hosts,omitempty"` + LocalDns *api.LocalDNSStatus `protobuf:"bytes,3,opt,name=local_dns,json=localDns,proto3" json:"local_dns,omitempty"` } func (x *StatusResponse) Reset() { @@ -114,6 +115,13 @@ func (x *StatusResponse) GetHosts() *api.HostsStatus { return nil } +func (x *StatusResponse) GetLocalDns() *api.LocalDNSStatus { + if x != nil { + return x.LocalDns + } + return nil +} + type ShutdownRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -200,31 +208,35 @@ var file_internal_locald_api_rootmanager_root_manager_api_proto_rawDesc = []byte 0x6e, 0x61, 0x67, 0x65, 0x72, 0x1a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x75, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, - 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, 0x65, - 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x6e, 0x65, - 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x48, 0x6f, 0x73, - 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x22, - 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa0, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x74, 0x4d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, 0x50, 0x49, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, - 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6f, - 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, - 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xad, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x6e, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, + 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4e, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x6e, + 0x65, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x48, 0x6f, + 0x73, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x68, 0x6f, 0x73, 0x74, 0x73, + 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x64, 0x6e, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x6e, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, + 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0xa0, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, + 0x50, 0x49, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x72, + 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, + 0x6f, 0x77, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, + 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, + 0x69, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -247,19 +259,21 @@ var file_internal_locald_api_rootmanager_root_manager_api_proto_goTypes = []inte (*ShutdownResponse)(nil), // 3: rootmanager.ShutdownResponse (*api.LocalNetStatus)(nil), // 4: apicommon.LocalNetStatus (*api.HostsStatus)(nil), // 5: apicommon.HostsStatus + (*api.LocalDNSStatus)(nil), // 6: apicommon.LocalDNSStatus } var file_internal_locald_api_rootmanager_root_manager_api_proto_depIdxs = []int32{ 4, // 0: rootmanager.StatusResponse.localnet:type_name -> apicommon.LocalNetStatus 5, // 1: rootmanager.StatusResponse.hosts:type_name -> apicommon.HostsStatus - 0, // 2: rootmanager.RootManagerAPI.Status:input_type -> rootmanager.StatusRequest - 2, // 3: rootmanager.RootManagerAPI.Shutdown:input_type -> rootmanager.ShutdownRequest - 1, // 4: rootmanager.RootManagerAPI.Status:output_type -> rootmanager.StatusResponse - 3, // 5: rootmanager.RootManagerAPI.Shutdown:output_type -> rootmanager.ShutdownResponse - 4, // [4:6] is the sub-list for method output_type - 2, // [2:4] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 6, // 2: rootmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus + 0, // 3: rootmanager.RootManagerAPI.Status:input_type -> rootmanager.StatusRequest + 2, // 4: rootmanager.RootManagerAPI.Shutdown:input_type -> rootmanager.ShutdownRequest + 1, // 5: rootmanager.RootManagerAPI.Status:output_type -> rootmanager.StatusResponse + 3, // 6: rootmanager.RootManagerAPI.Shutdown:output_type -> rootmanager.ShutdownResponse + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_internal_locald_api_rootmanager_root_manager_api_proto_init() } diff --git a/internal/locald/api/rootmanager/root_manager_api.proto b/internal/locald/api/rootmanager/root_manager_api.proto index 06a2a7e..663ff1a 100644 --- a/internal/locald/api/rootmanager/root_manager_api.proto +++ b/internal/locald/api/rootmanager/root_manager_api.proto @@ -24,6 +24,7 @@ message StatusRequest { message StatusResponse { apicommon.LocalNetStatus localnet = 1; apicommon.HostsStatus hosts = 2; + apicommon.LocalDNSStatus local_dns = 3; } // Shutdown diff --git a/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go b/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go index 73ba0b3..f6f657e 100644 --- a/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go +++ b/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go @@ -76,6 +76,7 @@ type StatusResponse struct { Watcher *api.WatcherStatus `protobuf:"bytes,6,opt,name=watcher,proto3" json:"watcher,omitempty"` Sandboxes []*api.SandboxStatus `protobuf:"bytes,5,rep,name=sandboxes,proto3" json:"sandboxes,omitempty"` DevboxSession *api.DevboxSessionStatus `protobuf:"bytes,9,opt,name=devbox_session,json=devboxSession,proto3" json:"devbox_session,omitempty"` + LocalDns *api.LocalDNSStatus `protobuf:"bytes,10,opt,name=local_dns,json=localDns,proto3" json:"local_dns,omitempty"` } func (x *StatusResponse) Reset() { @@ -173,6 +174,13 @@ func (x *StatusResponse) GetDevboxSession() *api.DevboxSessionStatus { return nil } +func (x *StatusResponse) GetLocalDns() *api.LocalDNSStatus { + if x != nil { + return x.LocalDns + } + return nil +} + type ShutdownRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -468,7 +476,7 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDesc = 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xb0, 0x04, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0xe8, 0x04, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x09, 0x63, 0x69, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, @@ -503,54 +511,58 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDesc = 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0d, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4b, 0x0a, 0x19, 0x47, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x52, 0x6f, 0x75, 0x74, - 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x22, 0x68, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, - 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x22, 0x74, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x07, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x3c, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x32, 0x9e, 0x02, 0x0a, 0x11, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, - 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, 0x50, 0x49, 0x12, 0x49, 0x0a, 0x06, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, - 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, - 0x6e, 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x73, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, - 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, - 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x64, 0x6e, 0x73, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x6e, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, + 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, + 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x4b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x13, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x73, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x22, 0x68, + 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x10, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x74, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x3c, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x3c, + 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x9e, 0x02, 0x0a, + 0x11, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, + 0x50, 0x49, 0x12, 0x49, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x73, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x61, + 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, + 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, + 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6d, + 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x2a, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3c, 0x5a, + 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -584,6 +596,7 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_goTypes = (*api.WatcherStatus)(nil), // 14: apicommon.WatcherStatus (*api.SandboxStatus)(nil), // 15: apicommon.SandboxStatus (*api.DevboxSessionStatus)(nil), // 16: apicommon.DevboxSessionStatus + (*api.LocalDNSStatus)(nil), // 17: apicommon.LocalDNSStatus } var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_depIdxs = []int32{ 8, // 0: sandboxmanager.StatusResponse.ci_config:type_name -> google.protobuf.Struct @@ -595,19 +608,20 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_depIdxs = 14, // 6: sandboxmanager.StatusResponse.watcher:type_name -> apicommon.WatcherStatus 15, // 7: sandboxmanager.StatusResponse.sandboxes:type_name -> apicommon.SandboxStatus 16, // 8: sandboxmanager.StatusResponse.devbox_session:type_name -> apicommon.DevboxSessionStatus - 6, // 9: sandboxmanager.GetResourceOutputsResponse.resource_outputs:type_name -> sandboxmanager.ResourceOutputs - 7, // 10: sandboxmanager.ResourceOutputs.outputs:type_name -> sandboxmanager.ResourceOutputItem - 0, // 11: sandboxmanager.SandboxManagerAPI.Status:input_type -> sandboxmanager.StatusRequest - 2, // 12: sandboxmanager.SandboxManagerAPI.Shutdown:input_type -> sandboxmanager.ShutdownRequest - 4, // 13: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:input_type -> sandboxmanager.GetResourceOutputsRequest - 1, // 14: sandboxmanager.SandboxManagerAPI.Status:output_type -> sandboxmanager.StatusResponse - 3, // 15: sandboxmanager.SandboxManagerAPI.Shutdown:output_type -> sandboxmanager.ShutdownResponse - 5, // 16: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:output_type -> sandboxmanager.GetResourceOutputsResponse - 14, // [14:17] is the sub-list for method output_type - 11, // [11:14] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 17, // 9: sandboxmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus + 6, // 10: sandboxmanager.GetResourceOutputsResponse.resource_outputs:type_name -> sandboxmanager.ResourceOutputs + 7, // 11: sandboxmanager.ResourceOutputs.outputs:type_name -> sandboxmanager.ResourceOutputItem + 0, // 12: sandboxmanager.SandboxManagerAPI.Status:input_type -> sandboxmanager.StatusRequest + 2, // 13: sandboxmanager.SandboxManagerAPI.Shutdown:input_type -> sandboxmanager.ShutdownRequest + 4, // 14: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:input_type -> sandboxmanager.GetResourceOutputsRequest + 1, // 15: sandboxmanager.SandboxManagerAPI.Status:output_type -> sandboxmanager.StatusResponse + 3, // 16: sandboxmanager.SandboxManagerAPI.Shutdown:output_type -> sandboxmanager.ShutdownResponse + 5, // 17: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:output_type -> sandboxmanager.GetResourceOutputsResponse + 15, // [15:18] is the sub-list for method output_type + 12, // [12:15] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_init() } diff --git a/internal/locald/api/sandboxmanager/sandbox_manager_api.proto b/internal/locald/api/sandboxmanager/sandbox_manager_api.proto index 23f58a8..f0ca265 100644 --- a/internal/locald/api/sandboxmanager/sandbox_manager_api.proto +++ b/internal/locald/api/sandboxmanager/sandbox_manager_api.proto @@ -38,6 +38,7 @@ message StatusResponse { apicommon.WatcherStatus watcher = 6; repeated apicommon.SandboxStatus sandboxes = 5; apicommon.DevboxSessionStatus devbox_session = 9; + apicommon.LocalDNSStatus local_dns = 10; } // Shutdown diff --git a/internal/locald/rootmanager/root_server.go b/internal/locald/rootmanager/root_server.go index 77d8bcb..ce3274b 100644 --- a/internal/locald/rootmanager/root_server.go +++ b/internal/locald/rootmanager/root_server.go @@ -83,9 +83,41 @@ func (s *rootServer) Status(ctx context.Context, req *rootapi.StatusRequest) (*r } } + // Local DNS + var localDNSSt *commonapi.LocalDNSStatus + if s.localDNSSVC != nil { + status := s.localDNSSVC.Status() + + var lastErrortime *timestamppb.Timestamp + if status.LastErrorTime != nil { + lastErrortime = timestamppb.New(*status.LastErrorTime) + } + var lastRefresh *timestamppb.Timestamp + if status.LastRefresh != nil { + lastRefresh = timestamppb.New(*status.LastRefresh) + } + localDNSSt = &commonapi.LocalDNSStatus{ + Health: &commonapi.ServiceHealth{ + Healthy: status.Healthy, + ErrorCount: uint32(status.ErrorCount), + LastErrorReason: status.LastErrorReason, + LastErrorTime: lastErrortime, + }, + Mode: status.Mode, + BindAddr: status.BindAddr, + Suffixes: status.Suffixes, + Upstreams: status.Upstreams, + RecordCount: uint32(status.RecordCount), + LastRefresh: lastRefresh, + ResolvConfManaged: status.ResolvConfManaged, + Warning: status.Warning, + } + } + resp := &rootapi.StatusResponse{ Localnet: localnetSt, Hosts: etcHostsSt, + LocalDns: localDNSSt, } return resp, nil } diff --git a/internal/locald/sandboxmanager/sandbox_server.go b/internal/locald/sandboxmanager/sandbox_server.go index 8f2799f..1b7bbf7 100644 --- a/internal/locald/sandboxmanager/sandbox_server.go +++ b/internal/locald/sandboxmanager/sandbox_server.go @@ -87,7 +87,7 @@ func (s *sbmServer) Status(ctx context.Context, req *sbapi.StatusRequest) (*sbap Sandboxes: s.sbStatuses(), DevboxSession: s.devboxSessionStatus(), } - resp.Hosts, resp.Localnet = s.rootStatus() + resp.Hosts, resp.Localnet, resp.LocalDns = s.rootStatus() return resp, nil } @@ -125,16 +125,16 @@ func (s *sbmServer) GetResourceOutputs(ctx context.Context, req *sbapi.GetResour return res, nil } -func (s *sbmServer) rootStatus() (*commonapi.HostsStatus, *commonapi.LocalNetStatus) { +func (s *sbmServer) rootStatus() (*commonapi.HostsStatus, *commonapi.LocalNetStatus, *commonapi.LocalDNSStatus) { if !s.ciConfig.WithRootManager { // We are running without a root manager - return nil, nil + return nil, nil, nil } rootClient := s.getRootClient() if rootClient == nil { s.log.Debug("no root client available for rootStatus()") - return &commonapi.HostsStatus{}, &commonapi.LocalNetStatus{} + return &commonapi.HostsStatus{}, &commonapi.LocalNetStatus{}, nil } req := &rootapi.StatusRequest{} ctx, cancel := context.WithTimeout(context.Background(), @@ -143,9 +143,9 @@ func (s *sbmServer) rootStatus() (*commonapi.HostsStatus, *commonapi.LocalNetSta resp, err := rootClient.Status(ctx, req) if err != nil { s.log.Error("error getting status from root manager", "error", err) - return &commonapi.HostsStatus{}, &commonapi.LocalNetStatus{} + return &commonapi.HostsStatus{}, &commonapi.LocalNetStatus{}, nil } - return resp.Hosts, resp.Localnet + return resp.Hosts, resp.Localnet, resp.LocalDns } func (s *sbmServer) getRootClient() rootapi.RootManagerAPIClient { From bd897891279927f2cfaae2a9b260ecddf87f3d1b Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 8 Jun 2026 10:43:38 +0200 Subject: [PATCH 05/25] local: reject --local-dns with --unprivileged --local-dns manages the system DNS resolver (and binds 127.0.0.54:53 on Linux), which requires root. In --unprivileged mode there is no root-manager to run it, so fail fast with a clear error instead of silently ignoring the flag. (Logically belongs with the --local-dns flag commit; kept separate since this environment can't interactive-rebase to fold it in.) Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/connect.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/command/local/connect.go b/internal/command/local/connect.go index 8183116..e3872f8 100644 --- a/internal/command/local/connect.go +++ b/internal/command/local/connect.go @@ -68,6 +68,10 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg // fast response and is safe to return an error here, but the check is _not_ // used to assume that we have the lock later on when starting. withRootManager := !cfg.Unprivileged + if cfg.LocalDNS && !withRootManager { + return fmt.Errorf("--local-dns manages the system DNS resolver and requires root; " + + "it cannot be used with --unprivileged") + } pidFile := config.GetLocaldPIDfile(signadotDir, withRootManager) isRunning, err := processes.IsDaemonRunning(pidFile) if err != nil { From 77f5963933646d5ecb688789d4761c3a493ecfb2 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 9 Jun 2026 12:18:43 +0200 Subject: [PATCH 06/25] local: fix --local-dns connect health check CheckStatusConnectErrors always validated status.Hosts, which is nil under --local-dns (the root manager runs localdns in place of the /etc/hosts mechanism). Connect therefore failed the health gate with "failed to configure hosts in /etc/hosts" even though local DNS was healthy. Branch on EnableLocalDNS to check the LocalDns status instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/locald/sandboxmanager/sdk.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/locald/sandboxmanager/sdk.go b/internal/locald/sandboxmanager/sdk.go index a62d15c..47d93eb 100644 --- a/internal/locald/sandboxmanager/sdk.go +++ b/internal/locald/sandboxmanager/sdk.go @@ -72,8 +72,13 @@ func CheckStatusConnectErrors(status *sbmapi.StatusResponse, ciConfig *config.Co if err != nil { errs = append(errs, err) } - // check hosts service - err = checkHostsStatus(status.Hosts) + // check name-resolution service: localdns when --local-dns, + // otherwise the /etc/hosts mechanism + if ciConfig.EnableLocalDNS { + err = checkLocalDNSStatus(status.LocalDns) + } else { + err = checkHostsStatus(status.Hosts) + } if err != nil { errs = append(errs, err) } @@ -148,6 +153,21 @@ func checkHostsStatus(hosts *commonapi.HostsStatus) error { return errors.New(errorMsg) } +func checkLocalDNSStatus(localDNS *commonapi.LocalDNSStatus) error { + errorMsg := "failed to configure local DNS resolver" + if localDNS != nil { + if localDNS.Health != nil { + if localDNS.Health.Healthy { + return nil + } + if localDNS.Health.LastErrorReason != "" { + errorMsg += fmt.Sprintf(" (%q)", localDNS.Health.LastErrorReason) + } + } + } + return errors.New(errorMsg) +} + func checkDevboxSessionStatus(devboxSession *commonapi.DevboxSessionStatus) error { if devboxSession == nil { // Devbox session not initialized yet, not an error From 79ec064a34fa3a78a0e2e0fc7b339bc78754cf05 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 9 Jun 2026 12:45:13 +0200 Subject: [PATCH 07/25] deps: point libconnect at feat/localdns-1f branch Drop the local-checkout replace used during phase-1 development and pin libconnect to the pushed feat/localdns-1f tip (cb3c7da), so the branch builds and CI runs against the real module. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9afbf60..ab4c7c3 100644 --- a/go.mod +++ b/go.mod @@ -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.20260521222700-e386afcf25b5 - github.com/signadot/libconnect v0.1.1-0.20260527212426-31fdd269494f + github.com/signadot/libconnect v0.1.1-0.20260609101801-cb3c7da5106b github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 @@ -186,6 +186,4 @@ require ( ) // Used for local dev -replace github.com/signadot/libconnect => ../libconnect/ - //replace github.com/signadot/go-sdk => ../go-sdk diff --git a/go.sum b/go.sum index b73ac97..856da87 100644 --- a/go.sum +++ b/go.sum @@ -458,6 +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.20260521222700-e386afcf25b5 h1:k3SBmNm0VsniuO89/FOfEsIefbgSP/1MoKaECmz/xWg= github.com/signadot/go-sdk v0.3.8-0.20260521222700-e386afcf25b5/go.mod h1:l7XHS9snZXC+dy11NwXd1Ef+Z+K0SBhBUm4LAiFBbFU= +github.com/signadot/libconnect v0.1.1-0.20260609101801-cb3c7da5106b h1:5dlC2F4NEL32KA8lVu5gk6J7JQsNaBNKodi80w6Fu/E= +github.com/signadot/libconnect v0.1.1-0.20260609101801-cb3c7da5106b/go.mod h1:LUy59QIMQS5Z9EUssXPI+5wTJZOj2DlUi7PqbauQ5CE= 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= From 0e6c1218528dddd1b83bd69dea7b50470049b32a Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Mon, 6 Jul 2026 23:56:00 +0200 Subject: [PATCH 08/25] add `signadot local hosts` --- go.mod | 3 +- go.sum | 4 +- internal/command/local/hosts.go | 98 +++++ internal/command/local/local.go | 1 + internal/config/local.go | 7 + internal/locald/api/common.pb.go | 347 +++++++++++------- internal/locald/api/common.proto | 9 + .../api/rootmanager/root_manager_api.pb.go | 191 ++++++++-- .../api/rootmanager/root_manager_api.proto | 15 + .../rootmanager/root_manager_api_grpc.pb.go | 42 +++ .../sandboxmanager/sandbox_manager_api.pb.go | 241 +++++++++--- .../sandboxmanager/sandbox_manager_api.proto | 13 + .../sandbox_manager_api_grpc.pb.go | 40 ++ internal/locald/rootmanager/root_manager.go | 3 + internal/locald/rootmanager/root_server.go | 46 ++- .../locald/rootmanager/tp_monitor.go.head | 218 +++++++++++ .../locald/sandboxmanager/sandbox_server.go | 19 + internal/locald/sandboxmanager/sdk.go | 17 + 18 files changed, 1084 insertions(+), 230 deletions(-) create mode 100644 internal/command/local/hosts.go create mode 100644 internal/locald/rootmanager/tp_monitor.go.head diff --git a/go.mod b/go.mod index c6e94c7..7474023 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260630141204-b16775b06648 + github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 @@ -187,3 +187,4 @@ require ( // Used for local dev //replace github.com/signadot/go-sdk => ../go-sdk +// replace github.com/signadot/libconnect => ../libconnect diff --git a/go.sum b/go.sum index 70d90cf..5a42875 100644 --- a/go.sum +++ b/go.sum @@ -458,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.20260616155342-631831380993 h1:bF1URmHU+RLBAfDj7asExSv6WClCFwBAP0YPRpg/OjE= github.com/signadot/go-sdk v0.3.8-0.20260616155342-631831380993/go.mod h1:p+PG9uZvx5RTYtYovQQnrbzzB7a1SQ4A3WYNx6PC2xE= -github.com/signadot/libconnect v0.1.1-0.20260630141204-b16775b06648 h1:YXo02f+uhDeseLSq10OPQeXR3PTDdZDbbAPUM5z2SpQ= -github.com/signadot/libconnect v0.1.1-0.20260630141204-b16775b06648/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32 h1:ATP/W+jdNvewnBBUQISdrgciSMuLAjPfQ8qXuNOQ0Es= +github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32/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= diff --git a/internal/command/local/hosts.go b/internal/command/local/hosts.go new file mode 100644 index 0000000..66634e6 --- /dev/null +++ b/internal/command/local/hosts.go @@ -0,0 +1,98 @@ +package local + +import ( + "fmt" + "io" + "net" + + "github.com/signadot/cli/internal/config" + sbmgr "github.com/signadot/cli/internal/locald/sandboxmanager" + "github.com/signadot/cli/internal/print" + "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 (see pickIP), which matches how the cluster +// assigns names: a single-stack cluster hands out one family, and even when +// both are present a resolver answers a name with one address at a time. +type printableHost struct { + Name string `json:"name"` + IP string `json:"ip"` +} + +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 { + ip := pickIP(e.Ips) + if ip == "" { + // A name with no usable address is not resolvable; skip it. + continue + } + hosts = append(hosts, printableHost{Name: e.Name, IP: 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) + } +} + +// pickIP selects the single address to report for a host: the IPv4 address when +// one is present, otherwise the first IPv6 address (covering an IPv6-only +// cluster). It returns "" when there is no parseable address. +func pickIP(ips []string) string { + var fallback string + for _, ip := range ips { + parsed := net.ParseIP(ip) + if parsed == nil { + continue + } + if parsed.To4() != nil { + return ip + } + if fallback == "" { + fallback = ip + } + } + return fallback +} + +// printHosts writes one " " line per host. The entries arrive already +// sorted by name from the root controller (see rootServer.GetHosts). +func printHosts(out io.Writer, hosts []printableHost) error { + for _, h := range hosts { + if _, err := fmt.Fprintf(out, "%s %s\n", h.Name, h.IP); err != nil { + return err + } + } + return nil +} diff --git a/internal/command/local/local.go b/internal/command/local/local.go index 32630b9..d44405b 100644 --- a/internal/command/local/local.go +++ b/internal/command/local/local.go @@ -20,6 +20,7 @@ func New(api *config.API) *cobra.Command { newStatus(cfg), newDisconnect(cfg), newProxy(cfg), + newHosts(cfg), override.New(cfg), ) diff --git a/internal/config/local.go b/internal/config/local.go index 425f13f..1891df9 100644 --- a/internal/config/local.go +++ b/internal/config/local.go @@ -197,6 +197,13 @@ func (c *LocalStatus) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&c.Details, "details", false, "display status details") } +type LocalHosts struct { + *Local +} + +func (c *LocalHosts) AddFlags(cmd *cobra.Command) { +} + type LocalProxy struct { *Local diff --git a/internal/locald/api/common.pb.go b/internal/locald/api/common.pb.go index fbfd6b4..f05ecae 100644 --- a/internal/locald/api/common.pb.go +++ b/internal/locald/api/common.pb.go @@ -341,6 +341,65 @@ func (x *LocalDNSStatus) GetWarning() string { return "" } +// HostEntry is a single resolvable cluster name and the synthetic address(es) +// assigned to it. It is the unit returned by the GetHosts RPCs and is +// independent of the name-resolution mechanism (/etc/hosts or local DNS): both +// draw from the same address allocator in the root controller. +type HostEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Ips []string `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty"` +} + +func (x *HostEntry) Reset() { + *x = HostEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_common_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HostEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HostEntry) ProtoMessage() {} + +func (x *HostEntry) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_common_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HostEntry.ProtoReflect.Descriptor instead. +func (*HostEntry) Descriptor() ([]byte, []int) { + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{4} +} + +func (x *HostEntry) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *HostEntry) GetIps() []string { + if x != nil { + return x.Ips + } + return nil +} + // PortForward status (produced by local controller) type PortForwardStatus struct { state protoimpl.MessageState @@ -354,7 +413,7 @@ type PortForwardStatus struct { func (x *PortForwardStatus) Reset() { *x = PortForwardStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[4] + mi := &file_internal_locald_api_common_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -367,7 +426,7 @@ func (x *PortForwardStatus) String() string { func (*PortForwardStatus) ProtoMessage() {} func (x *PortForwardStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[4] + mi := &file_internal_locald_api_common_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -380,7 +439,7 @@ func (x *PortForwardStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PortForwardStatus.ProtoReflect.Descriptor instead. func (*PortForwardStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{4} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{5} } func (x *PortForwardStatus) GetHealth() *ServiceHealth { @@ -410,7 +469,7 @@ type ControlPlaneProxyStatus struct { func (x *ControlPlaneProxyStatus) Reset() { *x = ControlPlaneProxyStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[5] + mi := &file_internal_locald_api_common_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -423,7 +482,7 @@ func (x *ControlPlaneProxyStatus) String() string { func (*ControlPlaneProxyStatus) ProtoMessage() {} func (x *ControlPlaneProxyStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[5] + mi := &file_internal_locald_api_common_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -436,7 +495,7 @@ func (x *ControlPlaneProxyStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ControlPlaneProxyStatus.ProtoReflect.Descriptor instead. func (*ControlPlaneProxyStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{5} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6} } func (x *ControlPlaneProxyStatus) GetHealth() *ServiceHealth { @@ -465,7 +524,7 @@ type WatcherStatus struct { func (x *WatcherStatus) Reset() { *x = WatcherStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[6] + mi := &file_internal_locald_api_common_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -478,7 +537,7 @@ func (x *WatcherStatus) String() string { func (*WatcherStatus) ProtoMessage() {} func (x *WatcherStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[6] + mi := &file_internal_locald_api_common_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -491,7 +550,7 @@ func (x *WatcherStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WatcherStatus.ProtoReflect.Descriptor instead. func (*WatcherStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{6} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7} } func (x *WatcherStatus) GetHealth() *ServiceHealth { @@ -515,7 +574,7 @@ type SandboxStatus struct { func (x *SandboxStatus) Reset() { *x = SandboxStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[7] + mi := &file_internal_locald_api_common_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -528,7 +587,7 @@ func (x *SandboxStatus) String() string { func (*SandboxStatus) ProtoMessage() {} func (x *SandboxStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[7] + mi := &file_internal_locald_api_common_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -541,7 +600,7 @@ func (x *SandboxStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus.ProtoReflect.Descriptor instead. func (*SandboxStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8} } func (x *SandboxStatus) GetName() string { @@ -578,7 +637,7 @@ type OperatorInfo struct { func (x *OperatorInfo) Reset() { *x = OperatorInfo{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[8] + mi := &file_internal_locald_api_common_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -591,7 +650,7 @@ func (x *OperatorInfo) String() string { func (*OperatorInfo) ProtoMessage() {} func (x *OperatorInfo) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[8] + mi := &file_internal_locald_api_common_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -604,7 +663,7 @@ func (x *OperatorInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OperatorInfo.ProtoReflect.Descriptor instead. func (*OperatorInfo) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{9} } func (x *OperatorInfo) GetVersion() string { @@ -649,7 +708,7 @@ type DevboxSessionStatus struct { func (x *DevboxSessionStatus) Reset() { *x = DevboxSessionStatus{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[9] + mi := &file_internal_locald_api_common_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -662,7 +721,7 @@ func (x *DevboxSessionStatus) String() string { func (*DevboxSessionStatus) ProtoMessage() {} func (x *DevboxSessionStatus) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[9] + mi := &file_internal_locald_api_common_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -675,7 +734,7 @@ func (x *DevboxSessionStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use DevboxSessionStatus.ProtoReflect.Descriptor instead. func (*DevboxSessionStatus) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{9} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{10} } func (x *DevboxSessionStatus) GetHealthy() bool { @@ -727,7 +786,7 @@ type SandboxStatus_Baseline struct { func (x *SandboxStatus_Baseline) Reset() { *x = SandboxStatus_Baseline{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[10] + mi := &file_internal_locald_api_common_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -740,7 +799,7 @@ func (x *SandboxStatus_Baseline) String() string { func (*SandboxStatus_Baseline) ProtoMessage() {} func (x *SandboxStatus_Baseline) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[10] + mi := &file_internal_locald_api_common_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -753,7 +812,7 @@ func (x *SandboxStatus_Baseline) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_Baseline.ProtoReflect.Descriptor instead. func (*SandboxStatus_Baseline) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 0} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8, 0} } func (x *SandboxStatus_Baseline) GetApiVersion() string { @@ -796,7 +855,7 @@ type SandboxStatus_BaselineToLocal struct { func (x *SandboxStatus_BaselineToLocal) Reset() { *x = SandboxStatus_BaselineToLocal{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[11] + mi := &file_internal_locald_api_common_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -809,7 +868,7 @@ func (x *SandboxStatus_BaselineToLocal) String() string { func (*SandboxStatus_BaselineToLocal) ProtoMessage() {} func (x *SandboxStatus_BaselineToLocal) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[11] + mi := &file_internal_locald_api_common_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -822,7 +881,7 @@ func (x *SandboxStatus_BaselineToLocal) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_BaselineToLocal.ProtoReflect.Descriptor instead. func (*SandboxStatus_BaselineToLocal) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 1} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8, 1} } func (x *SandboxStatus_BaselineToLocal) GetBaselinePort() int32 { @@ -858,7 +917,7 @@ type SandboxStatus_LocalWorkload struct { func (x *SandboxStatus_LocalWorkload) Reset() { *x = SandboxStatus_LocalWorkload{} if protoimpl.UnsafeEnabled { - mi := &file_internal_locald_api_common_proto_msgTypes[12] + mi := &file_internal_locald_api_common_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -871,7 +930,7 @@ func (x *SandboxStatus_LocalWorkload) String() string { func (*SandboxStatus_LocalWorkload) ProtoMessage() {} func (x *SandboxStatus_LocalWorkload) ProtoReflect() protoreflect.Message { - mi := &file_internal_locald_api_common_proto_msgTypes[12] + mi := &file_internal_locald_api_common_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -884,7 +943,7 @@ func (x *SandboxStatus_LocalWorkload) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxStatus_LocalWorkload.ProtoReflect.Descriptor instead. func (*SandboxStatus_LocalWorkload) Descriptor() ([]byte, []int) { - return file_internal_locald_api_common_proto_rawDescGZIP(), []int{7, 2} + return file_internal_locald_api_common_proto_rawDescGZIP(), []int{8, 2} } func (x *SandboxStatus_LocalWorkload) GetName() string { @@ -976,88 +1035,91 @@ var file_internal_locald_api_common_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x66, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x6a, - 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, - 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x41, 0x0a, 0x0d, - 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, - 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, - 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, - 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, - 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x57, - 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x57, 0x6f, - 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, 0x73, 0x65, 0x6c, - 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, 0x42, 0x61, 0x73, - 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x23, 0x0a, 0x0d, - 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x72, - 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, - 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x08, - 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, - 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, - 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, - 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, 0x0a, 0x13, 0x77, - 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, - 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, - 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, - 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, 0x6e, 0x6e, 0x65, - 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, - 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0xdb, - 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, - 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, - 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, - 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x2d, 0x5a, 0x2b, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x31, + 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x70, + 0x73, 0x22, 0x6a, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, + 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, + 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, + 0x41, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x22, 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, + 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, + 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, + 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, + 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, + 0x50, 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, + 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x3d, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, + 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, + 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, + 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, + 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, + 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, + 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, + 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, + 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, + 0x65, 0x22, 0xdb, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, + 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, + 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, + 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1072,37 +1134,38 @@ func file_internal_locald_api_common_proto_rawDescGZIP() []byte { return file_internal_locald_api_common_proto_rawDescData } -var file_internal_locald_api_common_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_internal_locald_api_common_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_internal_locald_api_common_proto_goTypes = []interface{}{ (*ServiceHealth)(nil), // 0: apicommon.ServiceHealth (*LocalNetStatus)(nil), // 1: apicommon.LocalNetStatus (*HostsStatus)(nil), // 2: apicommon.HostsStatus (*LocalDNSStatus)(nil), // 3: apicommon.LocalDNSStatus - (*PortForwardStatus)(nil), // 4: apicommon.PortForwardStatus - (*ControlPlaneProxyStatus)(nil), // 5: apicommon.ControlPlaneProxyStatus - (*WatcherStatus)(nil), // 6: apicommon.WatcherStatus - (*SandboxStatus)(nil), // 7: apicommon.SandboxStatus - (*OperatorInfo)(nil), // 8: apicommon.OperatorInfo - (*DevboxSessionStatus)(nil), // 9: apicommon.DevboxSessionStatus - (*SandboxStatus_Baseline)(nil), // 10: apicommon.SandboxStatus.Baseline - (*SandboxStatus_BaselineToLocal)(nil), // 11: apicommon.SandboxStatus.BaselineToLocal - (*SandboxStatus_LocalWorkload)(nil), // 12: apicommon.SandboxStatus.LocalWorkload - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp + (*HostEntry)(nil), // 4: apicommon.HostEntry + (*PortForwardStatus)(nil), // 5: apicommon.PortForwardStatus + (*ControlPlaneProxyStatus)(nil), // 6: apicommon.ControlPlaneProxyStatus + (*WatcherStatus)(nil), // 7: apicommon.WatcherStatus + (*SandboxStatus)(nil), // 8: apicommon.SandboxStatus + (*OperatorInfo)(nil), // 9: apicommon.OperatorInfo + (*DevboxSessionStatus)(nil), // 10: apicommon.DevboxSessionStatus + (*SandboxStatus_Baseline)(nil), // 11: apicommon.SandboxStatus.Baseline + (*SandboxStatus_BaselineToLocal)(nil), // 12: apicommon.SandboxStatus.BaselineToLocal + (*SandboxStatus_LocalWorkload)(nil), // 13: apicommon.SandboxStatus.LocalWorkload + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp } var file_internal_locald_api_common_proto_depIdxs = []int32{ - 13, // 0: apicommon.ServiceHealth.last_error_time:type_name -> google.protobuf.Timestamp + 14, // 0: apicommon.ServiceHealth.last_error_time:type_name -> google.protobuf.Timestamp 0, // 1: apicommon.LocalNetStatus.health:type_name -> apicommon.ServiceHealth 0, // 2: apicommon.HostsStatus.health:type_name -> apicommon.ServiceHealth - 13, // 3: apicommon.HostsStatus.last_update_time:type_name -> google.protobuf.Timestamp + 14, // 3: apicommon.HostsStatus.last_update_time:type_name -> google.protobuf.Timestamp 0, // 4: apicommon.LocalDNSStatus.health:type_name -> apicommon.ServiceHealth - 13, // 5: apicommon.LocalDNSStatus.last_refresh:type_name -> google.protobuf.Timestamp + 14, // 5: apicommon.LocalDNSStatus.last_refresh:type_name -> google.protobuf.Timestamp 0, // 6: apicommon.PortForwardStatus.health:type_name -> apicommon.ServiceHealth 0, // 7: apicommon.ControlPlaneProxyStatus.health:type_name -> apicommon.ServiceHealth 0, // 8: apicommon.WatcherStatus.health:type_name -> apicommon.ServiceHealth - 12, // 9: apicommon.SandboxStatus.local_workloads:type_name -> apicommon.SandboxStatus.LocalWorkload - 13, // 10: apicommon.DevboxSessionStatus.last_error_time:type_name -> google.protobuf.Timestamp - 10, // 11: apicommon.SandboxStatus.LocalWorkload.baseline:type_name -> apicommon.SandboxStatus.Baseline - 11, // 12: apicommon.SandboxStatus.LocalWorkload.workloadPortMapping:type_name -> apicommon.SandboxStatus.BaselineToLocal + 13, // 9: apicommon.SandboxStatus.local_workloads:type_name -> apicommon.SandboxStatus.LocalWorkload + 14, // 10: apicommon.DevboxSessionStatus.last_error_time:type_name -> google.protobuf.Timestamp + 11, // 11: apicommon.SandboxStatus.LocalWorkload.baseline:type_name -> apicommon.SandboxStatus.Baseline + 12, // 12: apicommon.SandboxStatus.LocalWorkload.workloadPortMapping:type_name -> apicommon.SandboxStatus.BaselineToLocal 0, // 13: apicommon.SandboxStatus.LocalWorkload.tunnel_health:type_name -> apicommon.ServiceHealth 14, // [14:14] is the sub-list for method output_type 14, // [14:14] is the sub-list for method input_type @@ -1166,7 +1229,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PortForwardStatus); i { + switch v := v.(*HostEntry); i { case 0: return &v.state case 1: @@ -1178,7 +1241,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ControlPlaneProxyStatus); i { + switch v := v.(*PortForwardStatus); i { case 0: return &v.state case 1: @@ -1190,7 +1253,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WatcherStatus); i { + switch v := v.(*ControlPlaneProxyStatus); i { case 0: return &v.state case 1: @@ -1202,7 +1265,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus); i { + switch v := v.(*WatcherStatus); i { case 0: return &v.state case 1: @@ -1214,7 +1277,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OperatorInfo); i { + switch v := v.(*SandboxStatus); i { case 0: return &v.state case 1: @@ -1226,7 +1289,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DevboxSessionStatus); i { + switch v := v.(*OperatorInfo); i { case 0: return &v.state case 1: @@ -1238,7 +1301,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus_Baseline); i { + switch v := v.(*DevboxSessionStatus); i { case 0: return &v.state case 1: @@ -1250,7 +1313,7 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SandboxStatus_BaselineToLocal); i { + switch v := v.(*SandboxStatus_Baseline); i { case 0: return &v.state case 1: @@ -1262,6 +1325,18 @@ func file_internal_locald_api_common_proto_init() { } } file_internal_locald_api_common_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SandboxStatus_BaselineToLocal); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_locald_api_common_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SandboxStatus_LocalWorkload); i { case 0: return &v.state @@ -1280,7 +1355,7 @@ func file_internal_locald_api_common_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_locald_api_common_proto_rawDesc, NumEnums: 0, - NumMessages: 13, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/locald/api/common.proto b/internal/locald/api/common.proto index b253d1d..603d351 100644 --- a/internal/locald/api/common.proto +++ b/internal/locald/api/common.proto @@ -43,6 +43,15 @@ message LocalDNSStatus { string warning = 9; } +// HostEntry is a single resolvable cluster name and the synthetic address(es) +// assigned to it. It is the unit returned by the GetHosts RPCs and is +// independent of the name-resolution mechanism (/etc/hosts or local DNS): both +// draw from the same address allocator in the root controller. +message HostEntry { + string name = 1; + repeated string ips = 2; +} + // PortForward status (produced by local controller) message PortForwardStatus { ServiceHealth health = 1; diff --git a/internal/locald/api/rootmanager/root_manager_api.pb.go b/internal/locald/api/rootmanager/root_manager_api.pb.go index 3923f29..909cf24 100644 --- a/internal/locald/api/rootmanager/root_manager_api.pb.go +++ b/internal/locald/api/rootmanager/root_manager_api.pb.go @@ -198,6 +198,91 @@ func (*ShutdownResponse) Descriptor() ([]byte, []int) { return file_internal_locald_api_rootmanager_root_manager_api_proto_rawDescGZIP(), []int{3} } +type GetHostsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetHostsRequest) Reset() { + *x = GetHostsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHostsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostsRequest) ProtoMessage() {} + +func (x *GetHostsRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHostsRequest.ProtoReflect.Descriptor instead. +func (*GetHostsRequest) Descriptor() ([]byte, []int) { + return file_internal_locald_api_rootmanager_root_manager_api_proto_rawDescGZIP(), []int{4} +} + +type GetHostsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []*api.HostEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *GetHostsResponse) Reset() { + *x = GetHostsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHostsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostsResponse) ProtoMessage() {} + +func (x *GetHostsResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHostsResponse.ProtoReflect.Descriptor instead. +func (*GetHostsResponse) Descriptor() ([]byte, []int) { + return file_internal_locald_api_rootmanager_root_manager_api_proto_rawDescGZIP(), []int{5} +} + +func (x *GetHostsResponse) GetEntries() []*api.HostEntry { + if x != nil { + return x.Entries + } + return nil +} + var File_internal_locald_api_rootmanager_root_manager_api_proto protoreflect.FileDescriptor var file_internal_locald_api_rootmanager_root_manager_api_proto_rawDesc = []byte{ @@ -221,22 +306,32 @@ var file_internal_locald_api_rootmanager_root_manager_api_proto_rawDesc = []byte 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x6e, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, - 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, - 0xa0, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, - 0x50, 0x49, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x72, - 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, - 0x6f, 0x77, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, - 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, - 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x42, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x32, 0xeb, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x74, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, 0x50, 0x49, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1a, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, + 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6f, + 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -251,29 +346,35 @@ func file_internal_locald_api_rootmanager_root_manager_api_proto_rawDescGZIP() [ return file_internal_locald_api_rootmanager_root_manager_api_proto_rawDescData } -var file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_internal_locald_api_rootmanager_root_manager_api_proto_goTypes = []interface{}{ (*StatusRequest)(nil), // 0: rootmanager.StatusRequest (*StatusResponse)(nil), // 1: rootmanager.StatusResponse (*ShutdownRequest)(nil), // 2: rootmanager.ShutdownRequest (*ShutdownResponse)(nil), // 3: rootmanager.ShutdownResponse - (*api.LocalNetStatus)(nil), // 4: apicommon.LocalNetStatus - (*api.HostsStatus)(nil), // 5: apicommon.HostsStatus - (*api.LocalDNSStatus)(nil), // 6: apicommon.LocalDNSStatus + (*GetHostsRequest)(nil), // 4: rootmanager.GetHostsRequest + (*GetHostsResponse)(nil), // 5: rootmanager.GetHostsResponse + (*api.LocalNetStatus)(nil), // 6: apicommon.LocalNetStatus + (*api.HostsStatus)(nil), // 7: apicommon.HostsStatus + (*api.LocalDNSStatus)(nil), // 8: apicommon.LocalDNSStatus + (*api.HostEntry)(nil), // 9: apicommon.HostEntry } var file_internal_locald_api_rootmanager_root_manager_api_proto_depIdxs = []int32{ - 4, // 0: rootmanager.StatusResponse.localnet:type_name -> apicommon.LocalNetStatus - 5, // 1: rootmanager.StatusResponse.hosts:type_name -> apicommon.HostsStatus - 6, // 2: rootmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus - 0, // 3: rootmanager.RootManagerAPI.Status:input_type -> rootmanager.StatusRequest - 2, // 4: rootmanager.RootManagerAPI.Shutdown:input_type -> rootmanager.ShutdownRequest - 1, // 5: rootmanager.RootManagerAPI.Status:output_type -> rootmanager.StatusResponse - 3, // 6: rootmanager.RootManagerAPI.Shutdown:output_type -> rootmanager.ShutdownResponse - 5, // [5:7] is the sub-list for method output_type - 3, // [3:5] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 6, // 0: rootmanager.StatusResponse.localnet:type_name -> apicommon.LocalNetStatus + 7, // 1: rootmanager.StatusResponse.hosts:type_name -> apicommon.HostsStatus + 8, // 2: rootmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus + 9, // 3: rootmanager.GetHostsResponse.entries:type_name -> apicommon.HostEntry + 0, // 4: rootmanager.RootManagerAPI.Status:input_type -> rootmanager.StatusRequest + 2, // 5: rootmanager.RootManagerAPI.Shutdown:input_type -> rootmanager.ShutdownRequest + 4, // 6: rootmanager.RootManagerAPI.GetHosts:input_type -> rootmanager.GetHostsRequest + 1, // 7: rootmanager.RootManagerAPI.Status:output_type -> rootmanager.StatusResponse + 3, // 8: rootmanager.RootManagerAPI.Shutdown:output_type -> rootmanager.ShutdownResponse + 5, // 9: rootmanager.RootManagerAPI.GetHosts:output_type -> rootmanager.GetHostsResponse + 7, // [7:10] is the sub-list for method output_type + 4, // [4:7] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_internal_locald_api_rootmanager_root_manager_api_proto_init() } @@ -330,6 +431,30 @@ func file_internal_locald_api_rootmanager_root_manager_api_proto_init() { return nil } } + file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHostsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_locald_api_rootmanager_root_manager_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHostsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -337,7 +462,7 @@ func file_internal_locald_api_rootmanager_root_manager_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_locald_api_rootmanager_root_manager_api_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/locald/api/rootmanager/root_manager_api.proto b/internal/locald/api/rootmanager/root_manager_api.proto index 663ff1a..46574e5 100644 --- a/internal/locald/api/rootmanager/root_manager_api.proto +++ b/internal/locald/api/rootmanager/root_manager_api.proto @@ -13,6 +13,11 @@ service RootManagerAPI { // This method requests the root controller to shutdown rpc Shutdown(ShutdownRequest) returns (ShutdownResponse) {} + + // This method returns the resolvable cluster hosts and their assigned + // addresses, regardless of whether name resolution is served via /etc/hosts + // or the local DNS resolver. + rpc GetHosts(GetHostsRequest) returns (GetHostsResponse) {} } // Status @@ -34,4 +39,14 @@ message ShutdownRequest { } message ShutdownResponse { +} + +// Hosts +// ---------------------------------------------------------------------------- + +message GetHostsRequest { +} + +message GetHostsResponse { + repeated apicommon.HostEntry entries = 1; } \ No newline at end of file diff --git a/internal/locald/api/rootmanager/root_manager_api_grpc.pb.go b/internal/locald/api/rootmanager/root_manager_api_grpc.pb.go index 9e070df..8dbf14d 100644 --- a/internal/locald/api/rootmanager/root_manager_api_grpc.pb.go +++ b/internal/locald/api/rootmanager/root_manager_api_grpc.pb.go @@ -22,6 +22,10 @@ type RootManagerAPIClient interface { Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) // This method requests the root controller to shutdown Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) + // This method returns the resolvable cluster hosts and their assigned + // addresses, regardless of whether name resolution is served via /etc/hosts + // or the local DNS resolver. + GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error) } type rootManagerAPIClient struct { @@ -50,6 +54,15 @@ func (c *rootManagerAPIClient) Shutdown(ctx context.Context, in *ShutdownRequest return out, nil } +func (c *rootManagerAPIClient) GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error) { + out := new(GetHostsResponse) + err := c.cc.Invoke(ctx, "/rootmanager.RootManagerAPI/GetHosts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // RootManagerAPIServer is the server API for RootManagerAPI service. // All implementations must embed UnimplementedRootManagerAPIServer // for forward compatibility @@ -58,6 +71,10 @@ type RootManagerAPIServer interface { Status(context.Context, *StatusRequest) (*StatusResponse, error) // This method requests the root controller to shutdown Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) + // This method returns the resolvable cluster hosts and their assigned + // addresses, regardless of whether name resolution is served via /etc/hosts + // or the local DNS resolver. + GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error) mustEmbedUnimplementedRootManagerAPIServer() } @@ -71,6 +88,9 @@ func (UnimplementedRootManagerAPIServer) Status(context.Context, *StatusRequest) func (UnimplementedRootManagerAPIServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented") } +func (UnimplementedRootManagerAPIServer) GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetHosts not implemented") +} func (UnimplementedRootManagerAPIServer) mustEmbedUnimplementedRootManagerAPIServer() {} // UnsafeRootManagerAPIServer may be embedded to opt out of forward compatibility for this service. @@ -120,6 +140,24 @@ func _RootManagerAPI_Shutdown_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _RootManagerAPI_GetHosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHostsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RootManagerAPIServer).GetHosts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/rootmanager.RootManagerAPI/GetHosts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RootManagerAPIServer).GetHosts(ctx, req.(*GetHostsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // RootManagerAPI_ServiceDesc is the grpc.ServiceDesc for RootManagerAPI service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -135,6 +173,10 @@ var RootManagerAPI_ServiceDesc = grpc.ServiceDesc{ MethodName: "Shutdown", Handler: _RootManagerAPI_Shutdown_Handler, }, + { + MethodName: "GetHosts", + Handler: _RootManagerAPI_GetHosts_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "internal/locald/api/rootmanager/root_manager_api.proto", diff --git a/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go b/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go index f6f657e..5c8ef3d 100644 --- a/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go +++ b/internal/locald/api/sandboxmanager/sandbox_manager_api.pb.go @@ -463,6 +463,93 @@ func (x *ResourceOutputItem) GetValue() string { return "" } +// Hosts +// --------------------------------------------------------------------------- +type GetHostsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetHostsRequest) Reset() { + *x = GetHostsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHostsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostsRequest) ProtoMessage() {} + +func (x *GetHostsRequest) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHostsRequest.ProtoReflect.Descriptor instead. +func (*GetHostsRequest) Descriptor() ([]byte, []int) { + return file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDescGZIP(), []int{8} +} + +type GetHostsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []*api.HostEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *GetHostsResponse) Reset() { + *x = GetHostsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetHostsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetHostsResponse) ProtoMessage() {} + +func (x *GetHostsResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetHostsResponse.ProtoReflect.Descriptor instead. +func (*GetHostsResponse) Descriptor() ([]byte, []int) { + return file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDescGZIP(), []int{9} +} + +func (x *GetHostsResponse) GetEntries() []*api.HostEntry { + if x != nil { + return x.Entries + } + return nil +} + var File_internal_locald_api_sandboxmanager_sandbox_manager_api_proto protoreflect.FileDescriptor var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDesc = []byte{ @@ -539,30 +626,40 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDesc = 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x9e, 0x02, 0x0a, - 0x11, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, - 0x50, 0x49, 0x12, 0x49, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x73, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, - 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, - 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6d, - 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2a, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3c, 0x5a, - 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x11, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x42, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, + 0x69, 0x65, 0x73, 0x32, 0xef, 0x02, 0x0a, 0x11, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x41, 0x50, 0x49, 0x12, 0x49, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x1d, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, + 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x29, 0x2e, 0x73, 0x61, + 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, + 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x72, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, 0x67, + 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -577,7 +674,7 @@ func file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDescGZ return file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDescData } -var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_goTypes = []interface{}{ (*StatusRequest)(nil), // 0: sandboxmanager.StatusRequest (*StatusResponse)(nil), // 1: sandboxmanager.StatusResponse @@ -587,41 +684,47 @@ var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_goTypes = (*GetResourceOutputsResponse)(nil), // 5: sandboxmanager.GetResourceOutputsResponse (*ResourceOutputs)(nil), // 6: sandboxmanager.ResourceOutputs (*ResourceOutputItem)(nil), // 7: sandboxmanager.ResourceOutputItem - (*structpb.Struct)(nil), // 8: google.protobuf.Struct - (*api.OperatorInfo)(nil), // 9: apicommon.OperatorInfo - (*api.LocalNetStatus)(nil), // 10: apicommon.LocalNetStatus - (*api.HostsStatus)(nil), // 11: apicommon.HostsStatus - (*api.PortForwardStatus)(nil), // 12: apicommon.PortForwardStatus - (*api.ControlPlaneProxyStatus)(nil), // 13: apicommon.ControlPlaneProxyStatus - (*api.WatcherStatus)(nil), // 14: apicommon.WatcherStatus - (*api.SandboxStatus)(nil), // 15: apicommon.SandboxStatus - (*api.DevboxSessionStatus)(nil), // 16: apicommon.DevboxSessionStatus - (*api.LocalDNSStatus)(nil), // 17: apicommon.LocalDNSStatus + (*GetHostsRequest)(nil), // 8: sandboxmanager.GetHostsRequest + (*GetHostsResponse)(nil), // 9: sandboxmanager.GetHostsResponse + (*structpb.Struct)(nil), // 10: google.protobuf.Struct + (*api.OperatorInfo)(nil), // 11: apicommon.OperatorInfo + (*api.LocalNetStatus)(nil), // 12: apicommon.LocalNetStatus + (*api.HostsStatus)(nil), // 13: apicommon.HostsStatus + (*api.PortForwardStatus)(nil), // 14: apicommon.PortForwardStatus + (*api.ControlPlaneProxyStatus)(nil), // 15: apicommon.ControlPlaneProxyStatus + (*api.WatcherStatus)(nil), // 16: apicommon.WatcherStatus + (*api.SandboxStatus)(nil), // 17: apicommon.SandboxStatus + (*api.DevboxSessionStatus)(nil), // 18: apicommon.DevboxSessionStatus + (*api.LocalDNSStatus)(nil), // 19: apicommon.LocalDNSStatus + (*api.HostEntry)(nil), // 20: apicommon.HostEntry } var file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_depIdxs = []int32{ - 8, // 0: sandboxmanager.StatusResponse.ci_config:type_name -> google.protobuf.Struct - 9, // 1: sandboxmanager.StatusResponse.operator_info:type_name -> apicommon.OperatorInfo - 10, // 2: sandboxmanager.StatusResponse.localnet:type_name -> apicommon.LocalNetStatus - 11, // 3: sandboxmanager.StatusResponse.hosts:type_name -> apicommon.HostsStatus - 12, // 4: sandboxmanager.StatusResponse.portforward:type_name -> apicommon.PortForwardStatus - 13, // 5: sandboxmanager.StatusResponse.control_plane_proxy:type_name -> apicommon.ControlPlaneProxyStatus - 14, // 6: sandboxmanager.StatusResponse.watcher:type_name -> apicommon.WatcherStatus - 15, // 7: sandboxmanager.StatusResponse.sandboxes:type_name -> apicommon.SandboxStatus - 16, // 8: sandboxmanager.StatusResponse.devbox_session:type_name -> apicommon.DevboxSessionStatus - 17, // 9: sandboxmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus + 10, // 0: sandboxmanager.StatusResponse.ci_config:type_name -> google.protobuf.Struct + 11, // 1: sandboxmanager.StatusResponse.operator_info:type_name -> apicommon.OperatorInfo + 12, // 2: sandboxmanager.StatusResponse.localnet:type_name -> apicommon.LocalNetStatus + 13, // 3: sandboxmanager.StatusResponse.hosts:type_name -> apicommon.HostsStatus + 14, // 4: sandboxmanager.StatusResponse.portforward:type_name -> apicommon.PortForwardStatus + 15, // 5: sandboxmanager.StatusResponse.control_plane_proxy:type_name -> apicommon.ControlPlaneProxyStatus + 16, // 6: sandboxmanager.StatusResponse.watcher:type_name -> apicommon.WatcherStatus + 17, // 7: sandboxmanager.StatusResponse.sandboxes:type_name -> apicommon.SandboxStatus + 18, // 8: sandboxmanager.StatusResponse.devbox_session:type_name -> apicommon.DevboxSessionStatus + 19, // 9: sandboxmanager.StatusResponse.local_dns:type_name -> apicommon.LocalDNSStatus 6, // 10: sandboxmanager.GetResourceOutputsResponse.resource_outputs:type_name -> sandboxmanager.ResourceOutputs 7, // 11: sandboxmanager.ResourceOutputs.outputs:type_name -> sandboxmanager.ResourceOutputItem - 0, // 12: sandboxmanager.SandboxManagerAPI.Status:input_type -> sandboxmanager.StatusRequest - 2, // 13: sandboxmanager.SandboxManagerAPI.Shutdown:input_type -> sandboxmanager.ShutdownRequest - 4, // 14: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:input_type -> sandboxmanager.GetResourceOutputsRequest - 1, // 15: sandboxmanager.SandboxManagerAPI.Status:output_type -> sandboxmanager.StatusResponse - 3, // 16: sandboxmanager.SandboxManagerAPI.Shutdown:output_type -> sandboxmanager.ShutdownResponse - 5, // 17: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:output_type -> sandboxmanager.GetResourceOutputsResponse - 15, // [15:18] is the sub-list for method output_type - 12, // [12:15] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 20, // 12: sandboxmanager.GetHostsResponse.entries:type_name -> apicommon.HostEntry + 0, // 13: sandboxmanager.SandboxManagerAPI.Status:input_type -> sandboxmanager.StatusRequest + 2, // 14: sandboxmanager.SandboxManagerAPI.Shutdown:input_type -> sandboxmanager.ShutdownRequest + 4, // 15: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:input_type -> sandboxmanager.GetResourceOutputsRequest + 8, // 16: sandboxmanager.SandboxManagerAPI.GetHosts:input_type -> sandboxmanager.GetHostsRequest + 1, // 17: sandboxmanager.SandboxManagerAPI.Status:output_type -> sandboxmanager.StatusResponse + 3, // 18: sandboxmanager.SandboxManagerAPI.Shutdown:output_type -> sandboxmanager.ShutdownResponse + 5, // 19: sandboxmanager.SandboxManagerAPI.GetResourceOutputs:output_type -> sandboxmanager.GetResourceOutputsResponse + 9, // 20: sandboxmanager.SandboxManagerAPI.GetHosts:output_type -> sandboxmanager.GetHostsResponse + 17, // [17:21] is the sub-list for method output_type + 13, // [13:17] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name } func init() { file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_init() } @@ -726,6 +829,30 @@ func file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_init() { return nil } } + file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHostsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetHostsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -733,7 +860,7 @@ func file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_locald_api_sandboxmanager_sandbox_manager_api_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/locald/api/sandboxmanager/sandbox_manager_api.proto b/internal/locald/api/sandboxmanager/sandbox_manager_api.proto index f0ca265..84288a2 100644 --- a/internal/locald/api/sandboxmanager/sandbox_manager_api.proto +++ b/internal/locald/api/sandboxmanager/sandbox_manager_api.proto @@ -18,6 +18,10 @@ service SandboxManagerAPI { // This method returns all the available resource outputs for a sandbox given // its routing key. rpc GetResourceOutputs(GetResourceOutputsRequest) returns (GetResourceOutputsResponse) {} + + // This method returns the resolvable cluster hosts and their assigned + // addresses (relayed from the root controller). + rpc GetHosts(GetHostsRequest) returns (GetHostsResponse) {} } // Status @@ -69,3 +73,12 @@ message ResourceOutputItem { string key = 1; string value = 2; } + +// Hosts +// --------------------------------------------------------------------------- +message GetHostsRequest { +} + +message GetHostsResponse { + repeated apicommon.HostEntry entries = 1; +} diff --git a/internal/locald/api/sandboxmanager/sandbox_manager_api_grpc.pb.go b/internal/locald/api/sandboxmanager/sandbox_manager_api_grpc.pb.go index 552e58a..92b7031 100644 --- a/internal/locald/api/sandboxmanager/sandbox_manager_api_grpc.pb.go +++ b/internal/locald/api/sandboxmanager/sandbox_manager_api_grpc.pb.go @@ -25,6 +25,9 @@ type SandboxManagerAPIClient interface { // This method returns all the available resource outputs for a sandbox given // its routing key. GetResourceOutputs(ctx context.Context, in *GetResourceOutputsRequest, opts ...grpc.CallOption) (*GetResourceOutputsResponse, error) + // This method returns the resolvable cluster hosts and their assigned + // addresses (relayed from the root controller). + GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error) } type sandboxManagerAPIClient struct { @@ -62,6 +65,15 @@ func (c *sandboxManagerAPIClient) GetResourceOutputs(ctx context.Context, in *Ge return out, nil } +func (c *sandboxManagerAPIClient) GetHosts(ctx context.Context, in *GetHostsRequest, opts ...grpc.CallOption) (*GetHostsResponse, error) { + out := new(GetHostsResponse) + err := c.cc.Invoke(ctx, "/sandboxmanager.SandboxManagerAPI/GetHosts", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SandboxManagerAPIServer is the server API for SandboxManagerAPI service. // All implementations must embed UnimplementedSandboxManagerAPIServer // for forward compatibility @@ -73,6 +85,9 @@ type SandboxManagerAPIServer interface { // This method returns all the available resource outputs for a sandbox given // its routing key. GetResourceOutputs(context.Context, *GetResourceOutputsRequest) (*GetResourceOutputsResponse, error) + // This method returns the resolvable cluster hosts and their assigned + // addresses (relayed from the root controller). + GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error) mustEmbedUnimplementedSandboxManagerAPIServer() } @@ -89,6 +104,9 @@ func (UnimplementedSandboxManagerAPIServer) Shutdown(context.Context, *ShutdownR func (UnimplementedSandboxManagerAPIServer) GetResourceOutputs(context.Context, *GetResourceOutputsRequest) (*GetResourceOutputsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetResourceOutputs not implemented") } +func (UnimplementedSandboxManagerAPIServer) GetHosts(context.Context, *GetHostsRequest) (*GetHostsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetHosts not implemented") +} func (UnimplementedSandboxManagerAPIServer) mustEmbedUnimplementedSandboxManagerAPIServer() {} // UnsafeSandboxManagerAPIServer may be embedded to opt out of forward compatibility for this service. @@ -156,6 +174,24 @@ func _SandboxManagerAPI_GetResourceOutputs_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _SandboxManagerAPI_GetHosts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHostsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SandboxManagerAPIServer).GetHosts(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/sandboxmanager.SandboxManagerAPI/GetHosts", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SandboxManagerAPIServer).GetHosts(ctx, req.(*GetHostsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // SandboxManagerAPI_ServiceDesc is the grpc.ServiceDesc for SandboxManagerAPI service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -175,6 +211,10 @@ var SandboxManagerAPI_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetResourceOutputs", Handler: _SandboxManagerAPI_GetResourceOutputs_Handler, }, + { + MethodName: "GetHosts", + Handler: _SandboxManagerAPI_GetHosts_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "internal/locald/api/sandboxmanager/sandbox_manager_api.proto", diff --git a/internal/locald/rootmanager/root_manager.go b/internal/locald/rootmanager/root_manager.go index bcbca1d..22423ab 100644 --- a/internal/locald/rootmanager/root_manager.go +++ b/internal/locald/rootmanager/root_manager.go @@ -69,6 +69,9 @@ func (m *rootManager) Run(ctx context.Context) error { if err != nil { return fmt.Errorf("error creating ipmap: %w", err) } + // Publish the ipMap on the root API server so GetHosts can enumerate the + // host->address table regardless of which name-resolution service runs. + m.root.setIPMap(ipMap) // Run the sandbox manager go m.sbmMonitor.run() diff --git a/internal/locald/rootmanager/root_server.go b/internal/locald/rootmanager/root_server.go index ce3274b..6f974ec 100644 --- a/internal/locald/rootmanager/root_server.go +++ b/internal/locald/rootmanager/root_server.go @@ -2,12 +2,14 @@ package rootmanager import ( "context" + "sort" "sync" commonapi "github.com/signadot/cli/internal/locald/api" rootapi "github.com/signadot/cli/internal/locald/api/rootmanager" "github.com/signadot/libconnect/common/apiclient" "github.com/signadot/libconnect/fwdtun/etchosts" + "github.com/signadot/libconnect/fwdtun/ipmap" "github.com/signadot/libconnect/fwdtun/localdns" "github.com/signadot/libconnect/fwdtun/localnet" "google.golang.org/protobuf/types/known/timestamppb" @@ -23,7 +25,11 @@ type rootServer struct { // The CLI owns the injected tunnel-api client and closes it on teardown. localDNSSVC *localdns.Service localDNSAPIClient apiclient.Client - shutdownCh chan struct{} + // ipMap is the shared host->address allocator that backs whichever + // name-resolution service is running; it is the source of truth for + // GetHosts in both /etc/hosts and local-DNS modes. + ipMap *ipmap.IPMap + shutdownCh chan struct{} } var _ rootapi.RootManagerAPIServer = &rootServer{} @@ -131,6 +137,44 @@ func (s *rootServer) Shutdown(ctx context.Context, req *rootapi.ShutdownRequest) return &rootapi.ShutdownResponse{}, nil } +func (s *rootServer) GetHosts(ctx context.Context, req *rootapi.GetHostsRequest) (*rootapi.GetHostsResponse, error) { + s.mu.RLock() + ipMap := s.ipMap + s.mu.RUnlock() + + resp := &rootapi.GetHostsResponse{} + if ipMap == nil { + // No name-resolution service has started yet (e.g. tunnel-proxy link + // still coming up): report an empty set rather than an error. + return resp, nil + } + + entries := ipMap.Entries() + resp.Entries = make([]*commonapi.HostEntry, 0, len(entries)) + for name, ips := range entries { + strIPs := make([]string, len(ips)) + for i, ip := range ips { + strIPs[i] = ip.String() + } + resp.Entries = append(resp.Entries, &commonapi.HostEntry{ + Name: name, + Ips: strIPs, + }) + } + // Stable, name-sorted output so callers (and `signadot local hosts`) get a + // deterministic listing. + sort.Slice(resp.Entries, func(i, j int) bool { + return resp.Entries[i].Name < resp.Entries[j].Name + }) + return resp, nil +} + +func (s *rootServer) setIPMap(ipMap *ipmap.IPMap) { + s.mu.Lock() + defer s.mu.Unlock() + s.ipMap = ipMap +} + func (s *rootServer) setLocalnetService(localnetSVC *localnet.Service) { s.mu.Lock() defer s.mu.Unlock() diff --git a/internal/locald/rootmanager/tp_monitor.go.head b/internal/locald/rootmanager/tp_monitor.go.head new file mode 100644 index 0000000..0eea0b9 --- /dev/null +++ b/internal/locald/rootmanager/tp_monitor.go.head @@ -0,0 +1,218 @@ +package rootmanager + +import ( + "context" + "fmt" + "net/http" + "time" + + "log/slog" + + sbmanagerapi "github.com/signadot/cli/internal/locald/api/sandboxmanager" + connectcfg "github.com/signadot/libconnect/config" + "github.com/signadot/libconnect/fwdtun/ipmap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +const ( + checkingPeriodNotOK = time.Second + checkingPeriodOK = 10 * time.Second + // maxStartingTime needs to be large enough for worst case startup time + maxStartingTime = 30 * time.Second +) + +type tpMonitor struct { + log *slog.Logger + root *rootManager + sbManagerAddr string + tpLocalAddr string + ipMap *ipmap.IPMap + starting bool + beginStarting time.Time + sbClient sbmanagerapi.SandboxManagerAPIClient + closeCh chan struct{} +} + +func NewTunnelProxyMonitor(ctx context.Context, root *rootManager, ipMap *ipmap.IPMap) *tpMonitor { + mon := &tpMonitor{ + log: root.log, + ipMap: ipMap, + sbManagerAddr: fmt.Sprintf("127.0.0.1:%d", root.ciConfig.APIPort), + root: root, + starting: true, + beginStarting: time.Now(), + closeCh: make(chan struct{}), + } + go mon.run(ctx) + return mon +} + +func (mon *tpMonitor) Stop() { + select { + case <-mon.closeCh: + return + default: + close(mon.closeCh) + } +} + +func (mon *tpMonitor) run(ctx context.Context) { + ticker := time.NewTicker(checkingPeriodNotOK) + defer ticker.Stop() + + mon.starting = true + mon.beginStarting = time.Now() + for { + success, restarted := mon.checkTunnelProxyAccess(ctx) + if success { + ticker.Reset(checkingPeriodOK) + mon.starting = false + } else { + ticker.Reset(checkingPeriodNotOK) + if restarted || !mon.starting { + mon.beginStarting = time.Now() + } + mon.starting = true + } + select { + case <-ctx.Done(): + // Context is done + return + case <-mon.closeCh: + // We have been stopped + return + case <-ticker.C: + // Check ticker + } + } +} + +func (mon *tpMonitor) checkTunnelProxyAccess(ctx context.Context) (success bool, restarted bool) { + mon.log.Debug("checking tunnel-proxy access") + if mon.sbClient == nil { + // Establish the connection if needed + grpcConn, err := grpc.NewClient(mon.sbManagerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + if mon.starting { + mon.log.Debug("waiting for sandbox manager to be ready (not running yet)") + } else { + mon.log.Warn("couldn't connect with sandbox manager", "error", err) + } + return false, mon.restartServices(ctx) + } + mon.sbClient = sbmanagerapi.NewSandboxManagerAPIClient(grpcConn) + } + mon.log.Debug("connected to sandbox manager") + + // Get the sandbox manager status + status, err := mon.sbClient.Status(ctx, &sbmanagerapi.StatusRequest{}) + if err != nil { + if mon.starting { + mon.log.Debug("waiting for sandbox manager to be ready (api not ready)") + } else { + mon.log.Warn("couldn't get status from sandbox manager", "error", err) + } + return false, mon.restartServices(ctx) + } + + // Check the status + ok := mon.checkLinkStatus(status) + if !ok { + return false, mon.restartServices(ctx) + } + ok = mon.checkRootServer(mon.root.root) + if !ok { + return false, mon.restartServices(ctx) + } + ok = mon.checkAgentMetrics(ctx) + if !ok { + return false, mon.restartServices(ctx) + } + return true, false +} + +func (mon *tpMonitor) checkLinkStatus(status *sbmanagerapi.StatusResponse) (ok bool) { + switch mon.root.ciConfig.ConnectionConfig.Type { + case connectcfg.PortForwardLinkType: + if status.Portforward == nil || status.Portforward.Health == nil || + !status.Portforward.Health.Healthy { + mon.log.Debug("port-forward not ready in sandbox manager") + return false + } + if status.Portforward.LocalAddress != mon.tpLocalAddr { + mon.log.Info("port forward is ready", "addr", status.Portforward.LocalAddress, "was", mon.tpLocalAddr) + mon.tpLocalAddr = status.Portforward.LocalAddress + return false + } + return true + case connectcfg.ControlPlaneProxyLinkType: + if status.ControlPlaneProxy == nil || status.ControlPlaneProxy.Health == nil || + !status.ControlPlaneProxy.Health.Healthy { + mon.log.Debug("control-plane proxy not ready in sandbox manager") + return false + } + if status.ControlPlaneProxy.LocalAddress != mon.tpLocalAddr { + mon.log.Info("control-plane proxy is ready", "addr", status.ControlPlaneProxy.LocalAddress, "was", mon.tpLocalAddr) + mon.tpLocalAddr = status.ControlPlaneProxy.LocalAddress + return false + } + } + return true +} + +func (mon *tpMonitor) checkRootServer(r *rootServer) (ok bool) { + if r.localnetSVC == nil || !r.localnetSVC.Status().Healthy { + return false + } + // localnet ok, check etc hosts + if r.etcHostsSVC == nil || !r.etcHostsSVC.Status().Healthy { + return false + } + return true +} + +func (mon *tpMonitor) checkAgentMetrics(ctx context.Context) (ok bool) { + cli := &http.Client{ + // don't cache connections, use fresh transport + Transport: &http.Transport{}, + Timeout: 10 * time.Second, + } + resp, err := cli.Get("http://agent-metrics.signadot.svc:9090/metrics") + if err != nil { + mon.log.Error("unable to reach agent-metrics", "error", err) + return false + } + // don't check status, that' irrelevant as to whether we communicated + // with agent metrics + resp.Body.Close() + mon.log.Debug("able to reach agent-metrics") + return true +} + +func (mon *tpMonitor) restartServices(ctx context.Context) bool { + if !mon.shouldRestartDueToUnhealthy() { + return false + } + mon.log.Info("restarting localnet and etchosts services") + + // Restart localnet + mon.root.stopLocalnetService() + mon.root.runLocalnetService(ctx, mon.tpLocalAddr, mon.ipMap) + + // Restart etc hosts + mon.root.stopEtcHostsService() + mon.root.runEtcHostsService(ctx, mon.tpLocalAddr, mon.ipMap) + return true +} + +// shouldRestartDueToUnhealthy determines whether services should be restarted +// when they become unhealthy. During the initial startup phase (first 10 seconds), +// it waits to give services time to become healthy. After startup, it immediately +// indicates that services should be restarted. +func (mon *tpMonitor) shouldRestartDueToUnhealthy() bool { + if mon.starting { + return time.Since(mon.beginStarting) > maxStartingTime + } + return true +} diff --git a/internal/locald/sandboxmanager/sandbox_server.go b/internal/locald/sandboxmanager/sandbox_server.go index 1b7bbf7..702031b 100644 --- a/internal/locald/sandboxmanager/sandbox_server.go +++ b/internal/locald/sandboxmanager/sandbox_server.go @@ -125,6 +125,25 @@ func (s *sbmServer) GetResourceOutputs(ctx context.Context, req *sbapi.GetResour return res, nil } +func (s *sbmServer) GetHosts(ctx context.Context, req *sbapi.GetHostsRequest) (*sbapi.GetHostsResponse, error) { + if !s.ciConfig.WithRootManager { + // Without a root manager there is no name-resolution service and hence + // no managed hosts. + return &sbapi.GetHostsResponse{}, nil + } + rootClient := s.getRootClient() + if rootClient == nil { + return nil, fmt.Errorf("no root manager client available") + } + rctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + resp, err := rootClient.GetHosts(rctx, &rootapi.GetHostsRequest{}) + if err != nil { + return nil, fmt.Errorf("error getting hosts from root manager: %w", err) + } + return &sbapi.GetHostsResponse{Entries: resp.Entries}, nil +} + func (s *sbmServer) rootStatus() (*commonapi.HostsStatus, *commonapi.LocalNetStatus, *commonapi.LocalDNSStatus) { if !s.ciConfig.WithRootManager { // We are running without a root manager diff --git a/internal/locald/sandboxmanager/sdk.go b/internal/locald/sandboxmanager/sdk.go index 47d93eb..c1721ed 100644 --- a/internal/locald/sandboxmanager/sdk.go +++ b/internal/locald/sandboxmanager/sdk.go @@ -38,6 +38,23 @@ func GetStatus() (*sbmapi.StatusResponse, error) { return sbStatus, nil } +func GetHosts() (*sbmapi.GetHostsResponse, error) { + // get a sandbox manager API client + grpcConn, err := connectSandboxManager() + if err != nil { + return nil, err + } + defer grpcConn.Close() + + // get the hosts + sbManagerClient := sbmapi.NewSandboxManagerAPIClient(grpcConn) + resp, err := sbManagerClient.GetHosts(context.Background(), &sbmapi.GetHostsRequest{}) + if err != nil { + return nil, processGRPCError("unable to get hosts from sandboxmanager", err) + } + return resp, nil +} + func CheckStatusConnectErrors(status *sbmapi.StatusResponse, ciConfig *config.ConnectInvocationConfig) []error { var errs []error From 7d75d85cb9af17f9ac578ba539c344c97f0e5647 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:38:25 +0200 Subject: [PATCH 09/25] deps: bump libconnect for localdns.Recover and resolver config Pulls in the standalone crash-recovery entry point and the per-connection resolver enum (EtcHosts|LocalDNS) added on the localdns branch. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 3 +-- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 7474023..b25f6fa 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32 + github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 @@ -187,4 +187,3 @@ require ( // Used for local dev //replace github.com/signadot/go-sdk => ../go-sdk -// replace github.com/signadot/libconnect => ../libconnect diff --git a/go.sum b/go.sum index 5a42875..0ff0b50 100644 --- a/go.sum +++ b/go.sum @@ -458,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.20260616155342-631831380993 h1:bF1URmHU+RLBAfDj7asExSv6WClCFwBAP0YPRpg/OjE= github.com/signadot/go-sdk v0.3.8-0.20260616155342-631831380993/go.mod h1:p+PG9uZvx5RTYtYovQQnrbzzB7a1SQ4A3WYNx6PC2xE= -github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32 h1:ATP/W+jdNvewnBBUQISdrgciSMuLAjPfQ8qXuNOQ0Es= -github.com/signadot/libconnect v0.1.1-0.20260706215411-37170079ef32/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8 h1:SM2xrpLR+UnXV9g3KAIx/B7LP4YKSndHJlb3MmQF/3U= +github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8/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= From ce126a79df5361703301324f185b5817777c43e8 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:38:25 +0200 Subject: [PATCH 10/25] local: reap sandboxmanager on error and recover DNS in all modes Route the name-service startup error through the full cleanup so the already-spawned sandboxmanager child isn't orphaned. Also call localdns.Recover unconditionally at startup, so a plain connect after a crashed --local-dns session repairs the OS resolver regardless of this session's mode. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/locald/rootmanager/root_manager.go | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/locald/rootmanager/root_manager.go b/internal/locald/rootmanager/root_manager.go index 22423ab..cea8818 100644 --- a/internal/locald/rootmanager/root_manager.go +++ b/internal/locald/rootmanager/root_manager.go @@ -73,6 +73,16 @@ func (m *rootManager) Run(ctx context.Context) error { // host->address table regardless of which name-resolution service runs. m.root.setIPMap(ipMap) + // Repair OS resolver config left behind by a crashed prior --local-dns + // session, regardless of this session's mode. A --local-dns session that + // dies uncleanly can leave the system resolver pointing at our dead server + // (all DNS broken on Linux); the natural next move is a plain + // `signadot local connect`, so recovery must not be gated on --local-dns. + // Best-effort: never block connect on it. No-op when there's nothing to fix. + if err := localdns.Recover(localdns.Config{LocalNetPath: m.ciConfig.LocalNetPath}, m.log); err != nil { + m.log.Warn("localdns crash-recovery failed", "error", err) + } + // Run the sandbox manager go m.sbmMonitor.run() @@ -85,7 +95,10 @@ func (m *rootManager) Run(ctx context.Context) error { // Start localnet and the name-resolution service (localdns or etchosts) m.runLocalnetService(ctx, m.ciConfig.ConnectionConfig.ProxyAddress, ipMap) if err := m.runNameService(ctx, m.ciConfig.ConnectionConfig.ProxyAddress, ipMap); err != nil { - _ = m.stopLocalnetService() + // Route through the full cleanup: the sandbox manager was already + // spawned above as a daemonized child and stop() is the only thing + // that reaps it, so a bare return here would orphan it. + _ = m.cleanup() return fmt.Errorf("error starting name resolution: %w", err) } } @@ -98,6 +111,13 @@ func (m *rootManager) Run(ctx context.Context) error { } // Clean up + return m.cleanup() +} + +// cleanup tears down everything Run started. It is safe to call on the startup +// error path as well as the normal shutdown path: each stop helper is a no-op +// when its service was never started. +func (m *rootManager) cleanup() error { m.log.Info("Shutting down") var me *multierror.Error if m.tpMonitor != nil { From 942980383018f1aff45fdcd6d91c2da2c7bf59a0 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:39:39 +0200 Subject: [PATCH 11/25] local: fix --local-dns tunnel-proxy restart loop checkRootServer gated tunnel-proxy health on the /etc/hosts service, which is always nil under --local-dns, so PortForward/ControlPlaneProxy links tore down and reinstalled localnet + the OS resolver config every ~10s. Gate on whichever name service is active for the mode, via the mutex-guarded accessors (closing a pre-existing race). The decision is extracted into a pure evalRootServerHealth for a mode-matrix test. Also drops a stray tp_monitor.go.head merge artifact. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/locald/rootmanager/tp_monitor.go | 52 ++++- .../locald/rootmanager/tp_monitor.go.head | 218 ------------------ .../locald/rootmanager/tp_monitor_test.go | 117 ++++++++++ 3 files changed, 158 insertions(+), 229 deletions(-) delete mode 100644 internal/locald/rootmanager/tp_monitor.go.head create mode 100644 internal/locald/rootmanager/tp_monitor_test.go diff --git a/internal/locald/rootmanager/tp_monitor.go b/internal/locald/rootmanager/tp_monitor.go index bd16070..dc76d56 100644 --- a/internal/locald/rootmanager/tp_monitor.go +++ b/internal/locald/rootmanager/tp_monitor.go @@ -204,20 +204,50 @@ func (mon *tpMonitor) checkLinkStatus(status *sbmanagerapi.StatusResponse) (ok, } func (mon *tpMonitor) checkRootServer(r *rootServer) (ok, restart bool) { - if r.localnetSVC == nil || !r.localnetSVC.Status().Healthy { - if mon.shouldRestartDueToUnhealthy() { - return true, true - } else { - return false, false - } + // Use the mutex-guarded accessors: these fields are written from the + // name-service (re)start paths on other goroutines. The inactive + // name-resolution service is nil (etcHostsSVC under --local-dns, localDNSSVC + // otherwise), and the nil check short-circuits its Status() call. + localnetSVC := r.getLocalnetService() + localDNSSVC, _ := r.getLocalDNSService() + etcHostsSVC := r.getEtcHostsService() + return evalRootServerHealth(rootServerHealth{ + enableLocalDNS: mon.root.ciConfig.EnableLocalDNS, + localnetHealthy: localnetSVC != nil && localnetSVC.Status().Healthy, + localDNSHealthy: localDNSSVC != nil && localDNSSVC.Status().Healthy, + etcHostsHealthy: etcHostsSVC != nil && etcHostsSVC.Status().Healthy, + allowRestart: mon.shouldRestartDueToUnhealthy(), + }) +} + +// rootServerHealth is the input to evalRootServerHealth: the presence-and-health +// of each root-managed service plus whether a restart is currently allowed. +type rootServerHealth struct { + enableLocalDNS bool + localnetHealthy bool + localDNSHealthy bool + etcHostsHealthy bool + allowRestart bool +} + +// evalRootServerHealth is the pure decision behind checkRootServer, extracted so +// the mode matrix (which name-resolution service gates health) is unit-testable +// without standing up real localnet/localdns/etchosts services. Health gates on +// localnet and on whichever name service is active for the mode: with +// --local-dns that is localdns, otherwise etchosts. Gating on the wrong one +// would report perpetual unhealthiness and restart the services every cycle. +// allowRestart mirrors shouldRestartDueToUnhealthy: during the startup grace +// period an unhealthy service is tolerated rather than restarted. +func evalRootServerHealth(h rootServerHealth) (ok, restart bool) { + nameSvcHealthy := h.etcHostsHealthy + if h.enableLocalDNS { + nameSvcHealthy = h.localDNSHealthy } - // localnet ok, check etc hosts - if r.etcHostsSVC == nil || !r.etcHostsSVC.Status().Healthy { - if mon.shouldRestartDueToUnhealthy() { + if !h.localnetHealthy || !nameSvcHealthy { + if h.allowRestart { return true, true - } else { - return false, false } + return false, false } return true, false } diff --git a/internal/locald/rootmanager/tp_monitor.go.head b/internal/locald/rootmanager/tp_monitor.go.head deleted file mode 100644 index 0eea0b9..0000000 --- a/internal/locald/rootmanager/tp_monitor.go.head +++ /dev/null @@ -1,218 +0,0 @@ -package rootmanager - -import ( - "context" - "fmt" - "net/http" - "time" - - "log/slog" - - sbmanagerapi "github.com/signadot/cli/internal/locald/api/sandboxmanager" - connectcfg "github.com/signadot/libconnect/config" - "github.com/signadot/libconnect/fwdtun/ipmap" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -const ( - checkingPeriodNotOK = time.Second - checkingPeriodOK = 10 * time.Second - // maxStartingTime needs to be large enough for worst case startup time - maxStartingTime = 30 * time.Second -) - -type tpMonitor struct { - log *slog.Logger - root *rootManager - sbManagerAddr string - tpLocalAddr string - ipMap *ipmap.IPMap - starting bool - beginStarting time.Time - sbClient sbmanagerapi.SandboxManagerAPIClient - closeCh chan struct{} -} - -func NewTunnelProxyMonitor(ctx context.Context, root *rootManager, ipMap *ipmap.IPMap) *tpMonitor { - mon := &tpMonitor{ - log: root.log, - ipMap: ipMap, - sbManagerAddr: fmt.Sprintf("127.0.0.1:%d", root.ciConfig.APIPort), - root: root, - starting: true, - beginStarting: time.Now(), - closeCh: make(chan struct{}), - } - go mon.run(ctx) - return mon -} - -func (mon *tpMonitor) Stop() { - select { - case <-mon.closeCh: - return - default: - close(mon.closeCh) - } -} - -func (mon *tpMonitor) run(ctx context.Context) { - ticker := time.NewTicker(checkingPeriodNotOK) - defer ticker.Stop() - - mon.starting = true - mon.beginStarting = time.Now() - for { - success, restarted := mon.checkTunnelProxyAccess(ctx) - if success { - ticker.Reset(checkingPeriodOK) - mon.starting = false - } else { - ticker.Reset(checkingPeriodNotOK) - if restarted || !mon.starting { - mon.beginStarting = time.Now() - } - mon.starting = true - } - select { - case <-ctx.Done(): - // Context is done - return - case <-mon.closeCh: - // We have been stopped - return - case <-ticker.C: - // Check ticker - } - } -} - -func (mon *tpMonitor) checkTunnelProxyAccess(ctx context.Context) (success bool, restarted bool) { - mon.log.Debug("checking tunnel-proxy access") - if mon.sbClient == nil { - // Establish the connection if needed - grpcConn, err := grpc.NewClient(mon.sbManagerAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - if mon.starting { - mon.log.Debug("waiting for sandbox manager to be ready (not running yet)") - } else { - mon.log.Warn("couldn't connect with sandbox manager", "error", err) - } - return false, mon.restartServices(ctx) - } - mon.sbClient = sbmanagerapi.NewSandboxManagerAPIClient(grpcConn) - } - mon.log.Debug("connected to sandbox manager") - - // Get the sandbox manager status - status, err := mon.sbClient.Status(ctx, &sbmanagerapi.StatusRequest{}) - if err != nil { - if mon.starting { - mon.log.Debug("waiting for sandbox manager to be ready (api not ready)") - } else { - mon.log.Warn("couldn't get status from sandbox manager", "error", err) - } - return false, mon.restartServices(ctx) - } - - // Check the status - ok := mon.checkLinkStatus(status) - if !ok { - return false, mon.restartServices(ctx) - } - ok = mon.checkRootServer(mon.root.root) - if !ok { - return false, mon.restartServices(ctx) - } - ok = mon.checkAgentMetrics(ctx) - if !ok { - return false, mon.restartServices(ctx) - } - return true, false -} - -func (mon *tpMonitor) checkLinkStatus(status *sbmanagerapi.StatusResponse) (ok bool) { - switch mon.root.ciConfig.ConnectionConfig.Type { - case connectcfg.PortForwardLinkType: - if status.Portforward == nil || status.Portforward.Health == nil || - !status.Portforward.Health.Healthy { - mon.log.Debug("port-forward not ready in sandbox manager") - return false - } - if status.Portforward.LocalAddress != mon.tpLocalAddr { - mon.log.Info("port forward is ready", "addr", status.Portforward.LocalAddress, "was", mon.tpLocalAddr) - mon.tpLocalAddr = status.Portforward.LocalAddress - return false - } - return true - case connectcfg.ControlPlaneProxyLinkType: - if status.ControlPlaneProxy == nil || status.ControlPlaneProxy.Health == nil || - !status.ControlPlaneProxy.Health.Healthy { - mon.log.Debug("control-plane proxy not ready in sandbox manager") - return false - } - if status.ControlPlaneProxy.LocalAddress != mon.tpLocalAddr { - mon.log.Info("control-plane proxy is ready", "addr", status.ControlPlaneProxy.LocalAddress, "was", mon.tpLocalAddr) - mon.tpLocalAddr = status.ControlPlaneProxy.LocalAddress - return false - } - } - return true -} - -func (mon *tpMonitor) checkRootServer(r *rootServer) (ok bool) { - if r.localnetSVC == nil || !r.localnetSVC.Status().Healthy { - return false - } - // localnet ok, check etc hosts - if r.etcHostsSVC == nil || !r.etcHostsSVC.Status().Healthy { - return false - } - return true -} - -func (mon *tpMonitor) checkAgentMetrics(ctx context.Context) (ok bool) { - cli := &http.Client{ - // don't cache connections, use fresh transport - Transport: &http.Transport{}, - Timeout: 10 * time.Second, - } - resp, err := cli.Get("http://agent-metrics.signadot.svc:9090/metrics") - if err != nil { - mon.log.Error("unable to reach agent-metrics", "error", err) - return false - } - // don't check status, that' irrelevant as to whether we communicated - // with agent metrics - resp.Body.Close() - mon.log.Debug("able to reach agent-metrics") - return true -} - -func (mon *tpMonitor) restartServices(ctx context.Context) bool { - if !mon.shouldRestartDueToUnhealthy() { - return false - } - mon.log.Info("restarting localnet and etchosts services") - - // Restart localnet - mon.root.stopLocalnetService() - mon.root.runLocalnetService(ctx, mon.tpLocalAddr, mon.ipMap) - - // Restart etc hosts - mon.root.stopEtcHostsService() - mon.root.runEtcHostsService(ctx, mon.tpLocalAddr, mon.ipMap) - return true -} - -// shouldRestartDueToUnhealthy determines whether services should be restarted -// when they become unhealthy. During the initial startup phase (first 10 seconds), -// it waits to give services time to become healthy. After startup, it immediately -// indicates that services should be restarted. -func (mon *tpMonitor) shouldRestartDueToUnhealthy() bool { - if mon.starting { - return time.Since(mon.beginStarting) > maxStartingTime - } - return true -} diff --git a/internal/locald/rootmanager/tp_monitor_test.go b/internal/locald/rootmanager/tp_monitor_test.go new file mode 100644 index 0000000..e2c423f --- /dev/null +++ b/internal/locald/rootmanager/tp_monitor_test.go @@ -0,0 +1,117 @@ +package rootmanager + +import "testing" + +// TestEvalRootServerHealth is the mode matrix behind checkRootServer: health +// must gate on the name-resolution service that is actually active for the mode +// (localdns under --local-dns, etchosts otherwise). The regression it guards +// against is the restart loop where --local-dns gated on the always-nil +// etcHostsSVC and restarted the services every cycle. +func TestEvalRootServerHealth(t *testing.T) { + tests := []struct { + name string + in rootServerHealth + wantOK bool + wantRestart bool + }{ + // --- localdns mode --- + { + // The regression scenario: localdns healthy, etchosts absent + // (nil->false). Must be healthy, NOT a restart. + name: "localdns healthy, etchosts absent -> healthy", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: true, + localDNSHealthy: true, etcHostsHealthy: false, allowRestart: true, + }, + wantOK: true, wantRestart: false, + }, + { + name: "localdns unhealthy past grace -> restart", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: true, + localDNSHealthy: false, etcHostsHealthy: false, allowRestart: true, + }, + wantOK: true, wantRestart: true, + }, + { + name: "localdns unhealthy in grace -> wait", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: true, + localDNSHealthy: false, etcHostsHealthy: false, allowRestart: false, + }, + wantOK: false, wantRestart: false, + }, + { + // etchosts health must be ignored in localdns mode. + name: "localdns healthy, etchosts unhealthy -> healthy", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: true, + localDNSHealthy: true, etcHostsHealthy: false, allowRestart: true, + }, + wantOK: true, wantRestart: false, + }, + + // --- etchosts mode --- + { + name: "etchosts healthy, localdns absent -> healthy", + in: rootServerHealth{ + enableLocalDNS: false, localnetHealthy: true, + localDNSHealthy: false, etcHostsHealthy: true, allowRestart: true, + }, + wantOK: true, wantRestart: false, + }, + { + // localdns health must be ignored in etchosts mode. + name: "etchosts unhealthy, localdns healthy -> restart", + in: rootServerHealth{ + enableLocalDNS: false, localnetHealthy: true, + localDNSHealthy: true, etcHostsHealthy: false, allowRestart: true, + }, + wantOK: true, wantRestart: true, + }, + { + name: "etchosts unhealthy in grace -> wait", + in: rootServerHealth{ + enableLocalDNS: false, localnetHealthy: true, + localDNSHealthy: false, etcHostsHealthy: false, allowRestart: false, + }, + wantOK: false, wantRestart: false, + }, + + // --- localnet gating (independent of mode) --- + { + name: "localnet unhealthy past grace -> restart", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: false, + localDNSHealthy: true, etcHostsHealthy: true, allowRestart: true, + }, + wantOK: true, wantRestart: true, + }, + { + name: "localnet unhealthy in grace -> wait", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: false, + localDNSHealthy: true, etcHostsHealthy: true, allowRestart: false, + }, + wantOK: false, wantRestart: false, + }, + { + name: "all healthy (localdns) -> healthy", + in: rootServerHealth{ + enableLocalDNS: true, localnetHealthy: true, + localDNSHealthy: true, etcHostsHealthy: false, allowRestart: false, + }, + wantOK: true, wantRestart: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ok, restart := evalRootServerHealth(tt.in) + if ok != tt.wantOK || restart != tt.wantRestart { + t.Errorf("evalRootServerHealth(%+v) = (ok=%v, restart=%v), want (ok=%v, restart=%v)", + tt.in, ok, restart, tt.wantOK, tt.wantRestart) + } + }) + } +} From 04f6c8bf21f08ee89791aef86246530e19801f6f Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:39:39 +0200 Subject: [PATCH 12/25] local: opt into local DNS via connection resolver config A connection's resolver: EtcHosts|LocalDNS field is now the encouraged way to opt in; --local-dns remains a per-invocation override applied only when explicitly set. The --unprivileged conflict check fires for the config-sourced mode too, naming the source. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/connect.go | 24 +++++++++++++++++++----- internal/config/local.go | 3 ++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/internal/command/local/connect.go b/internal/command/local/connect.go index e3872f8..9a47e9d 100644 --- a/internal/command/local/connect.go +++ b/internal/command/local/connect.go @@ -68,10 +68,6 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg // fast response and is safe to return an error here, but the check is _not_ // used to assume that we have the lock later on when starting. withRootManager := !cfg.Unprivileged - if cfg.LocalDNS && !withRootManager { - return fmt.Errorf("--local-dns manages the system DNS resolver and requires root; " + - "it cannot be used with --unprivileged") - } pidFile := config.GetLocaldPIDfile(signadotDir, withRootManager) isRunning, err := processes.IsDaemonRunning(pidFile) if err != nil { @@ -87,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() @@ -166,7 +180,7 @@ func runConnect(cmd *cobra.Command, out io.Writer, cfg *config.LocalConnect, arg }, Env: os.Environ(), ConnectionConfig: connConfig, - EnableLocalDNS: cfg.LocalDNS, + EnableLocalDNS: enableLocalDNS, ProxyURL: cfg.ProxyURL, APIURL: cfg.API.APIURL, APIKey: cfg.GetAPIKey(), diff --git a/internal/config/local.go b/internal/config/local.go index 1891df9..c4271cf 100644 --- a/internal/config/local.go +++ b/internal/config/local.go @@ -117,7 +117,8 @@ func (c *LocalConnect) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&c.Unprivileged, "unprivileged", false, "run without root privileges") cmd.Flags().BoolVar(&c.LocalDNS, "local-dns", false, - "resolve cluster service names with a local DNS resolver instead of /etc/hosts") + "resolve cluster service names with a local DNS resolver instead of /etc/hosts "+ + "(overrides the connection's 'resolver' config field for this invocation)") cmd.Flags().Var(&c.Wait, "wait", "status to wait for while connecting {none,connect,sandboxes}") cmd.Flags().DurationVar(&c.WaitTimeout, "wait-timeout", 10*time.Second, "timeout to wait") From 49aac83b6a39870c2a4d4a387f4959275be67973 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:39:55 +0200 Subject: [PATCH 13/25] local hosts: single ip per host, sdtab table output A host resolves to one virtual address, so collapse HostEntry.ips to a scalar ip at the gRPC boundary and select it (IPv4-preferred) in the root manager; the ipMap keeps []ip as the backing the resolver needs for A/AAAA answers. Default output uses the shared sdtab table for consistency with the other list commands. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/hosts.go | 46 ++--- internal/command/local/hosts_test.go | 44 +++++ internal/locald/api/common.pb.go | 169 +++++++++--------- internal/locald/api/common.proto | 5 +- internal/locald/rootmanager/root_server.go | 28 ++- .../locald/rootmanager/root_server_test.go | 37 ++++ 6 files changed, 208 insertions(+), 121 deletions(-) create mode 100644 internal/command/local/hosts_test.go create mode 100644 internal/locald/rootmanager/root_server_test.go diff --git a/internal/command/local/hosts.go b/internal/command/local/hosts.go index 66634e6..13a8f8e 100644 --- a/internal/command/local/hosts.go +++ b/internal/command/local/hosts.go @@ -3,11 +3,11 @@ package local import ( "fmt" "io" - "net" "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" ) @@ -27,9 +27,8 @@ func newHosts(localConfig *config.Local) *cobra.Command { } // printableHost is the JSON/YAML shape of a single resolvable host. Each host -// carries exactly one address (see pickIP), which matches how the cluster -// assigns names: a single-stack cluster hands out one family, and even when -// both are present a resolver answers a name with one address at a time. +// 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"` @@ -46,12 +45,7 @@ func runHosts(cfg *config.LocalHosts, out io.Writer, args []string) error { hosts := make([]printableHost, 0, len(resp.Entries)) for _, e := range resp.Entries { - ip := pickIP(e.Ips) - if ip == "" { - // A name with no usable address is not resolvable; skip it. - continue - } - hosts = append(hosts, printableHost{Name: e.Name, IP: ip}) + hosts = append(hosts, printableHost{Name: e.Name, IP: e.Ip}) } switch cfg.OutputFormat { @@ -66,33 +60,19 @@ func runHosts(cfg *config.LocalHosts, out io.Writer, args []string) error { } } -// pickIP selects the single address to report for a host: the IPv4 address when -// one is present, otherwise the first IPv6 address (covering an IPv6-only -// cluster). It returns "" when there is no parseable address. -func pickIP(ips []string) string { - var fallback string - for _, ip := range ips { - parsed := net.ParseIP(ip) - if parsed == nil { - continue - } - if parsed.To4() != nil { - return ip - } - if fallback == "" { - fallback = ip - } - } - return fallback +type hostRow struct { + Name string `sdtab:"NAME"` + IP string `sdtab:"IP"` } -// printHosts writes one " " line per host. The entries arrive already +// 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 { + t := sdtab.New[hostRow](out) + t.AddHeader() for _, h := range hosts { - if _, err := fmt.Fprintf(out, "%s %s\n", h.Name, h.IP); err != nil { - return err - } + t.AddRow(hostRow{Name: h.Name, IP: h.IP}) } - return nil + return t.Flush() } diff --git a/internal/command/local/hosts_test.go b/internal/command/local/hosts_test.go new file mode 100644 index 0000000..7847497 --- /dev/null +++ b/internal/command/local/hosts_test.go @@ -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()) + } +} diff --git a/internal/locald/api/common.pb.go b/internal/locald/api/common.pb.go index f05ecae..28d52f6 100644 --- a/internal/locald/api/common.pb.go +++ b/internal/locald/api/common.pb.go @@ -350,8 +350,11 @@ type HostEntry struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Ips []string `protobuf:"bytes,2,rep,name=ips,proto3" json:"ips,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Single resolvable address for the host. A host is assigned one virtual + // address (IPv4-preferred; IPv6 on an IPv6-only allocation), so there is no + // plurality to carry here. + Ip string `protobuf:"bytes,2,opt,name=ip,proto3" json:"ip,omitempty"` } func (x *HostEntry) Reset() { @@ -393,11 +396,11 @@ func (x *HostEntry) GetName() string { return "" } -func (x *HostEntry) GetIps() []string { +func (x *HostEntry) GetIp() string { if x != nil { - return x.Ips + return x.Ip } - return nil + return "" } // PortForward status (produced by local controller) @@ -1035,91 +1038,91 @@ var file_internal_locald_api_common_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x66, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x31, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x2f, 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x10, 0x0a, 0x03, 0x69, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x69, 0x70, - 0x73, 0x22, 0x6a, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, + 0x6a, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, + 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, + 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, - 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, - 0x78, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, - 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, - 0x41, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x22, 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, - 0x69, 0x6e, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, - 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, - 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, - 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, - 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, - 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, - 0x50, 0x6f, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, - 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x3d, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, - 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, - 0x6c, 0x69, 0x6e, 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, - 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, - 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, - 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, - 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, - 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, - 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, - 0x6e, 0x65, 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, - 0x65, 0x22, 0xdb, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, - 0x74, 0x68, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, - 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0d, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, - 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x41, 0x0a, + 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, + 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x22, 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, + 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, + 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x57, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, 0x73, 0x65, + 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, 0x42, 0x61, + 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x23, 0x0a, + 0x0d, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x6f, + 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, + 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, + 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, + 0x6e, 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, 0x0a, 0x13, + 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, + 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, + 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, 0x6e, 0x6e, + 0x65, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, + 0xdb, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, + 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, + 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x2d, 0x5a, + 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/locald/api/common.proto b/internal/locald/api/common.proto index 603d351..891b01a 100644 --- a/internal/locald/api/common.proto +++ b/internal/locald/api/common.proto @@ -49,7 +49,10 @@ message LocalDNSStatus { // draw from the same address allocator in the root controller. message HostEntry { string name = 1; - repeated string ips = 2; + // Single resolvable address for the host. A host is assigned one virtual + // address (IPv4-preferred; IPv6 on an IPv6-only allocation), so there is no + // plurality to carry here. + string ip = 2; } // PortForward status (produced by local controller) diff --git a/internal/locald/rootmanager/root_server.go b/internal/locald/rootmanager/root_server.go index 6f974ec..bf117e9 100644 --- a/internal/locald/rootmanager/root_server.go +++ b/internal/locald/rootmanager/root_server.go @@ -2,6 +2,7 @@ package rootmanager import ( "context" + "net" "sort" "sync" @@ -152,13 +153,14 @@ func (s *rootServer) GetHosts(ctx context.Context, req *rootapi.GetHostsRequest) entries := ipMap.Entries() resp.Entries = make([]*commonapi.HostEntry, 0, len(entries)) for name, ips := range entries { - strIPs := make([]string, len(ips)) - for i, ip := range ips { - strIPs[i] = ip.String() + ip := pickIP(ips) + if ip == "" { + // A host with no assigned address is not resolvable; skip it. + continue } resp.Entries = append(resp.Entries, &commonapi.HostEntry{ Name: name, - Ips: strIPs, + Ip: ip, }) } // Stable, name-sorted output so callers (and `signadot local hosts`) get a @@ -169,6 +171,24 @@ func (s *rootServer) GetHosts(ctx context.Context, req *rootapi.GetHostsRequest) return resp, nil } +// pickIP returns the single address to report for a host: the IPv4 address when +// one is present, otherwise the first IPv6 address (covering an IPv6-only +// allocation). It returns "" when the host has no address. A host is assigned +// one virtual address in practice; the preference only makes the choice +// deterministic should it ever carry both families. +func pickIP(ips []net.IP) string { + var fallback string + for _, ip := range ips { + if ip.To4() != nil { + return ip.String() + } + if fallback == "" { + fallback = ip.String() + } + } + return fallback +} + func (s *rootServer) setIPMap(ipMap *ipmap.IPMap) { s.mu.Lock() defer s.mu.Unlock() diff --git a/internal/locald/rootmanager/root_server_test.go b/internal/locald/rootmanager/root_server_test.go new file mode 100644 index 0000000..da4619c --- /dev/null +++ b/internal/locald/rootmanager/root_server_test.go @@ -0,0 +1,37 @@ +package rootmanager + +import ( + "net" + "testing" +) + +// TestPickIP covers the single-address selection GetHosts reports per host: +// IPv4 preferred, IPv6 fallback, empty when there is nothing to report. +func TestPickIP(t *testing.T) { + v4 := net.ParseIP("242.242.0.3") + v4b := net.ParseIP("242.242.0.9") + v6 := net.ParseIP("fd00:5161::3") + v6b := net.ParseIP("fd00:5161::9") + + tests := []struct { + name string + in []net.IP + want string + }{ + {"nil", nil, ""}, + {"empty", []net.IP{}, ""}, + {"v4 only", []net.IP{v4}, "242.242.0.3"}, + {"v6 only", []net.IP{v6}, "fd00:5161::3"}, + {"v4 before v6", []net.IP{v4, v6}, "242.242.0.3"}, + {"v6 before v4 -> still v4", []net.IP{v6, v4}, "242.242.0.3"}, + {"two v6 -> first", []net.IP{v6, v6b}, "fd00:5161::3"}, + {"two v4 -> first", []net.IP{v4, v4b}, "242.242.0.3"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pickIP(tt.in); got != tt.want { + t.Errorf("pickIP(%v) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} From 9a4d687b27e594f705e073f3eab097e9843aa74f Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 12:39:55 +0200 Subject: [PATCH 14/25] local: omit hosts status under --local-dns, clearer GetHosts error, gofmt getRawHosts returns nil under --local-dns so status JSON/YAML no longer emits a bogus failed /etc/hosts section. GetHosts maps a daemon-side Unimplemented to disconnect/reconnect guidance rather than the operator-upgrade message. Realign ConnectInvocationConfig struct tags. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/printers.go | 7 ++ internal/command/local/printers_test.go | 93 ++++++++++++++++++++++ internal/config/locald.go | 18 ++--- internal/locald/sandboxmanager/sdk.go | 8 ++ internal/locald/sandboxmanager/sdk_test.go | 62 +++++++++++++++ 5 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 internal/command/local/printers_test.go create mode 100644 internal/locald/sandboxmanager/sdk_test.go diff --git a/internal/command/local/printers.go b/internal/command/local/printers.go index a51ddcc..babe4c3 100644 --- a/internal/command/local/printers.go +++ b/internal/command/local/printers.go @@ -188,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 diff --git a/internal/command/local/printers_test.go b/internal/command/local/printers_test.go new file mode 100644 index 0000000..cd8d866 --- /dev/null +++ b/internal/command/local/printers_test.go @@ -0,0 +1,93 @@ +package local + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/signadot/cli/internal/config" + commonapi "github.com/signadot/cli/internal/locald/api" +) + +// TestGetRawHostsGating covers the section gating for the JSON/YAML status +// output: /etc/hosts status is omitted under --local-dns (where the root +// manager reports no hosts status) rather than emitted as a bogus failure. +func TestGetRawHostsGating(t *testing.T) { + cfg := &config.LocalStatus{} + statusMap := map[string]any{} + hosts := &commonapi.HostsStatus{ + Health: &commonapi.ServiceHealth{Healthy: true}, + NumHosts: 5, + } + + t.Run("local-dns mode omits hosts", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: true, EnableLocalDNS: true} + if got := getRawHosts(cfg, ci, hosts, statusMap); got != nil { + t.Errorf("getRawHosts under --local-dns = %v, want nil", got) + } + }) + + t.Run("etchosts mode reports hosts", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: true, EnableLocalDNS: false} + got := getRawHosts(cfg, ci, hosts, statusMap) + if got == nil { + t.Fatal("getRawHosts in etchosts mode = nil, want a hosts section") + } + if js := mustJSON(t, got); !strings.Contains(js, `"healthy":true`) || !strings.Contains(js, `"numHosts":5`) { + t.Errorf("unexpected hosts json: %s", js) + } + }) + + t.Run("no root manager returns raw status", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: false} + if got := getRawHosts(cfg, ci, hosts, statusMap); got == nil { + t.Error("getRawHosts without root manager = nil, want the raw status") + } + }) +} + +// TestGetRawLocalDNSGating is the inverse: local DNS status is present only when +// running under --local-dns with the root manager. +func TestGetRawLocalDNSGating(t *testing.T) { + cfg := &config.LocalStatus{} + statusMap := map[string]any{} + ldns := &commonapi.LocalDNSStatus{ + Health: &commonapi.ServiceHealth{Healthy: true}, + RecordCount: 7, + BindAddr: "127.0.0.54:53", + } + + t.Run("local-dns mode reports localDNS", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: true, EnableLocalDNS: true} + got := getRawLocalDNS(cfg, ci, ldns, statusMap) + if got == nil { + t.Fatal("getRawLocalDNS under --local-dns = nil, want a localDNS section") + } + if js := mustJSON(t, got); !strings.Contains(js, `"healthy":true`) { + t.Errorf("unexpected localDNS json: %s", js) + } + }) + + t.Run("etchosts mode omits localDNS", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: true, EnableLocalDNS: false} + if got := getRawLocalDNS(cfg, ci, ldns, statusMap); got != nil { + t.Errorf("getRawLocalDNS in etchosts mode = %v, want nil", got) + } + }) + + t.Run("no root manager omits localDNS", func(t *testing.T) { + ci := &config.ConnectInvocationConfig{WithRootManager: false, EnableLocalDNS: true} + if got := getRawLocalDNS(cfg, ci, ldns, statusMap); got != nil { + t.Errorf("getRawLocalDNS without root manager = %v, want nil", got) + } + }) +} + +func mustJSON(t *testing.T, v any) string { + t.Helper() + b, err := json.Marshal(v) + if err != nil { + t.Fatalf("marshal: %v", err) + } + return string(b) +} diff --git a/internal/config/locald.go b/internal/config/locald.go index 21a0fcf..2e89c1b 100644 --- a/internal/config/locald.go +++ b/internal/config/locald.go @@ -79,15 +79,15 @@ type ConnectInvocationConfig struct { // EnableLocalDNS runs the libconnect localdns resolver (managing the OS // resolver config) in place of the /etc/hosts mechanism for cluster names. - EnableLocalDNS bool `json:"enableLocalDNS,omitempty"` - ProxyURL string `json:"proxyURL"` - APIURL string `json:"apiURL"` - APIKey string `json:"apiKey"` - ConfigFile string `json:"configFile"` - Debug bool `json:"debug"` - ConnectTimeout string `json:"connectTimeout"` - DevboxID string `json:"devboxID"` - DevboxSessionID string `json:"devboxSessionID"` + EnableLocalDNS bool `json:"enableLocalDNS,omitempty"` + ProxyURL string `json:"proxyURL"` + APIURL string `json:"apiURL"` + APIKey string `json:"apiKey"` + ConfigFile string `json:"configFile"` + Debug bool `json:"debug"` + ConnectTimeout string `json:"connectTimeout"` + DevboxID string `json:"devboxID"` + DevboxSessionID string `json:"devboxSessionID"` // LocalNetPath is prepended to PATH when the root-manager invokes // tunnel-setup commands (iptables on linux; pfctl and route on diff --git a/internal/locald/sandboxmanager/sdk.go b/internal/locald/sandboxmanager/sdk.go index c1721ed..51a1b0a 100644 --- a/internal/locald/sandboxmanager/sdk.go +++ b/internal/locald/sandboxmanager/sdk.go @@ -50,6 +50,14 @@ func GetHosts() (*sbmapi.GetHostsResponse, error) { sbManagerClient := sbmapi.NewSandboxManagerAPIClient(grpcConn) resp, err := sbManagerClient.GetHosts(context.Background(), &sbmapi.GetHostsRequest{}) if err != nil { + // This call is CLI-to-local-daemon, so Unimplemented means the running + // locald predates 'local hosts' (the user upgraded the CLI while + // connected), not a stale operator. Give restart guidance rather than + // processGRPCError's "upgrade the operator" message. + if st, ok := status.FromError(err); ok && st.Code() == codes.Unimplemented { + return nil, fmt.Errorf("the running local daemon predates 'signadot local hosts'; " + + "run 'signadot local disconnect && signadot local connect' to restart it") + } return nil, processGRPCError("unable to get hosts from sandboxmanager", err) } return resp, nil diff --git a/internal/locald/sandboxmanager/sdk_test.go b/internal/locald/sandboxmanager/sdk_test.go new file mode 100644 index 0000000..37feab6 --- /dev/null +++ b/internal/locald/sandboxmanager/sdk_test.go @@ -0,0 +1,62 @@ +package sandboxmanager + +import ( + "strings" + "testing" + + commonapi "github.com/signadot/cli/internal/locald/api" +) + +func TestCheckLocalDNSStatus(t *testing.T) { + tests := []struct { + name string + in *commonapi.LocalDNSStatus + wantErr bool + wantSubstr string + }{ + {"nil status", nil, true, "failed to configure local DNS resolver"}, + {"nil health", &commonapi.LocalDNSStatus{}, true, "failed to configure local DNS resolver"}, + { + "healthy", + &commonapi.LocalDNSStatus{Health: &commonapi.ServiceHealth{Healthy: true}}, + false, "", + }, + { + "unhealthy with reason", + &commonapi.LocalDNSStatus{Health: &commonapi.ServiceHealth{ + Healthy: false, LastErrorReason: "bind failed"}}, + true, "bind failed", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkLocalDNSStatus(tt.in) + if tt.wantErr != (err != nil) { + t.Fatalf("checkLocalDNSStatus err = %v, wantErr = %v", err, tt.wantErr) + } + if err != nil && tt.wantSubstr != "" && !strings.Contains(err.Error(), tt.wantSubstr) { + t.Errorf("error %q does not contain %q", err.Error(), tt.wantSubstr) + } + }) + } +} + +func TestCheckHostsStatus(t *testing.T) { + tests := []struct { + name string + in *commonapi.HostsStatus + wantErr bool + }{ + {"nil status", nil, true}, + {"nil health", &commonapi.HostsStatus{}, true}, + {"healthy", &commonapi.HostsStatus{Health: &commonapi.ServiceHealth{Healthy: true}}, false}, + {"unhealthy", &commonapi.HostsStatus{Health: &commonapi.ServiceHealth{Healthy: false}}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := checkHostsStatus(tt.in); tt.wantErr != (err != nil) { + t.Errorf("checkHostsStatus err = %v, wantErr = %v", err, tt.wantErr) + } + }) + } +} From 48a594ec702f2957b5c1a7059671aa38f8451661 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 16:55:05 +0200 Subject: [PATCH 15/25] deps: bump libconnect for localdns bind-address probe Pulls in the fix for the 127.0.0.54 collision with systemd-resolved: localdns now probes for a free loopback address instead of hardcoding one. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b25f6fa..a977193 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8 + github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index 0ff0b50..b0c5496 100644 --- a/go.sum +++ b/go.sum @@ -460,6 +460,8 @@ github.com/signadot/go-sdk v0.3.8-0.20260616155342-631831380993 h1:bF1URmHU+RLBA github.com/signadot/go-sdk v0.3.8-0.20260616155342-631831380993/go.mod h1:p+PG9uZvx5RTYtYovQQnrbzzB7a1SQ4A3WYNx6PC2xE= github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8 h1:SM2xrpLR+UnXV9g3KAIx/B7LP4YKSndHJlb3MmQF/3U= github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd h1:m4aGKr5M3oz3vFZt4IQLPC/z00N4yhlP5X7IJxZCZoQ= +github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd/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= From 587761eec1a19e0f88c5171dcdf0072c732c48fa Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 18:40:48 +0200 Subject: [PATCH 16/25] deps: bump libconnect for localdns ndots:5 fix Pulls in options ndots:5 in the managed resolv.conf so cluster short names (.) resolve via the search list locally instead of a slow absolute upstream query (~7s -> sub-second). Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index a977193..27a9c0e 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd + github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index b0c5496..098e868 100644 --- a/go.sum +++ b/go.sum @@ -462,6 +462,8 @@ github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8 h1:SM2xrpLR+ github.com/signadot/libconnect v0.1.1-0.20260710092926-300395d4c4d8/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd h1:m4aGKr5M3oz3vFZt4IQLPC/z00N4yhlP5X7IJxZCZoQ= github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712 h1:hSeEu/QUBkmRMJ1a6fYpox3URsa2qPU6/p8FVWo2yAY= +github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712/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= From f2ebe46c5e88e2e53a25deebdb90f3b3d993c399 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Fri, 10 Jul 2026 19:23:52 +0200 Subject: [PATCH 17/25] deps: bump libconnect for Linux short-name synthesis Replaces the resolv.conf ndots:5 approach with cluster short-name synthesis on Linux, so both FQDNs and . short names resolve on the absolute query (fixes the FQDN-vs-short-name latency inversion). Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 27a9c0e..9da1fae 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712 + github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index 098e868..b76a4b8 100644 --- a/go.sum +++ b/go.sum @@ -464,6 +464,8 @@ github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd h1:m4aGKr5M3 github.com/signadot/libconnect v0.1.1-0.20260710145300-e1e03fd7f0cd/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712 h1:hSeEu/QUBkmRMJ1a6fYpox3URsa2qPU6/p8FVWo2yAY= github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7 h1:J6o0E0hE5PPERlo5lZGkexNfsDNGec39RoXbf6iZ18c= +github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7/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= From ce4f10d20ff062bc9eb7f2d9e222a655e49c0488 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 08:37:37 +0200 Subject: [PATCH 18/25] local: label the local-DNS status count as "names", not "cluster names" `local status` printed "N cluster names resolvable via local DNS" using the resolver's record count, which includes the synthesized short forms (., ..svc) and is thus a multiple of the host count shown by `local hosts` (e.g. 147 vs 49). Say "names" to avoid the mismatch reading as a discrepancy between the two commands. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/printers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/command/local/printers.go b/internal/command/local/printers.go index babe4c3..04e907f 100644 --- a/internal/command/local/printers.go +++ b/internal/command/local/printers.go @@ -565,7 +565,11 @@ func (p *statusPrinter) printLocalDNSStatus() { return } if ldns.Health.Healthy { - p.printLine(p.out, 1, fmt.Sprintf("%d cluster names resolvable via local DNS (%s)", + // RecordCount is the total resolvable DNS names, which includes the + // synthesized short forms (e.g. ., ..svc) — so it is a + // multiple of the host count shown by `local hosts`. Say "names", not + // "hosts", to avoid a confusing mismatch between the two commands. + p.printLine(p.out, 1, fmt.Sprintf("%d names resolvable via local DNS (%s)", ldns.RecordCount, ldns.BindAddr), "*") } else { p.printLine(p.out, 1, fmt.Sprintf("local DNS resolver not healthy (%q)", From c4e43a4025a71d526803f4231257fedcf1d26e65 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 08:37:37 +0200 Subject: [PATCH 19/25] deps: bump libconnect for local-DNS self-forward + warning fixes Picks up signadot/libconnect@5dc7dd1: Linux self-forwarding-loop guard (drop own bind address from captured upstreams; capture() refuses a leftover sentinel resolv.conf) and the macOS resolver-file warning gated on change. Addresses the two bugs from the #342 live review. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 9da1fae..c923a22 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7 + github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index b76a4b8..97ff750 100644 --- a/go.sum +++ b/go.sum @@ -466,6 +466,8 @@ github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712 h1:hSeEu/QUB github.com/signadot/libconnect v0.1.1-0.20260710163957-b168fa5c0712/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7 h1:J6o0E0hE5PPERlo5lZGkexNfsDNGec39RoXbf6iZ18c= github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852 h1:FY6xQbxIEGyFOdpkPoAU0uxa+BB/XNadxFMTNgJbzQQ= +github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852/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= From 99f6e9d5ed91520fbb7cf498c69dca66f6b2ed67 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 09:04:48 +0200 Subject: [PATCH 20/25] local: show distinct host count alongside name count in DNS status `local status` reported only the resolver's record count (147), which includes synthesized short forms and reads as a discrepancy against the 49 rows `local hosts` lists. Add a host_count field to LocalDNSStatus (populated from libconnect's new Status.HostCount) and render "N names (M hosts) resolvable via local DNS", also exposing hostCount in the JSON/details view. Bumps libconnect to 5a7b7ce (adds Status.HostCount) and regenerates common.pb.go (protoc-gen-go v1.26.0, matching the repo). Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 + internal/command/local/printers.go | 12 +- internal/locald/api/common.pb.go | 182 +++++++++++---------- internal/locald/api/common.proto | 3 + internal/locald/rootmanager/root_server.go | 1 + 6 files changed, 111 insertions(+), 91 deletions(-) diff --git a/go.mod b/go.mod index c923a22..07f5a3c 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852 + github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index 97ff750..f7fb1c9 100644 --- a/go.sum +++ b/go.sum @@ -468,6 +468,8 @@ github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7 h1:J6o0E0hE5 github.com/signadot/libconnect v0.1.1-0.20260710172305-c142183b9cb7/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852 h1:FY6xQbxIEGyFOdpkPoAU0uxa+BB/XNadxFMTNgJbzQQ= github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0 h1:0vEOMWhLu+TI0p1iFdyMxpkHwJVt+nswGqTXlXStmpU= +github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0/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= diff --git a/internal/command/local/printers.go b/internal/command/local/printers.go index 04e907f..9866b35 100644 --- a/internal/command/local/printers.go +++ b/internal/command/local/printers.go @@ -243,6 +243,7 @@ func getRawLocalDNS(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationC 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"` @@ -256,6 +257,7 @@ func getRawLocalDNS(cfg *config.LocalStatus, ciConfig *config.ConnectInvocationC result = &PrintableLocalDNS{ Healthy: true, RecordCount: ldns.RecordCount, + HostCount: ldns.HostCount, BindAddr: ldns.BindAddr, Warning: ldns.Warning, } @@ -566,11 +568,11 @@ func (p *statusPrinter) printLocalDNSStatus() { } if ldns.Health.Healthy { // RecordCount is the total resolvable DNS names, which includes the - // synthesized short forms (e.g. ., ..svc) — so it is a - // multiple of the host count shown by `local hosts`. Say "names", not - // "hosts", to avoid a confusing mismatch between the two commands. - p.printLine(p.out, 1, fmt.Sprintf("%d names resolvable via local DNS (%s)", - ldns.RecordCount, ldns.BindAddr), "*") + // synthesized short forms (e.g. ., ..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`. + p.printLine(p.out, 1, fmt.Sprintf("%d names (%d hosts) resolvable via local DNS (%s)", + ldns.RecordCount, ldns.HostCount, ldns.BindAddr), "*") } else { p.printLine(p.out, 1, fmt.Sprintf("local DNS resolver not healthy (%q)", ldns.Health.LastErrorReason), "*") diff --git a/internal/locald/api/common.pb.go b/internal/locald/api/common.pb.go index 28d52f6..5615f1a 100644 --- a/internal/locald/api/common.pb.go +++ b/internal/locald/api/common.pb.go @@ -244,6 +244,9 @@ type LocalDNSStatus struct { LastRefresh *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=last_refresh,json=lastRefresh,proto3" json:"last_refresh,omitempty"` ResolvConfManaged bool `protobuf:"varint,8,opt,name=resolv_conf_managed,json=resolvConfManaged,proto3" json:"resolv_conf_managed,omitempty"` Warning string `protobuf:"bytes,9,opt,name=warning,proto3" json:"warning,omitempty"` + // host_count is the number of distinct cluster hosts (FQDNs), i.e. + // record_count excluding synthesized short forms. Matches `local hosts`. + HostCount uint32 `protobuf:"varint,10,opt,name=host_count,json=hostCount,proto3" json:"host_count,omitempty"` } func (x *LocalDNSStatus) Reset() { @@ -341,6 +344,13 @@ func (x *LocalDNSStatus) GetWarning() string { return "" } +func (x *LocalDNSStatus) GetHostCount() uint32 { + if x != nil { + return x.HostCount + } + return 0 +} + // HostEntry is a single resolvable cluster name and the synthetic address(es) // assigned to it. It is the unit returned by the GetHosts RPCs and is // independent of the name-resolution mechanism (/etc/hosts or local DNS): both @@ -1017,7 +1027,7 @@ var file_internal_locald_api_common_proto_rawDesc = []byte{ 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, - 0x6d, 0x65, 0x22, 0xd9, 0x02, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, + 0x6d, 0x65, 0x22, 0xf8, 0x02, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x4e, 0x53, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, @@ -1038,91 +1048,93 @@ var file_internal_locald_api_common_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x66, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x43, 0x6f, 0x6e, 0x66, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x2f, - 0x0a, 0x09, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, - 0x6a, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, - 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, - 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, - 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x41, 0x0a, - 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, - 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x1d, + 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x2f, 0x0a, + 0x09, 0x48, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x6a, + 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, + 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x70, 0x0a, 0x17, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x41, 0x0a, 0x0d, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x30, 0x0a, + 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, + 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, + 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, + 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, + 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x57, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x57, 0x6f, + 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, 0x73, 0x65, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, 0x42, 0x61, 0x73, + 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x23, 0x0a, 0x0d, + 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x6f, 0x72, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, 0x6c, + 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x08, + 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, + 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, + 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, + 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, 0x0a, 0x13, 0x77, + 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, + 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, + 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, + 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x22, 0xe4, 0x04, 0x0a, 0x0d, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, - 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x75, - 0x74, 0x69, 0x6e, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x4f, 0x0a, 0x0f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x26, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, - 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, - 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x57, - 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x1a, 0x70, 0x0a, 0x08, 0x42, 0x61, 0x73, 0x65, - 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x1a, 0x5b, 0x0a, 0x0f, 0x42, 0x61, - 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x12, 0x23, 0x0a, - 0x0d, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x50, 0x6f, - 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x6f, 0x63, 0x61, 0x6c, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xfd, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x63, 0x61, - 0x6c, 0x57, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, - 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x21, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, - 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, - 0x6e, 0x65, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x5a, 0x0a, 0x13, - 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x4d, 0x61, 0x70, 0x70, - 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x61, 0x6e, 0x64, 0x62, 0x6f, 0x78, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x2e, 0x42, 0x61, 0x73, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x54, 0x6f, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x6f, 0x72, - 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3d, 0x0a, 0x0d, 0x74, 0x75, 0x6e, 0x6e, - 0x65, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x61, 0x70, 0x69, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, - 0x6c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, - 0xdb, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, - 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, - 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x2d, 0x5a, - 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x65, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x0c, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x22, 0x66, 0x0a, 0x0c, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0xdb, + 0x01, 0x0a, 0x13, 0x44, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, + 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x62, 0x6f, 0x78, 0x49, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x61, 0x73, 0x74, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, + 0x61, 0x73, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x2d, 0x5a, 0x2b, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x64, 0x6f, 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/internal/locald/api/common.proto b/internal/locald/api/common.proto index 891b01a..d242d3b 100644 --- a/internal/locald/api/common.proto +++ b/internal/locald/api/common.proto @@ -41,6 +41,9 @@ message LocalDNSStatus { google.protobuf.Timestamp last_refresh = 7; bool resolv_conf_managed = 8; string warning = 9; + // host_count is the number of distinct cluster hosts (FQDNs), i.e. + // record_count excluding synthesized short forms. Matches `local hosts`. + uint32 host_count = 10; } // HostEntry is a single resolvable cluster name and the synthetic address(es) diff --git a/internal/locald/rootmanager/root_server.go b/internal/locald/rootmanager/root_server.go index bf117e9..731a861 100644 --- a/internal/locald/rootmanager/root_server.go +++ b/internal/locald/rootmanager/root_server.go @@ -115,6 +115,7 @@ func (s *rootServer) Status(ctx context.Context, req *rootapi.StatusRequest) (*r Suffixes: status.Suffixes, Upstreams: status.Upstreams, RecordCount: uint32(status.RecordCount), + HostCount: uint32(status.HostCount), LastRefresh: lastRefresh, ResolvConfManaged: status.ResolvConfManaged, Warning: status.Warning, From 367783408b456056d28de3188f2fb3d915a2cd04 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 09:55:44 +0200 Subject: [PATCH 21/25] deps: bump libconnect (drop obsolete macOS >10 resolver-files warning) Picks up signadot/libconnect@0788891, which removes the ">10 resolver files" warning that fired on the normal case (one resolver file per namespace). Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 07f5a3c..4b40b47 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0 + github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index f7fb1c9..599d347 100644 --- a/go.sum +++ b/go.sum @@ -470,6 +470,8 @@ github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852 h1:FY6xQbxIE github.com/signadot/libconnect v0.1.1-0.20260721063356-5dc7dd1c8852/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0 h1:0vEOMWhLu+TI0p1iFdyMxpkHwJVt+nswGqTXlXStmpU= github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672 h1:iFqAs7tkBgREi3OgqzS0OniMl3lOiwumZSIXoV3ZR1M= +github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672/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= From ac86f052427ac882aa7f105aa58dd30721529788 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 10:46:20 +0200 Subject: [PATCH 22/25] deps: bump libconnect (probe skips resolv.conf addresses) Picks up signadot/libconnect@45a1c0f: the managed bind probe now skips loopback addresses already referenced in /etc/resolv.conf, so localdns can never bind an address the system forwards to (structural self-forward-loop prevention, including the crashed-session leftover case). Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4b40b47..e117af0 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672 + github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index 599d347..4f66f81 100644 --- a/go.sum +++ b/go.sum @@ -472,6 +472,8 @@ github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0 h1:0vEOMWhLu github.com/signadot/libconnect v0.1.1-0.20260721064448-5a7b7ce98ab0/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672 h1:iFqAs7tkBgREi3OgqzS0OniMl3lOiwumZSIXoV3ZR1M= github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0 h1:jK7SLTOFQPxd317WI/xQv1txRDUwepelgTpUTQmeHkk= +github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0/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= From c3a8932c8dfc78d60d8c5014bbbfd4918961b657 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 11:32:48 +0200 Subject: [PATCH 23/25] deps: bump libconnect (resolv.conf recovery hardening + server de-race) Picks up signadot/libconnect@008c84a: - robust identification/cleanup of a crashed session's leftover /etc/resolv.conf (in-file @@@SIGNADOT-BIND-@@@ marker + uncontested bind record, surgical strip that preserves a competing DNS manager's entries, loud logging); - de-race of server startup vs shutdown that flaked the lifecycle tests on Linux. Co-Authored-By: Claude Opus 4.8 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e117af0..0887d39 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0 + github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index 4f66f81..f1d90ee 100644 --- a/go.sum +++ b/go.sum @@ -474,6 +474,8 @@ github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672 h1:iFqAs7tkB github.com/signadot/libconnect v0.1.1-0.20260721075401-0788891ef672/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0 h1:jK7SLTOFQPxd317WI/xQv1txRDUwepelgTpUTQmeHkk= github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c h1:5L7oL42b6JrCrvLWPbcC9R6OSNWpGPhPdbOx8UVeq68= +github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c/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= From 5a5e17c9e2e0b510249672078f3754734c950c24 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 21 Jul 2026 12:08:41 +0200 Subject: [PATCH 24/25] local: handle new-CLI/old-daemon skew in local-DNS status line A new CLI can query an older local daemon (the user upgraded the CLI while connected). Such a daemon predates the host_count field, which protobuf decodes as 0. Omit the "(N hosts)" suffix when the count is 0 so the line reads "147 names resolvable via local DNS" instead of a confusing "(0 hosts)". Adds cross-version rendering tests (new daemon reports the host count, old daemon omits it, resolver-not-running). Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/command/local/printers.go | 12 +++++-- internal/command/local/printers_test.go | 43 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/internal/command/local/printers.go b/internal/command/local/printers.go index 9866b35..e3127f7 100644 --- a/internal/command/local/printers.go +++ b/internal/command/local/printers.go @@ -570,9 +570,15 @@ func (p *statusPrinter) printLocalDNSStatus() { // RecordCount is the total resolvable DNS names, which includes the // synthesized short forms (e.g. ., ..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`. - p.printLine(p.out, 1, fmt.Sprintf("%d names (%d hosts) resolvable via local DNS (%s)", - ldns.RecordCount, ldns.HostCount, ldns.BindAddr), "*") + // 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), "*") diff --git a/internal/command/local/printers_test.go b/internal/command/local/printers_test.go index cd8d866..290e6b9 100644 --- a/internal/command/local/printers_test.go +++ b/internal/command/local/printers_test.go @@ -1,14 +1,57 @@ package local import ( + "bytes" "encoding/json" "strings" "testing" "github.com/signadot/cli/internal/config" commonapi "github.com/signadot/cli/internal/locald/api" + sbmapi "github.com/signadot/cli/internal/locald/api/sandboxmanager" ) +// TestLocalDNSStatusLineCrossVersion covers the CLI-vs-daemon version skew for +// the local-DNS status line: a new CLI rendering the status of a daemon that +// does (new) or does not (old, pre-host_count) report the host count. protobuf +// makes host_count decode as 0 against an older daemon, so the line must omit +// "(0 hosts)" rather than read as a discrepancy against `local hosts`. +func TestLocalDNSStatusLineCrossVersion(t *testing.T) { + line := func(ldns *commonapi.LocalDNSStatus) string { + var buf bytes.Buffer + p := &statusPrinter{cfg: &config.LocalStatus{}, status: &sbmapi.StatusResponse{LocalDns: ldns}, out: &buf} + p.printLocalDNSStatus() + return buf.String() + } + + t.Run("new daemon reports host count", func(t *testing.T) { + got := line(&commonapi.LocalDNSStatus{ + Health: &commonapi.ServiceHealth{Healthy: true}, RecordCount: 147, HostCount: 49, BindAddr: "127.0.0.54:53", + }) + if !strings.Contains(got, "147 names (49 hosts) resolvable via local DNS") { + t.Errorf("got %q, want name and host counts", got) + } + }) + + t.Run("old daemon (host_count absent -> 0) omits the host count", func(t *testing.T) { + got := line(&commonapi.LocalDNSStatus{ + Health: &commonapi.ServiceHealth{Healthy: true}, RecordCount: 147, HostCount: 0, BindAddr: "127.0.0.54:53", + }) + if !strings.Contains(got, "147 names resolvable via local DNS") { + t.Errorf("got %q, want the plain name count", got) + } + if strings.Contains(got, "hosts)") { + t.Errorf("got %q, want no host count for a pre-host_count daemon", got) + } + }) + + t.Run("not running", func(t *testing.T) { + if got := line(nil); !strings.Contains(got, "not running") { + t.Errorf("got %q", got) + } + }) +} + // TestGetRawHostsGating covers the section gating for the JSON/YAML status // output: /etc/hosts status is omitted under --local-dns (where the root // manager reports no hosts status) rather than emitted as a bogus failure. From df4cc3daa5dff7179748cfff46deeccfae0d4801 Mon Sep 17 00:00:00 2001 From: Scott Cotton Date: Tue, 28 Jul 2026 13:55:19 +0200 Subject: [PATCH 25/25] deps: bump libconnect (PR #163 review fixes) Picks up the fixes for foxish's review of signadot/libconnect#163: - ipmap no longer hands a dropped host's address to a new host in the same Set, which was misrouting the tunnel's ORIG_DEST reverse lookup to whichever service inherited the address. This one also affects the default /etc/hosts resolver, not just --local-dns. - Cluster domain and namespace names from the tunnel API are validated as DNS names before they become /etc/resolver file names and /etc/resolv.conf directives. - Linux crash recovery keeps the resolv.conf backup when the file is missing, instead of deleting the only copy that could restore it. - The macOS PATH prefix for scutil/dscacheutil/killall is now actually applied, flush failures are logged, and install reconciles against what is on disk rather than its own bookkeeping. - Borrowed short-name zones are only claimed when off-zone misses can be forwarded upstream, and reserved single-label namespaces are skipped. Verified end to end on macOS against signadot-staging with this pin: 225 hosts / 675 names, 30 resolver files registered in scutil, short and fully-qualified names resolving to the same address, HTTP 200 through the tunnel, no resolver-file churn across refresh ticks, clean restore on disconnect, and zero WARN/ERROR for the session. Co-Authored-By: Claude Opus 5 (1M context) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 0887d39..89152d9 100644 --- a/go.mod +++ b/go.mod @@ -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.20260616155342-631831380993 - github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c + github.com/signadot/libconnect v0.1.1-0.20260728110714-4f2cc6a3a14d github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.11.0 github.com/theckman/yacspin v0.13.12 diff --git a/go.sum b/go.sum index f1d90ee..47f3152 100644 --- a/go.sum +++ b/go.sum @@ -476,6 +476,8 @@ github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0 h1:jK7SLTOFQ github.com/signadot/libconnect v0.1.1-0.20260721084548-45a1c0ff0fd0/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c h1:5L7oL42b6JrCrvLWPbcC9R6OSNWpGPhPdbOx8UVeq68= github.com/signadot/libconnect v0.1.1-0.20260721093213-008c84a5471c/go.mod h1:awUWZx6SnE/vqfB1Yh+n6YVUkgc0bkILEx+L55sWze0= +github.com/signadot/libconnect v0.1.1-0.20260728110714-4f2cc6a3a14d h1:zwp9O0JzCeD5l8npdMylX+h+IFjK5qJ9nP1/STttrt0= +github.com/signadot/libconnect v0.1.1-0.20260728110714-4f2cc6a3a14d/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=