diff --git a/cmd/tailscale/cli/jsonoutput/dns.go b/cmd/tailscale/cli/jsonoutput/dns.go index d9d3cc0bb..66e6a031f 100644 --- a/cmd/tailscale/cli/jsonoutput/dns.go +++ b/cmd/tailscale/cli/jsonoutput/dns.go @@ -3,7 +3,7 @@ package jsonoutput -// DNSResolverInfo is the JSON form of [dnstype.Resolver]. +// DNSResolverInfo is the JSON form of [tailscale.com/types/dnstype.Resolver]. type DNSResolverInfo struct { // Addr is a plain IP, IP:port, DoH URL, or HTTP-over-WireGuard URL. Addr string @@ -13,7 +13,7 @@ type DNSResolverInfo struct { BootstrapResolution []string `json:",omitempty"` } -// DNSExtraRecord is the JSON form of [tailcfg.DNSRecord]. +// DNSExtraRecord is the JSON form of [tailscale.com/tailcfg.DNSRecord]. type DNSExtraRecord struct { Name string Type string `json:",omitempty"` // empty means A or AAAA, depending on Value @@ -21,7 +21,7 @@ type DNSExtraRecord struct { } // DNSSystemConfig is the OS DNS configuration as observed by Tailscale, -// mirroring [net/dns.OSConfig]. +// mirroring [tailscale.com/net/dns.OSConfig]. type DNSSystemConfig struct { Nameservers []string `json:",omitzero"` SearchDomains []string `json:",omitzero"` @@ -33,7 +33,8 @@ type DNSSystemConfig struct { } // DNSTailnetInfo describes MagicDNS configuration for the tailnet, -// combining [ipnstate.TailnetStatus] and [ipnstate.PeerStatus]. +// combining [tailscale.com/ipn/ipnstate.TailnetStatus] +// and [tailscale.com/ipn/ipnstate.PeerStatus]. type DNSTailnetInfo struct { // MagicDNSEnabled is whether MagicDNS is enabled for the // tailnet. The device may still not use it if diff --git a/cmd/tailscale/cli/jsonoutput/example_jsonschemaversion_test.go b/cmd/tailscale/cli/jsonoutput/example_jsonschemaversion_test.go new file mode 100644 index 000000000..d51041cd8 --- /dev/null +++ b/cmd/tailscale/cli/jsonoutput/example_jsonschemaversion_test.go @@ -0,0 +1,25 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package jsonoutput_test + +import ( + "flag" + "fmt" + + "tailscale.com/cmd/tailscale/cli/jsonoutput" +) + +var args struct { + json jsonoutput.JSONSchemaVersion +} + +func ExampleJSONSchemaVersion() { + fs := flag.NewFlagSet("ExampleJSONSchemaVersion", flag.ExitOnError) + fs.Var(&args.json, "json", "output in JSON format") + + fs.Parse([]string{"-json=2"}) + fmt.Printf(`{set: %t, value: %d}`, args.json.IsSet, args.json.Value) + // Output: + // {set: true, value: 2} +} diff --git a/cmd/tailscale/cli/jsonoutput/example_responseenvelope_test.go b/cmd/tailscale/cli/jsonoutput/example_responseenvelope_test.go new file mode 100644 index 000000000..b48edef6d --- /dev/null +++ b/cmd/tailscale/cli/jsonoutput/example_responseenvelope_test.go @@ -0,0 +1,33 @@ +// Copyright (c) Tailscale Inc & contributors +// SPDX-License-Identifier: BSD-3-Clause + +package jsonoutput_test + +import ( + "encoding/json" + "fmt" + + "tailscale.com/cmd/tailscale/cli/jsonoutput" +) + +type Hello struct { + jsonoutput.ResponseEnvelope + Greeting string +} + +func ExampleResponseEnvelope() { + hi := Hello{ + ResponseEnvelope: jsonoutput.ResponseEnvelope{SchemaVersion: "1"}, + Greeting: "Hello, world", + } + out, err := json.MarshalIndent(hi, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("%s\n", out) + // Output: + // { + // "SchemaVersion": "1", + // "Greeting": "Hello, world" + // } +} diff --git a/cmd/tailscale/cli/jsonoutput/jsonoutput.go b/cmd/tailscale/cli/jsonoutput/jsonoutput.go index 69e7374d9..5a4af54af 100644 --- a/cmd/tailscale/cli/jsonoutput/jsonoutput.go +++ b/cmd/tailscale/cli/jsonoutput/jsonoutput.go @@ -5,32 +5,40 @@ // This allows us to provide stable output to scripts/clients, but also make // breaking changes to the output when it's useful. // -// Historically we only used `--json` as a boolean flag, so changing the output +// Historically we only used a boolean -json flag, so changing the output // could break scripts that rely on the existing format. // -// This package allows callers to pass a version number to `--json` and get -// a consistent output. We'll bump the version when we make a breaking change -// that's likely to break scripts that rely on the existing output, e.g. if -// we remove a field or change the type/format. -// -// Passing just the boolean flag `--json` will always return v1, to preserve +// This package provides a [JSONSchemaVersion] flag type that allows callers +// to pass either a boolean or a version number and get a consistent output. +// We'll bump the version when we make a breaking change +// that's likely to break scripts that rely on the existing output, +// e.g. if we remove a field or change the type/format. +// Passing just the boolean flag will always return 1, to preserve // compatibility with scripts written before we versioned our output. +// +// This package also provides [ResponseEnvelope] which is used to provide the +// set of fields common to all versioned JSON output. package jsonoutput import ( "errors" + "flag" "fmt" "strconv" ) -// JSONSchemaVersion implements flag.Value, and tracks whether the CLI has -// been called with `--json`, and if so, with what value. +var _ flag.Value = &JSONSchemaVersion{} + +// JSONSchemaVersion implements the [flag.Value] interface, +// tracking whether the flag has been set or cleared, and its value when set. type JSONSchemaVersion struct { - // IsSet tracks if the flag was provided at all. + // IsSet tracks if the flag was set or cleared. + // This flag is true when set by -name or -name=true or -name=INT, + // otherwise it is false when cleared by -name=false. IsSet bool - // Value tracks the desired schema version, which defaults to 1 if - // the user passes `--json` without an argument. + // Value tracks the desired schema version, as set by the -name=INT flag. + // The version defaults to 1 when implicitly set by -name or -name=true. Value int } @@ -67,8 +75,9 @@ func (v *JSONSchemaVersion) Set(s string) error { return nil } -// IsBoolFlag tells the flag package that JSONSchemaVersion can be set -// without an argument. +// IsBoolFlag reports that this [flag.Value] can be set without an argument. +// This is the magic interface that makes -name equivalent to -name=true +// rather than using the next command-line argument. func (v *JSONSchemaVersion) IsBoolFlag() bool { return true }