mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 22:15:51 +00:00
67df9abdc6
Package setting contains types for defining and representing policy settings. It facilitates the registration of setting definitions using Register and RegisterDefinition, and the retrieval of registered setting definitions via Definitions and DefinitionOf. This package is intended for use primarily within the syspolicy package hierarchy, and added in a preparation for the next PRs. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
174 lines
4.9 KiB
Go
174 lines
4.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package setting
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
|
|
xmaps "golang.org/x/exp/maps"
|
|
"tailscale.com/util/deephash"
|
|
)
|
|
|
|
// Snapshot is an immutable collection of ([Key], [RawItem]) pairs, representing
|
|
// a set of policy settings applied at a specific moment in time.
|
|
// A nil pointer to [Snapshot] is valid.
|
|
type Snapshot struct {
|
|
m map[Key]RawItem
|
|
sig deephash.Sum // of m
|
|
summary Summary
|
|
}
|
|
|
|
// NewSnapshot returns a new [Snapshot] with the specified items and options.
|
|
func NewSnapshot(items map[Key]RawItem, opts ...SummaryOption) *Snapshot {
|
|
return &Snapshot{m: xmaps.Clone(items), sig: deephash.Hash(&items), summary: SummaryWith(opts...)}
|
|
}
|
|
|
|
// All returns a map of all policy settings in s.
|
|
// The returned map must not be modified.
|
|
func (s *Snapshot) All() map[Key]RawItem {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
// TODO(nickkhyl): return iter.Seq2[[Key], [RawItem]] in Go 1.23,
|
|
// and remove [keyItemPair].
|
|
return s.m
|
|
}
|
|
|
|
// Get returns the value of the policy setting with the specified key
|
|
// or nil if it is not configured or has an error.
|
|
func (s *Snapshot) Get(k Key) any {
|
|
v, _ := s.GetErr(k)
|
|
return v
|
|
}
|
|
|
|
// GetErr returns the value of the policy setting with the specified key,
|
|
// [ErrNotConfigured] if it is not configured, or an error returned by
|
|
// the policy Store if the policy setting could not be read.
|
|
func (s *Snapshot) GetErr(k Key) (any, error) {
|
|
if s != nil {
|
|
if s, ok := s.m[k]; ok {
|
|
return s.Value(), s.Error()
|
|
}
|
|
}
|
|
return nil, ErrNotConfigured
|
|
}
|
|
|
|
// GetSetting returns the untyped policy setting with the specified key and true
|
|
// if a policy setting with such key has been configured;
|
|
// otherwise, it returns zero, false.
|
|
func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
|
|
setting, ok = s.m[k]
|
|
return setting, ok
|
|
}
|
|
|
|
// Equal reports whether s and s2 are equal.
|
|
func (s *Snapshot) Equal(s2 *Snapshot) bool {
|
|
if !s.EqualItems(s2) {
|
|
return false
|
|
}
|
|
return s.Summary() == s2.Summary()
|
|
}
|
|
|
|
// EqualItems reports whether items in s and s2 are equal.
|
|
func (s *Snapshot) EqualItems(s2 *Snapshot) bool {
|
|
if s == s2 {
|
|
return true
|
|
}
|
|
if s.Len() != s2.Len() {
|
|
return false
|
|
}
|
|
if s.Len() == 0 {
|
|
return true
|
|
}
|
|
return s.sig == s2.sig
|
|
}
|
|
|
|
// Keys return an iterator over keys in s. The iteration order is not specified
|
|
// and is not guaranteed to be the same from one call to the next.
|
|
func (s *Snapshot) Keys() []Key {
|
|
if s.m == nil {
|
|
return nil
|
|
}
|
|
// TODO(nickkhyl): return iter.Seq[Key] in Go 1.23.
|
|
return xmaps.Keys(s.m)
|
|
}
|
|
|
|
// Len reports the number of [RawItem]s in s.
|
|
func (s *Snapshot) Len() int {
|
|
if s == nil {
|
|
return 0
|
|
}
|
|
return len(s.m)
|
|
}
|
|
|
|
// Summary returns information about s as a whole rather than about specific [RawItem]s in it.
|
|
func (s *Snapshot) Summary() Summary {
|
|
if s == nil {
|
|
return Summary{}
|
|
}
|
|
return s.summary
|
|
}
|
|
|
|
// String implements [fmt.Stringer]
|
|
func (s *Snapshot) String() string {
|
|
if s.Len() == 0 && s.Summary().IsEmpty() {
|
|
return "{Empty}"
|
|
}
|
|
keys := s.Keys()
|
|
slices.Sort(keys)
|
|
var sb strings.Builder
|
|
if !s.summary.IsEmpty() {
|
|
sb.WriteRune('{')
|
|
if s.Len() == 0 {
|
|
sb.WriteString("Empty, ")
|
|
}
|
|
sb.WriteString(s.summary.String())
|
|
sb.WriteRune('}')
|
|
}
|
|
for _, k := range keys {
|
|
if sb.Len() != 0 {
|
|
sb.WriteRune('\n')
|
|
}
|
|
sb.WriteString(string(k))
|
|
sb.WriteString(" = ")
|
|
sb.WriteString(s.m[k].String())
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// MergeSnapshots returns a [Snapshot] that contains all [RawItem]s
|
|
// from snapshot1 and snapshot2 and the [Summary] with the narrower [PolicyScope].
|
|
// If there's a conflict between policy settings in the two snapshots,
|
|
// the policy settings from the snapshot with the broader scope take precedence.
|
|
// In other words, policy settings configured for the [DeviceScope] win
|
|
// over policy settings configured for a user scope.
|
|
func MergeSnapshots(snapshot1, snapshot2 *Snapshot) *Snapshot {
|
|
scope1, ok1 := snapshot1.Summary().Scope().GetOk()
|
|
scope2, ok2 := snapshot2.Summary().Scope().GetOk()
|
|
if ok1 && ok2 && scope1.StrictlyContains(scope2) {
|
|
// Swap snapshots if snapshot1 has higher precedence than snapshot2.
|
|
snapshot1, snapshot2 = snapshot2, snapshot1
|
|
}
|
|
if snapshot2.Len() == 0 {
|
|
return snapshot1
|
|
}
|
|
summaryOpts := make([]SummaryOption, 0, 2)
|
|
if scope, ok := snapshot1.Summary().Scope().GetOk(); ok {
|
|
// Use the scope from snapshot1, if present, which is the more specific snapshot.
|
|
summaryOpts = append(summaryOpts, scope)
|
|
}
|
|
if snapshot1.Len() == 0 {
|
|
if origin, ok := snapshot2.Summary().Origin().GetOk(); ok {
|
|
// Use the origin from snapshot2 if snapshot1 is empty.
|
|
summaryOpts = append(summaryOpts, origin)
|
|
}
|
|
return &Snapshot{snapshot2.m, snapshot2.sig, SummaryWith(summaryOpts...)}
|
|
}
|
|
m := make(map[Key]RawItem, snapshot1.Len()+snapshot2.Len())
|
|
xmaps.Copy(m, snapshot1.m)
|
|
xmaps.Copy(m, snapshot2.m) // snapshot2 has higher precedence
|
|
return &Snapshot{m, deephash.Hash(&m), SummaryWith(summaryOpts...)}
|
|
}
|