ipn/ipnlocal: use views for Peer.PrimaryRoutes and Peer.Tags

RELNOTE=`tailscale status --json` now shows Tags and PrimaryRoutes

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali
2022-02-22 09:52:49 -08:00
committed by Maisem Ali
parent 9cbb0913be
commit c7a8f0992d
6 changed files with 140 additions and 8 deletions

View File

@@ -7,6 +7,9 @@
package views
import (
"encoding/json"
"errors"
"inet.af/netaddr"
"tailscale.com/net/tsaddr"
)
@@ -21,6 +24,28 @@ type StringSlice struct {
// StringSliceOf returns a StringSlice for the provided slice.
func StringSliceOf(x []string) StringSlice { return StringSlice{x} }
// MarshalJSON implements json.Marshaler.
func (v StringSlice) MarshalJSON() ([]byte, error) {
return json.Marshal(v.ж)
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *StringSlice) UnmarshalJSON(b []byte) error {
if v.ж != nil {
return errors.New("StringSlice is already initialized")
}
if len(b) == 0 {
return nil
}
if err := json.Unmarshal(b, &v.ж); err != nil {
return err
}
return nil
}
// IsNil reports whether the underlying slice is nil.
func (v StringSlice) IsNil() bool { return v.ж == nil }
// Len returns the length of the slice.
func (v StringSlice) Len() int { return len(v.ж) }
@@ -47,6 +72,9 @@ type IPPrefixSlice struct {
// IPPrefixSliceOf returns a IPPrefixSlice for the provided slice.
func IPPrefixSliceOf(x []netaddr.IPPrefix) IPPrefixSlice { return IPPrefixSlice{x} }
// IsNil reports whether the underlying slice is nil.
func (v IPPrefixSlice) IsNil() bool { return v.ж == nil }
// Len returns the length of the slice.
func (v IPPrefixSlice) Len() int { return len(v.ж) }
@@ -72,3 +100,22 @@ func (v IPPrefixSlice) ContainsIP(ip netaddr.IP) bool {
func (v IPPrefixSlice) ContainsFunc(f func(netaddr.IPPrefix) bool) bool {
return tsaddr.PrefixesContainsFunc(v.ж, f)
}
// MarshalJSON implements json.Marshaler.
func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
return json.Marshal(v.ж)
}
// UnmarshalJSON implements json.Unmarshaler.
func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error {
if v.ж != nil {
return errors.New("IPPrefixSlice is already initialized")
}
if len(b) == 0 {
return nil
}
if err := json.Unmarshal(b, &v.ж); err != nil {
return err
}
return nil
}

73
types/views/views_test.go Normal file
View File

@@ -0,0 +1,73 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package views
import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"inet.af/netaddr"
)
func TestViewsJSON(t *testing.T) {
mustCIDR := func(cidrs ...string) (out []netaddr.IPPrefix) {
for _, cidr := range cidrs {
out = append(out, netaddr.MustParseIPPrefix(cidr))
}
return
}
type viewStruct struct {
Addrs IPPrefixSlice
Strings StringSlice
AddrsPtr *IPPrefixSlice `json:",omitempty"`
StringsPtr *StringSlice `json:",omitempty"`
}
tests := []struct {
name string
in viewStruct
wantJSON string
}{
{
name: "empty",
in: viewStruct{},
wantJSON: `{"Addrs":null,"Strings":null}`,
},
{
name: "everything",
in: viewStruct{
Addrs: IPPrefixSliceOf(mustCIDR("192.168.0.0/24")),
AddrsPtr: &IPPrefixSlice{mustCIDR("192.168.0.0/24")},
StringsPtr: &StringSlice{[]string{"foo"}},
Strings: StringSlice{[]string{"bar"}},
},
wantJSON: `{"Addrs":["192.168.0.0/24"],"Strings":["bar"],"AddrsPtr":["192.168.0.0/24"],"StringsPtr":["foo"]}`,
},
}
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", "")
for _, tc := range tests {
buf.Reset()
if err := encoder.Encode(&tc.in); err != nil {
t.Fatal(err)
}
b := buf.Bytes()
gotJSON := strings.TrimSpace(string(b))
if tc.wantJSON != gotJSON {
t.Fatalf("JSON: %v; want: %v", gotJSON, tc.wantJSON)
}
var got viewStruct
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tc.in) {
t.Fatalf("unmarshal resulted in different output: %+v; want %+v", got, tc.in)
}
}
}