Output command-line results in either yaml or json, based on a flag

This commit is contained in:
Brian Picciano 2024-11-09 17:39:49 +01:00
parent 06d85ca961
commit 335867644b
4 changed files with 62 additions and 28 deletions

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/network" "isle/daemon/network"
"isle/jsonutil"
"os" "os"
"sort" "sort"
) )
@ -60,15 +59,15 @@ var subCmdHostCreate = subCmd{
var subCmdHostList = subCmd{ var subCmdHostList = subCmd{
name: "list", name: "list",
descr: "Lists all hosts in the network, and their IPs", descr: "Lists all hosts in the network, and their IPs",
do: func(ctx subCmdCtx) error { do: doWithOutput(func(ctx subCmdCtx) (any, error) {
ctx, err := ctx.withParsedFlags() ctx, err := ctx.withParsedFlags()
if err != nil { if err != nil {
return fmt.Errorf("parsing flags: %w", err) return nil, fmt.Errorf("parsing flags: %w", err)
} }
hostsRes, err := ctx.getHosts() hostsRes, err := ctx.getHosts()
if err != nil { if err != nil {
return fmt.Errorf("calling GetHosts: %w", err) return nil, fmt.Errorf("calling GetHosts: %w", err)
} }
type host struct { type host struct {
@ -94,8 +93,8 @@ var subCmdHostList = subCmd{
sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name }) sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name })
return jsonutil.WriteIndented(os.Stdout, hosts) return hosts, nil
}, }),
} }
var subCmdHostRemove = subCmd{ var subCmdHostRemove = subCmd{

View File

@ -3,7 +3,6 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"isle/jsonutil"
"isle/nebula" "isle/nebula"
"os" "os"
) )
@ -67,27 +66,27 @@ var subCmdNebulaCreateCert = subCmd{
var subCmdNebulaShow = subCmd{ var subCmdNebulaShow = subCmd{
name: "show", name: "show",
descr: "Writes nebula network information to stdout in JSON format", descr: "Writes nebula network information to stdout in JSON format",
do: func(ctx subCmdCtx) error { do: doWithOutput(func(ctx subCmdCtx) (any, error) {
ctx, err := ctx.withParsedFlags() ctx, err := ctx.withParsedFlags()
if err != nil { if err != nil {
return fmt.Errorf("parsing flags: %w", err) return nil, fmt.Errorf("parsing flags: %w", err)
} }
hosts, err := ctx.getHosts() hosts, err := ctx.getHosts()
if err != nil { if err != nil {
return fmt.Errorf("getting hosts: %w", err) return nil, fmt.Errorf("getting hosts: %w", err)
} }
caPublicCreds, err := newDaemonRPCClient().GetNebulaCAPublicCredentials(ctx) caPublicCreds, err := newDaemonRPCClient().GetNebulaCAPublicCredentials(ctx)
if err != nil { if err != nil {
return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err) return nil, fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err)
} }
caCert := caPublicCreds.Cert caCert := caPublicCreds.Cert
caCertDetails := caCert.Unwrap().Details caCertDetails := caCert.Unwrap().Details
if len(caCertDetails.Subnets) != 1 { if len(caCertDetails.Subnets) != 1 {
return fmt.Errorf( return nil, fmt.Errorf(
"malformed ca.crt, contains unexpected subnets %#v", "malformed ca.crt, contains unexpected subnets %#v",
caCertDetails.Subnets, caCertDetails.Subnets,
) )
@ -120,12 +119,8 @@ var subCmdNebulaShow = subCmd{
}) })
} }
if err := jsonutil.WriteIndented(os.Stdout, out); err != nil { return out, nil
return fmt.Errorf("encoding to stdout: %w", err) }),
}
return nil
},
} }
var subCmdNebula = subCmd{ var subCmdNebula = subCmd{

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"isle/daemon/network" "isle/daemon/network"
"isle/jsonutil" "isle/jsonutil"
"os"
) )
var subCmdNetworkCreate = subCmd{ var subCmdNetworkCreate = subCmd{
@ -97,19 +96,14 @@ var subCmdNetworkList = subCmd{
name: "list", name: "list",
descr: "Lists all networks which have been joined", descr: "Lists all networks which have been joined",
noNetwork: true, noNetwork: true,
do: func(ctx subCmdCtx) error { do: doWithOutput(func(ctx subCmdCtx) (any, error) {
ctx, err := ctx.withParsedFlags() ctx, err := ctx.withParsedFlags()
if err != nil { if err != nil {
return fmt.Errorf("parsing flags: %w", err) return nil, fmt.Errorf("parsing flags: %w", err)
} }
creationParams, err := newDaemonRPCClient().GetNetworks(ctx) return newDaemonRPCClient().GetNetworks(ctx)
if err != nil { }),
return fmt.Errorf("getting joined networks: %w", err)
}
return jsonutil.WriteIndented(os.Stdout, creationParams)
},
} }
var subCmdNetwork = subCmd{ var subCmdNetwork = subCmd{

View File

@ -2,13 +2,16 @@ package main
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"isle/daemon" "isle/daemon"
"isle/jsonutil"
"os" "os"
"strings" "strings"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"gopkg.in/yaml.v3"
) )
type flagSet struct { type flagSet struct {
@ -191,3 +194,46 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
return nil return nil
} }
type outputFormat string
func (f outputFormat) MarshalText() ([]byte, error) { return []byte(f), nil }
func (f *outputFormat) UnmarshalText(b []byte) error {
*f = outputFormat(strings.ToLower(string(b)))
switch *f {
case "json", "yaml":
return nil
default:
return errors.New("invalid output format")
}
}
// doWithOutput wraps a subCmd's do function so that it will output some value
// to stdout. The value will be formatted according to a command-line argument.
func doWithOutput(fn func(subCmdCtx) (any, error)) func(subCmdCtx) error {
return func(ctx subCmdCtx) error {
type outputFormatFlag = textUnmarshalerFlag[outputFormat, *outputFormat]
outputFormat := outputFormatFlag{"yaml"}
ctx.flags.Var(
&outputFormat,
"format",
"How to format the output value. Can be 'json' or 'yaml'.",
)
res, err := fn(ctx)
if err != nil {
return err
}
switch outputFormat.V {
case "json":
return jsonutil.WriteIndented(os.Stdout, res)
case "yaml":
return yaml.NewEncoder(os.Stdout).Encode(res)
default:
panic(fmt.Sprintf("unexpected outputFormat %q", outputFormat))
}
}
}