mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
util/syspolicy/setting: make setting.Snapshot JSON-marshallable
Some checks are pending
checklocks / checklocks (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
Dockerfile build / deploy (push) Waiting to run
CI / staticcheck (amd64, windows) (push) Waiting to run
CI / notify_slack (push) Blocked by required conditions
CI / check_mergeability (push) Blocked by required conditions
CI / test (-race, amd64, 2/3) (push) Waiting to run
CI / test (-race, amd64, 3/3) (push) Waiting to run
CI / go_generate (push) Waiting to run
CI / go_mod_tidy (push) Waiting to run
CI / licenses (push) Waiting to run
CI / race-root-integration (1/4) (push) Waiting to run
CI / race-root-integration (2/4) (push) Waiting to run
CI / race-root-integration (3/4) (push) Waiting to run
CI / race-root-integration (4/4) (push) Waiting to run
CI / test (-coverprofile=/tmp/coverage.out, amd64) (push) Waiting to run
CI / test (-race, amd64, 1/3) (push) Waiting to run
CI / staticcheck (386, windows) (push) Waiting to run
CI / test (386) (push) Waiting to run
CI / windows (push) Waiting to run
CI / privileged (push) Waiting to run
CI / vm (push) Waiting to run
CI / race-build (push) Waiting to run
CI / staticcheck (amd64, darwin) (push) Waiting to run
CI / cross (386, linux) (push) Waiting to run
CI / cross (amd64, darwin) (push) Waiting to run
CI / cross (amd64, freebsd) (push) Waiting to run
CI / cross (amd64, openbsd) (push) Waiting to run
CI / cross (amd64, windows) (push) Waiting to run
CI / cross (arm, 5, linux) (push) Waiting to run
CI / cross (arm, 7, linux) (push) Waiting to run
CI / cross (arm64, darwin) (push) Waiting to run
CI / cross (arm64, linux) (push) Waiting to run
CI / cross (arm64, windows) (push) Waiting to run
CI / cross (loong64, linux) (push) Waiting to run
CI / ios (push) Waiting to run
CI / crossmin (amd64, plan9) (push) Waiting to run
CI / staticcheck (amd64, linux) (push) Waiting to run
CI / crossmin (ppc64, aix) (push) Waiting to run
CI / android (push) Waiting to run
CI / wasm (push) Waiting to run
CI / tailscale_go (push) Waiting to run
CI / fuzz (push) Waiting to run
CI / depaware (push) Waiting to run
Some checks are pending
checklocks / checklocks (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
Dockerfile build / deploy (push) Waiting to run
CI / staticcheck (amd64, windows) (push) Waiting to run
CI / notify_slack (push) Blocked by required conditions
CI / check_mergeability (push) Blocked by required conditions
CI / test (-race, amd64, 2/3) (push) Waiting to run
CI / test (-race, amd64, 3/3) (push) Waiting to run
CI / go_generate (push) Waiting to run
CI / go_mod_tidy (push) Waiting to run
CI / licenses (push) Waiting to run
CI / race-root-integration (1/4) (push) Waiting to run
CI / race-root-integration (2/4) (push) Waiting to run
CI / race-root-integration (3/4) (push) Waiting to run
CI / race-root-integration (4/4) (push) Waiting to run
CI / test (-coverprofile=/tmp/coverage.out, amd64) (push) Waiting to run
CI / test (-race, amd64, 1/3) (push) Waiting to run
CI / staticcheck (386, windows) (push) Waiting to run
CI / test (386) (push) Waiting to run
CI / windows (push) Waiting to run
CI / privileged (push) Waiting to run
CI / vm (push) Waiting to run
CI / race-build (push) Waiting to run
CI / staticcheck (amd64, darwin) (push) Waiting to run
CI / cross (386, linux) (push) Waiting to run
CI / cross (amd64, darwin) (push) Waiting to run
CI / cross (amd64, freebsd) (push) Waiting to run
CI / cross (amd64, openbsd) (push) Waiting to run
CI / cross (amd64, windows) (push) Waiting to run
CI / cross (arm, 5, linux) (push) Waiting to run
CI / cross (arm, 7, linux) (push) Waiting to run
CI / cross (arm64, darwin) (push) Waiting to run
CI / cross (arm64, linux) (push) Waiting to run
CI / cross (arm64, windows) (push) Waiting to run
CI / cross (loong64, linux) (push) Waiting to run
CI / ios (push) Waiting to run
CI / crossmin (amd64, plan9) (push) Waiting to run
CI / staticcheck (amd64, linux) (push) Waiting to run
CI / crossmin (ppc64, aix) (push) Waiting to run
CI / android (push) Waiting to run
CI / wasm (push) Waiting to run
CI / tailscale_go (push) Waiting to run
CI / fuzz (push) Waiting to run
CI / depaware (push) Waiting to run
We make setting.Snapshot JSON-marshallable in preparation for returning it from the LocalAPI. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
parent
2a2228f97b
commit
540e4c83d0
@ -4,11 +4,14 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
"tailscale.com/util/deephash"
|
||||
)
|
||||
@ -65,6 +68,9 @@ func (s *Snapshot) GetSetting(k Key) (setting RawItem, ok bool) {
|
||||
|
||||
// Equal reports whether s and s2 are equal.
|
||||
func (s *Snapshot) Equal(s2 *Snapshot) bool {
|
||||
if s == s2 {
|
||||
return true
|
||||
}
|
||||
if !s.EqualItems(s2) {
|
||||
return false
|
||||
}
|
||||
@ -135,6 +141,45 @@ func (s *Snapshot) String() string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// snapshotJSON holds JSON-marshallable data for [Snapshot].
|
||||
type snapshotJSON struct {
|
||||
Summary Summary `json:",omitzero"`
|
||||
Settings map[Key]RawItem `json:",omitempty"`
|
||||
}
|
||||
|
||||
// MarshalJSONV2 implements [jsonv2.MarshalerV2].
|
||||
func (s *Snapshot) MarshalJSONV2(out *jsontext.Encoder, opts jsonv2.Options) error {
|
||||
data := &snapshotJSON{}
|
||||
if s != nil {
|
||||
data.Summary = s.summary
|
||||
data.Settings = s.m
|
||||
}
|
||||
return jsonv2.MarshalEncode(out, data, opts)
|
||||
}
|
||||
|
||||
// UnmarshalJSONV2 implements [jsonv2.UnmarshalerV2].
|
||||
func (s *Snapshot) UnmarshalJSONV2(in *jsontext.Decoder, opts jsonv2.Options) error {
|
||||
if s == nil {
|
||||
return errors.New("s must not be nil")
|
||||
}
|
||||
data := &snapshotJSON{}
|
||||
if err := jsonv2.UnmarshalDecode(in, data, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = Snapshot{m: data.Settings, sig: deephash.Hash(&data.Settings), summary: data.Summary}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements [json.Marshaler].
|
||||
func (s *Snapshot) MarshalJSON() ([]byte, error) {
|
||||
return jsonv2.Marshal(s) // uses MarshalJSONV2
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [json.Unmarshaler].
|
||||
func (s *Snapshot) UnmarshalJSON(b []byte) error {
|
||||
return jsonv2.Unmarshal(b, s) // uses UnmarshalJSONV2
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -4,8 +4,13 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
jsonv2 "github.com/go-json-experiment/json"
|
||||
"tailscale.com/util/syspolicy/internal"
|
||||
)
|
||||
|
||||
func TestMergeSnapshots(t *testing.T) {
|
||||
@ -432,3 +437,133 @@ Setting3 = user-decides`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalSnapshot(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
snapshot *Snapshot
|
||||
wantJSON string
|
||||
wantBack *Snapshot
|
||||
}{
|
||||
{
|
||||
name: "Nil",
|
||||
snapshot: (*Snapshot)(nil),
|
||||
wantJSON: "null",
|
||||
wantBack: NewSnapshot(nil),
|
||||
},
|
||||
{
|
||||
name: "Zero",
|
||||
snapshot: &Snapshot{},
|
||||
wantJSON: "{}",
|
||||
},
|
||||
{
|
||||
name: "Bool/True",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(true)}),
|
||||
wantJSON: `{"Settings": {"BoolPolicy": {"Value": true}}}`,
|
||||
},
|
||||
{
|
||||
name: "Bool/False",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"BoolPolicy": RawItemOf(false)}),
|
||||
wantJSON: `{"Settings": {"BoolPolicy": {"Value": false}}}`,
|
||||
},
|
||||
{
|
||||
name: "String/Non-Empty",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("StringValue")}),
|
||||
wantJSON: `{"Settings": {"StringPolicy": {"Value": "StringValue"}}}`,
|
||||
},
|
||||
{
|
||||
name: "String/Empty",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"StringPolicy": RawItemOf("")}),
|
||||
wantJSON: `{"Settings": {"StringPolicy": {"Value": ""}}}`,
|
||||
},
|
||||
{
|
||||
name: "Integer/NonZero",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(42))}),
|
||||
wantJSON: `{"Settings": {"IntPolicy": {"Value": 42}}}`,
|
||||
},
|
||||
{
|
||||
name: "Integer/Zero",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"IntPolicy": RawItemOf(uint64(0))}),
|
||||
wantJSON: `{"Settings": {"IntPolicy": {"Value": 0}}}`,
|
||||
},
|
||||
{
|
||||
name: "String-List",
|
||||
snapshot: NewSnapshot(map[Key]RawItem{"ListPolicy": RawItemOf([]string{"Value1", "Value2"})}),
|
||||
wantJSON: `{"Settings": {"ListPolicy": {"Value": ["Value1", "Value2"]}}}`,
|
||||
},
|
||||
{
|
||||
name: "Empty/With-Summary",
|
||||
snapshot: NewSnapshot(
|
||||
map[Key]RawItem{},
|
||||
SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)),
|
||||
),
|
||||
wantJSON: `{"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"}}`,
|
||||
},
|
||||
{
|
||||
name: "Setting/With-Summary",
|
||||
snapshot: NewSnapshot(
|
||||
map[Key]RawItem{"PolicySetting": RawItemOf(uint64(42))},
|
||||
SummaryWith(CurrentUserScope, NewNamedOrigin("TestSource", DeviceScope)),
|
||||
),
|
||||
wantJSON: `{
|
||||
"Summary": {"Origin": {"Name": "TestSource", "Scope": "Device"}, "Scope": "User"},
|
||||
"Settings": {"PolicySetting": {"Value": 42}}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Settings/With-Origins",
|
||||
snapshot: NewSnapshot(
|
||||
map[Key]RawItem{
|
||||
"SettingA": RawItemWith(uint64(42), nil, NewNamedOrigin("SourceA", DeviceScope)),
|
||||
"SettingB": RawItemWith("B", nil, NewNamedOrigin("SourceB", CurrentProfileScope)),
|
||||
"SettingC": RawItemWith(true, nil, NewNamedOrigin("SourceC", CurrentUserScope)),
|
||||
},
|
||||
),
|
||||
wantJSON: `{
|
||||
"Settings": {
|
||||
"SettingA": {"Value": 42, "Origin": {"Name": "SourceA", "Scope": "Device"}},
|
||||
"SettingB": {"Value": "B", "Origin": {"Name": "SourceB", "Scope": "Profile"}},
|
||||
"SettingC": {"Value": true, "Origin": {"Name": "SourceC", "Scope": "User"}}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
doTest := func(t *testing.T, useJSONv2 bool) {
|
||||
var gotJSON []byte
|
||||
var err error
|
||||
if useJSONv2 {
|
||||
gotJSON, err = jsonv2.Marshal(tt.snapshot)
|
||||
} else {
|
||||
gotJSON, err = json.Marshal(tt.snapshot)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got, want, equal := internal.EqualJSONForTest(t, gotJSON, []byte(tt.wantJSON)); !equal {
|
||||
t.Errorf("JSON: got %s; want %s", got, want)
|
||||
}
|
||||
|
||||
gotBack := &Snapshot{}
|
||||
if useJSONv2 {
|
||||
err = jsonv2.Unmarshal(gotJSON, &gotBack)
|
||||
} else {
|
||||
err = json.Unmarshal(gotJSON, &gotBack)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if wantBack := cmp.Or(tt.wantBack, tt.snapshot); !gotBack.Equal(wantBack) {
|
||||
t.Errorf("Snapshot: got %+v; want %+v", gotBack, wantBack)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("json", func(t *testing.T) { doTest(t, false) })
|
||||
t.Run("jsonv2", func(t *testing.T) { doTest(t, true) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user