version, cmd/tailscale: add version.Meta, tailscale version --json

Add `tailscale version --json` JSON output mode. This will be used
later for a double-opt-in (per node consent like Tailscale SSH +
control config) to let admins do remote upgrades via `tailscale
update` via a c2n call, which would then need to verify the
cmd/tailscale found on disk for running tailscale update corresponds
to the running tailscaled, refusing if anything looks amiss.

Plus JSON output modes are just nice to have, rather than parsing
unstable/fragile/obscure text formats.

Updates #6995
Updates #6907

Change-Id: I7821ab7fbea4612f4b9b7bdc1be1ad1095aca71b
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2023-01-20 20:34:19 -08:00
committed by Brad Fitzpatrick
parent 5ca22a0068
commit ba5aa2c486
5 changed files with 113 additions and 11 deletions

View File

@@ -6,10 +6,13 @@ package cli
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn/ipnstate"
"tailscale.com/version"
)
@@ -20,6 +23,7 @@ var versionCmd = &ffcli.Command{
FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("version")
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
fs.BoolVar(&versionArgs.json, "json", false, "output in JSON format")
return fs
})(),
Exec: runVersion,
@@ -27,23 +31,38 @@ var versionCmd = &ffcli.Command{
var versionArgs struct {
daemon bool // also check local node's daemon version
json bool
}
func runVersion(ctx context.Context, args []string) error {
if len(args) > 0 {
return fmt.Errorf("too many non-flag arguments: %q", args)
}
if !versionArgs.daemon {
var err error
var st *ipnstate.Status
if versionArgs.daemon {
st, err = localClient.StatusWithoutPeers(ctx)
if err != nil {
return err
}
}
if versionArgs.json {
m := version.GetMeta()
if st != nil {
m.DaemonLong = st.Version
}
e := json.NewEncoder(os.Stdout)
e.SetIndent("", "\t")
return e.Encode(m)
}
if st == nil {
outln(version.String())
return nil
}
printf("Client: %s\n", version.String())
st, err := localClient.StatusWithoutPeers(ctx)
if err != nil {
return err
}
printf("Daemon: %s\n", st.Version)
return nil
}