mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 22:15:51 +00:00
ff5f233c3a
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 #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
// 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
|
|
}
|
|
}
|