Documentation
¶
Overview ¶
Package jsonoutput provides stable and versioned JSON serialisation for CLI output. 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 a boolean -json flag, so changing the output could break scripts that rely on the existing format.
This package provides a SchemaVersion 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 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.
This package also provides ResponseEnvelope which is used to provide the set of fields common to all versioned JSON output.
Index ¶
- func PrintTailnetLockLogJSONV1(out io.Writer, updates []ipnstate.TailnetLockUpdate) error
- func PrintTailnetLockStatusJSONV1(out io.Writer, status *ipnstate.TailnetLockStatus) error
- type DNSAnswer
- type DNSExtraRecord
- type DNSQueryResult
- type DNSResolverInfo
- type DNSStatusResult
- type DNSSystemConfig
- type DNSTailnetInfo
- type Format
- type ResponseEnvelope
- type SchemaVersion
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func PrintTailnetLockLogJSONV1 ¶ added in v1.102.0
func PrintTailnetLockLogJSONV1(out io.Writer, updates []ipnstate.TailnetLockUpdate) error
PrintTailnetLockLogJSONV1 prints the stored TKA state as a JSON object to the CLI, in a stable "v1" format.
This format includes:
- the AUM hash as a base32-encoded string
- the raw AUM as base64-encoded bytes
- the expanded AUM, which prints named fields for consumption by other tools
func PrintTailnetLockStatusJSONV1 ¶ added in v1.102.0
func PrintTailnetLockStatusJSONV1(out io.Writer, status *ipnstate.TailnetLockStatus) error
PrintTailnetLockStatusJSONV1 prints the current Tailnet Lock status as a JSON object to the CLI, in a stable "v1" format.
Types ¶
type DNSAnswer ¶ added in v1.96.0
type DNSAnswer struct {
Name string
TTL uint32
Class string // e.g. "ClassINET"
Type string // e.g. "TypeA", "TypeAAAA"
Body string // human-readable record data
}
DNSAnswer is a single DNS resource record from a query response.
type DNSExtraRecord ¶ added in v1.96.0
type DNSExtraRecord struct {
Name string
Type string `json:",omitempty"` // empty means A or AAAA, depending on Value
Value string // typically an IP address
}
DNSExtraRecord is the JSON form of tailscale.com/tailcfg.DNSRecord.
type DNSQueryResult ¶ added in v1.96.0
type DNSQueryResult struct {
Name string
QueryType string // e.g. "A", "AAAA"
Resolvers []DNSResolverInfo `json:",omitzero"`
ResponseCode string // e.g. "RCodeSuccess", "RCodeNameError"
Answers []DNSAnswer `json:",omitzero"`
}
DNSQueryResult is the result of a DNS query via the Tailscale internal forwarder (100.100.100.100). It is the output of:
$ tailscale dns query --json NAME
Example ¶
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"tailscale.com/cmd/tailscale/cli/jsonoutput"
)
func main() {
cmd := exec.Command("tailscale", "dns", "query", "--json", "hello.ts.net")
out, err := cmd.Output()
if err != nil {
if err, ok := errors.AsType[*exec.ExitError](err); ok {
fmt.Fprintf(os.Stderr, "%s", err.Stderr)
}
panic(err)
}
var dnsQuery jsonoutput.DNSQueryResult
if err := json.Unmarshal(out, &dnsQuery); err != nil {
panic(err)
}
fmt.Printf("{type: %s, name: %q}\n", dnsQuery.QueryType, dnsQuery.Name)
}
Output:
type DNSResolverInfo ¶ added in v1.96.0
type DNSResolverInfo struct {
// Addr is a plain IP, IP:port, DoH URL, or HTTP-over-WireGuard URL.
Addr string
// BootstrapResolution is optional pre-resolved IPs for DoT/DoH
// resolvers whose address is not already an IP.
BootstrapResolution []string `json:",omitempty"`
}
DNSResolverInfo is the JSON form of tailscale.com/types/dnstype.Resolver.
type DNSStatusResult ¶ added in v1.96.0
type DNSStatusResult struct {
// TailscaleDNS is whether the Tailscale DNS configuration is
// installed on this device (the --accept-dns setting).
TailscaleDNS bool
// CurrentTailnet describes MagicDNS configuration for the tailnet.
CurrentTailnet *DNSTailnetInfo `json:",omitzero"` // nil if not connected
// Resolvers are the DNS resolvers, in preference order. If
// empty, the system defaults are used.
Resolvers []DNSResolverInfo `json:",omitzero"`
// SplitDNSRoutes maps domain suffixes to dedicated resolvers.
// An empty resolver slice means the suffix is handled by
// Tailscale's built-in resolver (100.100.100.100).
SplitDNSRoutes map[string][]DNSResolverInfo `json:",omitzero"`
// FallbackResolvers are like Resolvers but only used when
// split DNS needs explicit default resolvers.
FallbackResolvers []DNSResolverInfo `json:",omitzero"`
SearchDomains []string `json:",omitzero"`
// Nameservers are nameserver IPs.
//
// Deprecated: old protocol versions only. Use Resolvers.
Nameservers []string `json:",omitzero"`
// CertDomains are FQDNs for which the coordination server
// provisions TLS certificates via dns-01 ACME challenges.
CertDomains []string `json:",omitzero"`
// ExtraRecords contains extra DNS records in the MagicDNS config.
ExtraRecords []DNSExtraRecord `json:",omitzero"`
// ExitNodeFilteredSet are DNS suffixes this node won't resolve
// when acting as an exit node DNS proxy. Period-prefixed
// entries are suffix matches; others are exact. Always
// lowercase, no trailing dots.
ExitNodeFilteredSet []string `json:",omitzero"`
SystemDNS *DNSSystemConfig `json:",omitzero"` // nil if unavailable
SystemDNSError string `json:",omitempty"`
}
DNSStatusResult is the full DNS status collected from the local Tailscale daemon. It is the output of:
$ tailscale dns status --json
Example ¶
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"tailscale.com/cmd/tailscale/cli/jsonoutput"
)
func main() {
cmd := exec.Command("tailscale", "dns", "status", "--json")
out, err := cmd.Output()
if err != nil {
if err, ok := errors.AsType[*exec.ExitError](err); ok {
fmt.Fprintf(os.Stderr, "%s", err.Stderr)
}
panic(err)
}
var dnsStatus jsonoutput.DNSStatusResult
if err := json.Unmarshal(out, &dnsStatus); err != nil {
panic(err)
}
fmt.Printf("{accept-dns: %t, resolvers: %q}\n", dnsStatus.TailscaleDNS, dnsStatus.Resolvers)
}
Output:
type DNSSystemConfig ¶ added in v1.96.0
type DNSSystemConfig struct {
Nameservers []string `json:",omitzero"`
SearchDomains []string `json:",omitzero"`
// MatchDomains are DNS suffixes restricting which queries use
// these Nameservers. Empty means Nameservers is the primary
// resolver.
MatchDomains []string `json:",omitzero"`
}
DNSSystemConfig is the OS DNS configuration as observed by Tailscale, mirroring tailscale.com/net/dns.OSConfig.
type DNSTailnetInfo ¶ added in v1.96.0
type DNSTailnetInfo struct {
// MagicDNSEnabled is whether MagicDNS is enabled for the
// tailnet. The device may still not use it if
// --accept-dns=false.
MagicDNSEnabled bool
// MagicDNSSuffix is the tailnet's MagicDNS suffix
// (e.g. "tail1234.ts.net"), without surrounding dots.
MagicDNSSuffix string `json:",omitempty"`
// SelfDNSName is this device's FQDN
// (e.g. "host.tail1234.ts.net."), with trailing dot.
SelfDNSName string `json:",omitempty"`
}
DNSTailnetInfo describes MagicDNS configuration for the tailnet, combining tailscale.com/ipn/ipnstate.TailnetStatus and tailscale.com/ipn/ipnstate.PeerStatus.
type Format ¶ added in v1.102.0
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 ¶ added in v1.102.0
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 ¶ added in v1.102.0
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/cli/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 ¶ added in v1.102.0
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/cli/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/cli/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 ¶ added in v1.102.0
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/cli/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 ¶ added in v1.102.0
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 ¶ added in v1.102.0
func (v *SchemaVersion) Set(s string) error
Set is called when the user passes the flag as a command-line argument.
func (SchemaVersion) String ¶ added in v1.102.0
func (v SchemaVersion) String() string
String returns the default value which is printed in the CLI help text.