forked from tailscale/tailscale
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
util/syspolicy: add rsop package that provides access to the resultan…
…t policy In this PR we add syspolicy/rsop package that facilitates policy source registration and provides access to the resultant policy merged from all registered sources for a given scope. Updates tailscale#12687 Signed-off-by: Nick Khyl <[email protected]>
- Loading branch information
Showing
9 changed files
with
1,834 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright (c) Tailscale Inc & AUTHORS | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
package rsop | ||
|
||
import ( | ||
"reflect" | ||
"slices" | ||
"sync" | ||
"time" | ||
|
||
"tailscale.com/util/set" | ||
"tailscale.com/util/syspolicy/internal/loggerx" | ||
"tailscale.com/util/syspolicy/setting" | ||
) | ||
|
||
// Change represents a change from the Old to the New value of type T. | ||
type Change[T any] struct { | ||
New, Old T | ||
} | ||
|
||
// PolicyChangeCallback is a function called whenever a policy changes. | ||
type PolicyChangeCallback func(*PolicyChange) | ||
|
||
// PolicyChange describes a policy change. | ||
type PolicyChange struct { | ||
snapshots Change[*setting.Snapshot] | ||
} | ||
|
||
// New returns the [setting.Snapshot] after the change. | ||
func (c PolicyChange) New() *setting.Snapshot { | ||
return c.snapshots.New | ||
} | ||
|
||
// Old returns the [setting.Snapshot] before the change. | ||
func (c PolicyChange) Old() *setting.Snapshot { | ||
return c.snapshots.Old | ||
} | ||
|
||
// HasChanged reports whether a policy setting with the specified [setting.Key], has changed. | ||
func (c PolicyChange) HasChanged(key setting.Key) bool { | ||
new, newErr := c.snapshots.New.GetErr(key) | ||
old, oldErr := c.snapshots.Old.GetErr(key) | ||
if newErr != nil && oldErr != nil { | ||
return false | ||
} | ||
if newErr != nil || oldErr != nil { | ||
return true | ||
} | ||
switch newVal := new.(type) { | ||
case bool, uint64, string, setting.Visibility, setting.PreferenceOption, time.Duration: | ||
return newVal != old | ||
case []string: | ||
oldVal, ok := old.([]string) | ||
return !ok || !slices.Equal(newVal, oldVal) | ||
default: | ||
loggerx.Errorf("[unexpected] %q has an unsupported value type: %T", key, newVal) | ||
return !reflect.DeepEqual(new, old) | ||
} | ||
} | ||
|
||
// policyChangeCallbacks are the callbacks to invoke when the effective policy changes. | ||
// It is safe for concurrent use. | ||
type policyChangeCallbacks struct { | ||
mu sync.Mutex | ||
cbs set.HandleSet[PolicyChangeCallback] | ||
} | ||
|
||
// Register adds the specified callback to be invoked whenever the policy changes. | ||
func (c *policyChangeCallbacks) Register(callback PolicyChangeCallback) (unregister func()) { | ||
c.mu.Lock() | ||
handle := c.cbs.Add(callback) | ||
c.mu.Unlock() | ||
return func() { | ||
c.mu.Lock() | ||
delete(c.cbs, handle) | ||
c.mu.Unlock() | ||
} | ||
} | ||
|
||
// Invoke calls the registered callback functions with the specified policy change info. | ||
func (c *policyChangeCallbacks) Invoke(snapshots Change[*setting.Snapshot]) { | ||
var wg sync.WaitGroup | ||
defer wg.Wait() | ||
|
||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
|
||
wg.Add(len(c.cbs)) | ||
change := &PolicyChange{snapshots: snapshots} | ||
for _, cb := range c.cbs { | ||
go func() { | ||
defer wg.Done() | ||
cb(change) | ||
}() | ||
} | ||
} | ||
|
||
// Close awaits the completion of active callbacks and prevents any further invocations. | ||
func (c *policyChangeCallbacks) Close() { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
if c.cbs != nil { | ||
clear(c.cbs) | ||
c.cbs = nil | ||
} | ||
} |
Oops, something went wrong.