feature,ipn/ipnlocal: add profileManager.StateChangeHook

We update profileManager to allow registering a single state (profile+prefs) change hook.
This is to invert the dependency between the profileManager and the LocalBackend, so that
instead of LocalBackend asking profileManager for the state, we can have profileManager
call LocalBackend when the state changes.

We also update feature.Hook with a new (*feature.Hook).GetOk method to avoid calling both
IsSet and Get.

Updates tailscale/corp#28014
Updates #12614

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2025-04-25 14:53:55 -05:00
committed by Nick Khyl
parent 0cfd643d95
commit 66371f392a
4 changed files with 589 additions and 19 deletions
-15
View File
@@ -1675,7 +1675,6 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
// Perform all mutations of prefs based on the netmap here.
if prefsChanged {
profile := b.pm.CurrentProfile()
// Prefs will be written out if stale; this is not safe unless locked or cloned.
if err := b.pm.SetPrefs(prefs.View(), ipn.NetworkProfile{
MagicDNSName: curNetMap.MagicDNSSuffix(),
@@ -1683,20 +1682,6 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control
}); err != nil {
b.logf("Failed to save new controlclient state: %v", err)
}
// Updating profile prefs may have resulted in a change to the current [ipn.LoginProfile],
// either because the user completed a login, which populated and persisted their profile
// for the first time, or because of an [ipn.NetworkProfile] or [tailcfg.UserProfile] change.
// Theoretically, a completed login could also result in a switch to a different existing
// profile representing a different node (see tailscale/tailscale#8816).
//
// Let's check if the current profile has changed, and invoke all registered
// [ipnext.ProfileStateChangeCallback] if necessary.
if cp := b.pm.CurrentProfile(); *cp.AsStruct() != *profile.AsStruct() {
// If the profile ID was empty before SetPrefs, it's a new profile
// and the user has just completed a login for the first time.
sameNode := profile.ID() == "" || profile.ID() == cp.ID()
b.extHost.NotifyProfileChange(profile, prefs.View(), sameNode)
}
}
// initTKALocked is dependent on CurrentProfile.ID, which is initialized