ipn,cmd/tailscale/cli: support hierarchical MaskedPrefs (#10507)

Some fields if `ipn.Prefs` are structs. `ipn.MaskedPrefs` has a single
level of boolean `*Set` flags, which doesn't map well to nested structs
within `ipn.Prefs`.

Change `MaskedPrefs` and `ApplyEdits` to support `FooSet` struct fields
that map to a nested struct of `ipn.Prefs` like `AutoUpdates`. Each
struct field in `MaskedPrefs` is just a bundle of more `Set` bool fields
or other structs. This allows you to have a `Set` flag for any
arbitrarily-nested field of `ipn.Prefs`.

Also, make `ApplyEdits` match fields between `Prefs` and `MaskedPrefs`
by name instead of order, to make it a bit less finicky. It's probably
slower but `ipn.ApplyEdits` should not be in any hot path.

As a result, `AutoUpdate.Check` and `AutoUpdate.Apply` fields don't
clobber each other when set individually.

Updates #16247

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov
2023-12-08 12:19:25 -06:00
committed by GitHub
parent 2f01d5e3da
commit e25f114916
6 changed files with 150 additions and 52 deletions

View File

@@ -786,7 +786,7 @@ func TestPrefFlagMapping(t *testing.T) {
prefHasFlag := map[string]bool{}
for _, pv := range prefsOfFlag {
for _, pref := range pv {
prefHasFlag[pref] = true
prefHasFlag[strings.Split(pref, ".")[0]] = true
}
}

View File

@@ -167,7 +167,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
return err
}
}
if maskedPrefs.AutoUpdateSet {
if maskedPrefs.AutoUpdateSet.ApplySet {
// On macsys, tailscaled will set the Sparkle auto-update setting. It
// does not use clientupdate.
if version.IsMacSysExt() {

View File

@@ -718,8 +718,8 @@ func init() {
addPrefFlagMapping("ssh", "RunSSH")
addPrefFlagMapping("webclient", "RunWebClient")
addPrefFlagMapping("nickname", "ProfileName")
addPrefFlagMapping("update-check", "AutoUpdate")
addPrefFlagMapping("auto-update", "AutoUpdate")
addPrefFlagMapping("update-check", "AutoUpdate.Check")
addPrefFlagMapping("auto-update", "AutoUpdate.Apply")
addPrefFlagMapping("advertise-connector", "AppConnector")
addPrefFlagMapping("posture-checking", "PostureChecking")
}
@@ -728,9 +728,14 @@ func addPrefFlagMapping(flagName string, prefNames ...string) {
prefsOfFlag[flagName] = prefNames
prefType := reflect.TypeOf(ipn.Prefs{})
for _, pref := range prefNames {
// Crash at runtime if there's a typo in the prefName.
if _, ok := prefType.FieldByName(pref); !ok {
panic(fmt.Sprintf("invalid ipn.Prefs field %q", pref))
t := prefType
for _, name := range strings.Split(pref, ".") {
// Crash at runtime if there's a typo in the prefName.
f, ok := t.FieldByName(name)
if !ok {
panic(fmt.Sprintf("invalid ipn.Prefs field %q", pref))
}
t = f.Type
}
}
}
@@ -751,7 +756,11 @@ func updateMaskedPrefsFromUpOrSetFlag(mp *ipn.MaskedPrefs, flagName string) {
}
if prefs, ok := prefsOfFlag[flagName]; ok {
for _, pref := range prefs {
reflect.ValueOf(mp).Elem().FieldByName(pref + "Set").SetBool(true)
f := reflect.ValueOf(mp).Elem()
for _, name := range strings.Split(pref, ".") {
f = f.FieldByName(name + "Set")
}
f.SetBool(true)
}
return
}