ipn: move Options.Notify to its own method

We already had SetNotifyCallback elsewhere on controlclient, so use
that name.

Baby steps towards some CLI refactor work.

Updates tailscale/tailscale#1436
pull/1613/head
Brad Fitzpatrick 3 years ago
parent d488678fdc
commit 799973a68d

@ -238,44 +238,44 @@ func runUp(ctx context.Context, args []string) error {
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) } startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
bc.SetPrefs(prefs) bc.SetPrefs(prefs)
bc.SetNotifyCallback(func(n ipn.Notify) {
opts := ipn.Options{ if n.ErrMessage != nil {
StateKey: ipn.GlobalDaemonStateKey, msg := *n.ErrMessage
AuthKey: upArgs.authKey, if msg == ipn.ErrMsgPermissionDenied {
Notify: func(n ipn.Notify) { switch runtime.GOOS {
if n.ErrMessage != nil { case "windows":
msg := *n.ErrMessage msg += " (Tailscale service in use by other user?)"
if msg == ipn.ErrMsgPermissionDenied { default:
switch runtime.GOOS { msg += " (try 'sudo tailscale up [...]')"
case "windows":
msg += " (Tailscale service in use by other user?)"
default:
msg += " (try 'sudo tailscale up [...]')"
}
} }
fatalf("backend error: %v\n", msg)
} }
if s := n.State; s != nil { fatalf("backend error: %v\n", msg)
switch *s { }
case ipn.NeedsLogin: if s := n.State; s != nil {
printed = true switch *s {
startLoginInteractive() case ipn.NeedsLogin:
case ipn.NeedsMachineAuth: printed = true
printed = true startLoginInteractive()
fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", upArgs.server) case ipn.NeedsMachineAuth:
case ipn.Starting, ipn.Running: printed = true
// Done full authentication process fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s/admin/machines\n\n", upArgs.server)
if printed { case ipn.Starting, ipn.Running:
// Only need to print an update if we printed the "please click" message earlier. // Done full authentication process
fmt.Fprintf(os.Stderr, "Success.\n") if printed {
} // Only need to print an update if we printed the "please click" message earlier.
cancel() fmt.Fprintf(os.Stderr, "Success.\n")
} }
cancel()
} }
if url := n.BrowseToURL; url != nil { }
fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url) if url := n.BrowseToURL; url != nil {
} fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
}, }
})
opts := ipn.Options{
StateKey: ipn.GlobalDaemonStateKey,
AuthKey: upArgs.authKey,
} }
// On Windows, we still run in mostly the "legacy" way that // On Windows, we still run in mostly the "legacy" way that
@ -293,8 +293,7 @@ func runUp(ctx context.Context, args []string) error {
opts.Prefs = prefs opts.Prefs = prefs
} }
// We still have to Start right now because it's the only way to // We still have to Start for now. This causes a bunch of churn
// set up notifications and whatnot. This causes a bunch of churn
// every time the CLI touches anything. // every time the CLI touches anything.
// //
// TODO(danderson): redo the frontend/backend API to assume // TODO(danderson): redo the frontend/backend API to assume

