Documentation
¶
Overview ¶
Package jsonoutput provides stable and versioned JSON serialization for CLI output. This allows us to provide stable output to scripts and clients, but also allows us to make useful and breaking changes to the output by incrementing the version number.
Historically we only used a boolean -json flag, so changing the output could break scripts that rely on the existing format.
This package provides a Format flag type that allows callers to specify which output format the command should print. This flag provides Format.JSONBool and Format.JSONSchemaVersion methods to support combining both -format=json and -format=json-line options with either boolean or versioned -json flags.
Unmarshaling JSON output in other programs ¶
The Tailscale client can format the output of many commands in JSON, which makes it easier to write programs that read and process this output. Commands that support JSON output will provide a -json flag:
For example, performing a DNS query produces this human-readable output:
$ tailscale dns query hello.ts.net DNS query for "hello.ts.net" (A) using internal resolver: Forwarding to resolver: 199.247.155.53 Response code: RCodeSuccess Name TTL Class Type Body ---- --- ----- ---- ---- hello.ts.net. 600 ClassINET TypeA 100.101.102.103
that corresponds with this JSON output:
$ tailscale dns query --json hello.ts.net
{
"Name": "hello.ts.net",
"QueryType": "A",
"Resolvers": [
{
"Addr": "199.247.155.53"
}
],
"ResponseCode": "RCodeSuccess",
"Answers": [
{
"Name": "hello.ts.net.",
"TTL": 600,
"Class": "ClassINET",
"Type": "TypeA",
"Body": "100.101.102.103"
}
]
}
To unmarshal this response, use tailscale.com/cmd/tailscale/jsonoutput.DNSStatusResult. For other responses, you can find the corresponding struct in this package or within one of its subpackages.
Defining a stable, versioned JSON format ¶
This package provides the ResponseEnvelope struct which provides the set of fields common to all versioned JSON output. This struct must be embedded in every JSON response from the CLI. For example, a hypothetical "tailscale hello" command:
$ tailscale hello --json=1
{
"SchemaVersion": "1",
"Greeting": "Hello, 世界"
}
would provide a hellocmdjsonv1 package under the tailscale.com/cmd/tailscale/jsonoutput package, that exports of a HelloResponse struct for third-party programs to use:
package hellocmdjsonv1
type HelloResponse struct {
jsonoutput.ResponseEnvelope
Greeting string
}
For an actual example for the "tailscale lock" subcommand, see tailscale.com/cmd/tailscale/tslockjsonv1.
When we make a backwards incompatible change to the JSON output, e.g. if we remove a field or change a field’s type or format, we must add a new package with an incremented ResponseEnvelope.SchemaVersion number. For example, if we were forced to break the format by changing a field’s type:
$ tailscale hello --json=2
{
"SchemaVersion": "2",
"Greeting": {
"en": "Hello, world",
"zh": "你好世界"
}
}
We must create a new hellocmdjsonv2 package that exports an updated HelloResponse that can be used to unmarshal the version 2 output:
package hellocmdjsonv2
type HelloResponse struct {
jsonoutput.ResponseEnvelope
Greeting map[string]string
}
We should also add a ResponseEnvelope.ResponseWarning to older versions that advise clients of the a newer version of this response:
$ tailscale hello --json=1
{
"_WARNING": "a newer schema version is available",
"SchemaVersion": "1",
"Greeting": "Hello, 世界"
}
Marshaling to JSON in the cmd/tailscale client ¶
This package provides a SchemaVersion flag type that allows callers to pass either a boolean -json flag or a version-numbered -json=2 flag in order to get consistent output.
Passing just the boolean flag will always imply -json=1 to preserve compatibility with scripts written before we versioned our output.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Format ¶
type Format struct {
SchemaVersion
// contains filtered or unexported fields
}
Format implements the flag.Value interface, supporting a combination of both -format and -json flags.
For some commands, like tailscale netcheck or tailscale routecheck, the user can specify the output format using the -format flag:
tailscale routecheck -format=json-line.
Setting this flag to "json" or "json-line" implies that the -json flag is set.
func (*Format) IsBoolFlag ¶
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 (*Format) JSONBool ¶
JSONBool returns a flag.Value for a boolean -json flag which is aware of the underlying format.
Example ¶
package main
import (
"flag"
"fmt"
"tailscale.com/cmd/tailscale/jsonoutput"
)
func main() {
var args struct {
format jsonoutput.Format
}
fs := flag.NewFlagSet("ExampleFormat", flag.ExitOnError)
fs.Var(&args.format, "format", `output format; empty (for human-readable), "json" or "json-line"`)
fs.Var(args.format.JSONBool(), "json", "output in JSON format")
fs.Parse([]string{"-json"})
fmt.Printf(`{format: %q, set: %t, version: %d}`, args.format, args.format.IsSet, args.format.Version)
}
Output: {format: "json", set: true, version: 1}
func (*Format) JSONSchemaVersion ¶
JSONSchemaVersion returns a flag.Value for a SchemaVersion -json flag which is aware of the underlying format.
Example ¶
package main
import (
"flag"
"fmt"
"tailscale.com/cmd/tailscale/jsonoutput"
)
func main() {
var args struct {
format jsonoutput.Format
}
fs := flag.NewFlagSet("ExampleFormat", flag.ExitOnError)
fs.Var(&args.format, "format", `output format; empty (for human-readable), "json" or "json-line"`)
fs.Var(args.format.JSONSchemaVersion(), "json", "output in JSON format")
fs.Parse([]string{"-json=2", "-format=json-line"})
fmt.Printf(`{format: %q, set: %t, version: %d}`, args.format, args.format.IsSet, args.format.Version)
}
Output: {format: "json-line", set: true, version: 2}
type ResponseEnvelope ¶
type ResponseEnvelope struct {
// SchemaVersion is the version of the JSON output, e.g. "1", "2", "3"
SchemaVersion string
// ResponseWarning tells a user if a newer version of the JSON output
// is available.
ResponseWarning string `json:"_WARNING,omitzero"`
}
ResponseEnvelope is a set of fields common to all versioned JSON output.
Example ¶
package main
import (
"encoding/json"
"fmt"
"tailscale.com/cmd/tailscale/jsonoutput"
)
type Hello struct {
jsonoutput.ResponseEnvelope
Greeting string
}
func main() {
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" }
type SchemaVersion ¶
type SchemaVersion struct {
// 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
// Version 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.
Version int
}
SchemaVersion implements the flag.Value interface, tracking whether the flag has been set or cleared, and its value when set.
Example ¶
package main
import (
"flag"
"fmt"
"tailscale.com/cmd/tailscale/jsonoutput"
)
var args struct {
json jsonoutput.SchemaVersion
}
func main() {
fs := flag.NewFlagSet("ExampleSchemaVersion", flag.ExitOnError)
fs.Var(&args.json, "json", "output in JSON format")
fs.Parse([]string{"-json=2"})
fmt.Printf(`{set: %t, version: %d}`, args.json.IsSet, args.json.Version)
}
Output: {set: true, version: 2}
func (*SchemaVersion) IsBoolFlag ¶
func (v *SchemaVersion) IsBoolFlag() bool
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 (*SchemaVersion) Set ¶
func (v *SchemaVersion) Set(s string) error
Set is called when the user passes the flag as a command-line argument.
func (SchemaVersion) String ¶
func (v SchemaVersion) String() string
String returns the default value which is printed in the CLI help text.