@ -259,30 +259,30 @@ func tailscaleUp(ctx context.Context) (authURL string, retErr error) {
c, bc, ctx, cancel := connect(ctx) c, bc, ctx, cancel := connect(ctx)
defer cancel() defer cancel()
bc.SetNotifyCallback(func(n ipn.Notify) {
if n.ErrMessage != nil {
msg := *n.ErrMessage
if msg == ipn.ErrMsgPermissionDenied {
switch runtime.GOOS {
case "windows":
msg += " (Tailscale service in use by other user?)"
default:
msg += " (try 'sudo tailscale up [...]')"
}
}
retErr = fmt.Errorf("backend error: %v", msg)
cancel()
} else if url := n.BrowseToURL; url != nil {
authURL = *url
cancel()
}
})
bc.SetPrefs(prefs) bc.SetPrefs(prefs)
opts := ipn.Options{ bc.Start(ipn.Options{
StateKey: ipn.GlobalDaemonStateKey, StateKey: ipn.GlobalDaemonStateKey,
Notify: func(n ipn.Notify) { })
if n.ErrMessage != nil {
msg := *n.ErrMessage
if msg == ipn.ErrMsgPermissionDenied {
switch runtime.GOOS {
case "windows":
msg += " (Tailscale service in use by other user?)"
default:
msg += " (try 'sudo tailscale up [...]')"
}
}
retErr = fmt.Errorf("backend error: %v", msg)
cancel()
} else if url := n.BrowseToURL; url != nil {
authURL = *url
cancel()
}
},
}
bc.Start(opts)
bc.StartLoginInteractive() bc.StartLoginInteractive()
pump(ctx, bc, c) pump(ctx, bc, c)

@ -122,8 +122,6 @@ type Options struct {
// TODO(danderson): remove some time after the transition to // TODO(danderson): remove some time after the transition to
// tailscaled is done. // tailscaled is done.
LegacyConfigPath string LegacyConfigPath string
// Notify is called when backend events happen.
Notify func(Notify) `json:"-"`
// HTTPTestClient is an optional HTTP client to pass to controlclient // HTTPTestClient is an optional HTTP client to pass to controlclient
// (for tests only). // (for tests only).
HTTPTestClient *http.Client HTTPTestClient *http.Client
@ -135,6 +133,9 @@ type Options struct {
// (It has nothing to do with the interface between the backends // (It has nothing to do with the interface between the backends
// and the cloud control plane.) // and the cloud control plane.)
type Backend interface { type Backend interface {
// SetNotifyCallback sets the callback to be called on updates
// from the backend to the client.
SetNotifyCallback(func(Notify))
// Start starts or restarts the backend, typically when a // Start starts or restarts the backend, typically when a
// frontend client connects. // frontend client connects.
Start(Options) error Start(Options) error

@ -5,7 +5,6 @@
package ipn package ipn
import ( import (
"log"
"time" "time"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
@ -21,18 +20,28 @@ type FakeBackend struct {
func (b *FakeBackend) Start(opts Options) error { func (b *FakeBackend) Start(opts Options) error {
b.serverURL = opts.Prefs.ControlURL b.serverURL = opts.Prefs.ControlURL
if opts.Notify == nil { if b.notify == nil {
log.Fatalf("FakeBackend.Start: opts.Notify is nil\n") panic("FakeBackend.Start: SetNotifyCallback not called")
} }
b.notify = opts.Notify
b.notify(Notify{Prefs: opts.Prefs})
nl := NeedsLogin nl := NeedsLogin
b.notify(Notify{State: &nl}) if b.notify != nil {
b.notify(Notify{Prefs: opts.Prefs})
b.notify(Notify{State: &nl})
}
return nil return nil
} }
func (b *FakeBackend) SetNotifyCallback(notify func(Notify)) {
if notify == nil {
panic("FakeBackend.SetNotifyCallback: notify is nil")
}
b.notify = notify
}
func (b *FakeBackend) newState(s State) { func (b *FakeBackend) newState(s State) {
b.notify(Notify{State: &s}) if b.notify != nil {
b.notify(Notify{State: &s})
}
if s == Running { if s == Running {
b.live = true b.live = true
} else { } else {
@ -42,7 +51,9 @@ func (b *FakeBackend) newState(s State) {
func (b *FakeBackend) StartLoginInteractive() { func (b *FakeBackend) StartLoginInteractive() {
u := b.serverURL + "/this/is/fake" u := b.serverURL + "/this/is/fake"
b.notify(Notify{BrowseToURL: &u}) if b.notify != nil {
b.notify(Notify{BrowseToURL: &u})
}
b.login() b.login()
} }
@ -54,10 +65,14 @@ func (b *FakeBackend) login() {
b.newState(NeedsMachineAuth) b.newState(NeedsMachineAuth)
b.newState(Stopped) b.newState(Stopped)
// TODO(apenwarr): Fill in a more interesting netmap here. // TODO(apenwarr): Fill in a more interesting netmap here.
b.notify(Notify{NetMap: &netmap.NetworkMap{}}) if b.notify != nil {
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
}
b.newState(Starting) b.newState(Starting)
// TODO(apenwarr): Fill in a more interesting status. // TODO(apenwarr): Fill in a more interesting status.
b.notify(Notify{Engine: &EngineStatus{}}) if b.notify != nil {
b.notify(Notify{Engine: &EngineStatus{}})
}
b.newState(Running) b.newState(Running)
} }
@ -70,7 +85,9 @@ func (b *FakeBackend) SetPrefs(new *Prefs) {
panic("FakeBackend.SetPrefs got nil prefs") panic("FakeBackend.SetPrefs got nil prefs")
} }
b.notify(Notify{Prefs: new.Clone()}) if b.notify != nil {
b.notify(Notify{Prefs: new.Clone()})
}
if new.WantRunning && !b.live { if new.WantRunning && !b.live {
b.newState(Starting) b.newState(Starting)
b.newState(Running) b.newState(Running)
@ -87,13 +104,19 @@ func (b *FakeBackend) EditPrefs(mp *MaskedPrefs) {
} }
func (b *FakeBackend) RequestEngineStatus() { func (b *FakeBackend) RequestEngineStatus() {
b.notify(Notify{Engine: &EngineStatus{}}) if b.notify != nil {
b.notify(Notify{Engine: &EngineStatus{}})
}
} }
func (b *FakeBackend) FakeExpireAfter(x time.Duration) { func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
b.notify(Notify{NetMap: &netmap.NetworkMap{}}) if b.notify != nil {
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
}
} }
func (b *FakeBackend) Ping(ip string, useTSMP bool) { func (b *FakeBackend) Ping(ip string, useTSMP bool) {
b.notify(Notify{PingResult: &ipnstate.PingResult{}}) if b.notify != nil {
b.notify(Notify{PingResult: &ipnstate.PingResult{}})
}
} }

@ -15,25 +15,26 @@ import (
) )
type Handle struct { type Handle struct {
frontendLogID string b Backend
b Backend logf logger.Logf
xnotify func(Notify)
logf logger.Logf
// Mutex protects everything below // Mutex protects everything below
mu sync.Mutex mu sync.Mutex
xnotify func(Notify)
frontendLogID string
netmapCache *netmap.NetworkMap netmapCache *netmap.NetworkMap
engineStatusCache EngineStatus engineStatusCache EngineStatus
stateCache State stateCache State
prefsCache *Prefs prefsCache *Prefs
} }
func NewHandle(b Backend, logf logger.Logf, opts Options) (*Handle, error) { func NewHandle(b Backend, logf logger.Logf, notify func(Notify), opts Options) (*Handle, error) {
h := &Handle{ h := &Handle{
b: b, b: b,
logf: logf, logf: logf,
} }
h.SetNotifyCallback(notify)
err := h.Start(opts) err := h.Start(opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -42,18 +43,25 @@ func NewHandle(b Backend, logf logger.Logf, opts Options) (*Handle, error) {
return h, nil return h, nil
} }
func (h *Handle) SetNotifyCallback(notify func(Notify)) {
h.mu.Lock()
h.xnotify = notify
h.mu.Unlock()
h.b.SetNotifyCallback(h.notify)
}
func (h *Handle) Start(opts Options) error { func (h *Handle) Start(opts Options) error {
h.mu.Lock()
h.frontendLogID = opts.FrontendLogID h.frontendLogID = opts.FrontendLogID
h.xnotify = opts.Notify
h.netmapCache = nil h.netmapCache = nil
h.engineStatusCache = EngineStatus{} h.engineStatusCache = EngineStatus{}
h.stateCache = NoState h.stateCache = NoState
if opts.Prefs != nil { if opts.Prefs != nil {
h.prefsCache = opts.Prefs.Clone() h.prefsCache = opts.Prefs.Clone()
} }
xopts := opts h.mu.Unlock()
xopts.Notify = h.notify return h.b.Start(opts)
return h.b.Start(xopts)
} }
func (h *Handle) Reset() { func (h *Handle) Reset() {

@ -519,6 +519,12 @@ func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) {
b.send(ipn.Notify{Engine: &es}) b.send(ipn.Notify{Engine: &es})
} }
func (b *LocalBackend) SetNotifyCallback(notify func(ipn.Notify)) {
b.mu.Lock()
defer b.mu.Unlock()
b.notify = notify
}
// Start applies the configuration specified in opts, and starts the // Start applies the configuration specified in opts, and starts the
// state machine. // state machine.
// //
@ -585,7 +591,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
} }
applyPrefsToHostinfo(hostinfo, b.prefs) applyPrefsToHostinfo(hostinfo, b.prefs)
b.notify = opts.Notify
b.setNetMapLocked(nil) b.setNetMapLocked(nil)
persistv := b.prefs.Persist persistv := b.prefs.Persist
b.mu.Unlock() b.mu.Unlock()

@ -189,8 +189,8 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
bs.GotQuit = true bs.GotQuit = true
return errors.New("Quit command received") return errors.New("Quit command received")
} else if c := cmd.Start; c != nil { } else if c := cmd.Start; c != nil {
bs.b.SetNotifyCallback(bs.send)
opts := c.Opts opts := c.Opts
opts.Notify = bs.send
return bs.b.Start(opts) return bs.b.Start(opts)
} else if c := cmd.StartLoginInteractive; c != nil { } else if c := cmd.StartLoginInteractive; c != nil {
bs.b.StartLoginInteractive() bs.b.StartLoginInteractive()
@ -287,8 +287,6 @@ func (bc *BackendClient) Quit() error {
} }
func (bc *BackendClient) Start(opts Options) error { func (bc *BackendClient) Start(opts Options) error {
bc.notify = opts.Notify
opts.Notify = nil // server can't call our function pointer
bc.send(Command{Start: &StartArgs{Opts: opts}}) bc.send(Command{Start: &StartArgs{Opts: opts}})
return nil // remote Start() errors must be handled remotely return nil // remote Start() errors must be handled remotely
} }

@ -90,13 +90,11 @@ func TestClientServer(t *testing.T) {
bc = NewBackendClient(clogf, clientToServer) bc = NewBackendClient(clogf, clientToServer)
ch := make(chan Notify, 256) ch := make(chan Notify, 256)
h, err := NewHandle(bc, clogf, Options{ notify := func(n Notify) { ch <- n }
h, err := NewHandle(bc, clogf, notify, Options{
Prefs: &Prefs{ Prefs: &Prefs{
ControlURL: "http://example.com/fake", ControlURL: "http://example.com/fake",
}, },
Notify: func(n Notify) {
ch <- n
},
}) })
if err != nil { if err != nil {
t.Fatalf("NewHandle error: %v\n", err) t.Fatalf("NewHandle error: %v\n", err)

Loading…
Cancel
Save