Refactor command-line parsing, pass --network to most commands
This commit is contained in:
parent
16aca610b4
commit
de7aac1f25
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (ctx subCmdCtx) getHosts() ([]bootstrap.Host, error) {
|
func (ctx subCmdCtx) getHosts() ([]bootstrap.Host, error) {
|
||||||
res, err := ctx.daemonRPC.GetHosts(ctx)
|
res, err := newDaemonRPCClient().GetHosts(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("calling GetHosts: %w", err)
|
return nil, fmt.Errorf("calling GetHosts: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -16,28 +16,27 @@ import (
|
|||||||
// restart the service into a configuration that will definitely fail.
|
// restart the service into a configuration that will definitely fail.
|
||||||
|
|
||||||
var subCmdDaemon = subCmd{
|
var subCmdDaemon = subCmd{
|
||||||
name: "daemon",
|
name: "daemon",
|
||||||
descr: "Runs the isle daemon (Default if no sub-command given)",
|
descr: "Runs the isle daemon (Default if no sub-command given)",
|
||||||
|
noNetwork: true,
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
daemonConfigPath := ctx.flags.StringP(
|
||||||
flags := ctx.flagSet(false)
|
|
||||||
|
|
||||||
daemonConfigPath := flags.StringP(
|
|
||||||
"config-path", "c", "",
|
"config-path", "c", "",
|
||||||
"Optional path to a daemon.yml file to load configuration from.",
|
"Optional path to a daemon.yml file to load configuration from.",
|
||||||
)
|
)
|
||||||
|
|
||||||
dumpConfig := flags.Bool(
|
dumpConfig := ctx.flags.Bool(
|
||||||
"dump-config", false,
|
"dump-config", false,
|
||||||
"Write the default configuration file to stdout and exit.",
|
"Write the default configuration file to stdout and exit.",
|
||||||
)
|
)
|
||||||
|
|
||||||
logLevelStr := flags.StringP(
|
logLevelStr := ctx.flags.StringP(
|
||||||
"log-level", "l", "info",
|
"log-level", "l", "info",
|
||||||
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
|
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
|
"isle/daemon/jsonrpc2"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -16,6 +17,14 @@ import (
|
|||||||
|
|
||||||
const daemonHTTPRPCPath = "/rpc/v0.json"
|
const daemonHTTPRPCPath = "/rpc/v0.json"
|
||||||
|
|
||||||
|
func newDaemonRPCClient() daemon.RPC {
|
||||||
|
return daemon.RPCFromClient(
|
||||||
|
jsonrpc2.NewUnixHTTPClient(
|
||||||
|
daemon.HTTPSocketPath(), daemonHTTPRPCPath,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func newHTTPServer(
|
func newHTTPServer(
|
||||||
ctx context.Context, logger *mlog.Logger, daemonInst *daemon.Daemon,
|
ctx context.Context, logger *mlog.Logger, daemonInst *daemon.Daemon,
|
||||||
) (
|
) (
|
||||||
|
@ -32,27 +32,26 @@ func initMCConfigDir(envVars daecommon.EnvVars) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var subCmdGarageMC = subCmd{
|
var subCmdGarageMC = subCmd{
|
||||||
name: "mc",
|
name: "mc",
|
||||||
descr: "Runs the mc (minio-client) binary. The isle garage can be accessed under the `garage` alias",
|
descr: "Runs the mc (minio-client) binary. The isle garage can be accessed under the `garage` alias",
|
||||||
|
passthroughArgs: true,
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
keyID := ctx.flags.StringP(
|
||||||
flags := ctx.flagSet(true)
|
|
||||||
|
|
||||||
keyID := flags.StringP(
|
|
||||||
"key-id", "i", "",
|
"key-id", "i", "",
|
||||||
"Optional key ID to use, defaults to that of the shared global key",
|
"Optional key ID to use, defaults to that of the shared global key",
|
||||||
)
|
)
|
||||||
|
|
||||||
keySecret := flags.StringP(
|
keySecret := ctx.flags.StringP(
|
||||||
"key-secret", "s", "",
|
"key-secret", "s", "",
|
||||||
"Optional key secret to use, defaults to that of the shared global key",
|
"Optional key secret to use, defaults to that of the shared global key",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
clientParams, err := newDaemonRPCClient().GetGarageClientParams(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
||||||
}
|
}
|
||||||
@ -67,9 +66,9 @@ var subCmdGarageMC = subCmd{
|
|||||||
*keySecret = clientParams.GlobalBucketS3APICredentials.Secret
|
*keySecret = clientParams.GlobalBucketS3APICredentials.Secret
|
||||||
}
|
}
|
||||||
|
|
||||||
args := flags.Args()
|
args := ctx.flags.Args()
|
||||||
|
|
||||||
if i := flags.ArgsLenAtDash(); i >= 0 {
|
if i := ctx.flags.ArgsLenAtDash(); i >= 0 {
|
||||||
args = args[i:]
|
args = args[i:]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,8 +116,12 @@ var subCmdGarageCLI = subCmd{
|
|||||||
name: "cli",
|
name: "cli",
|
||||||
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
descr: "Runs the garage binary, automatically configured to point to the garage sub-process of a running isle daemon",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
|
clientParams, err := newDaemonRPCClient().GetGarageClientParams(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
return fmt.Errorf("calling GetGarageClientParams: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -16,26 +16,26 @@ var subCmdHostCreate = subCmd{
|
|||||||
descr: "Creates a new host in the network, writing its new bootstrap.json to stdout",
|
descr: "Creates a new host in the network, writing its new bootstrap.json to stdout",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
|
||||||
hostName hostNameFlag
|
hostName hostNameFlag
|
||||||
ip ipFlag
|
ip ipFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := ctx.flags.VarPF(
|
||||||
&hostName,
|
&hostName,
|
||||||
"hostname", "n",
|
"hostname", "n",
|
||||||
"Name of the host to generate bootstrap.json for",
|
"Name of the host to generate bootstrap.json for",
|
||||||
)
|
)
|
||||||
|
|
||||||
flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.")
|
ctx.flags.VarP(&ip, "ip", "i", "IP of the new host. An available IP will be chosen if none is given.")
|
||||||
|
|
||||||
canCreateHosts := flags.Bool(
|
canCreateHosts := ctx.flags.Bool(
|
||||||
"can-create-hosts",
|
"can-create-hosts",
|
||||||
false,
|
false,
|
||||||
"The new host should have the ability to create hosts too",
|
"The new host should have the ability to create hosts too",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,11 +43,7 @@ var subCmdHostCreate = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
res, err := newDaemonRPCClient().CreateHost(
|
||||||
res network.JoiningBootstrap
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
res, err = ctx.daemonRPC.CreateHost(
|
|
||||||
ctx, hostName.V, network.CreateHostOpts{
|
ctx, hostName.V, network.CreateHostOpts{
|
||||||
IP: ip.V,
|
IP: ip.V,
|
||||||
CanCreateHosts: *canCreateHosts,
|
CanCreateHosts: *canCreateHosts,
|
||||||
@ -65,6 +61,11 @@ 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: func(ctx subCmdCtx) error {
|
||||||
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
|
return 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 fmt.Errorf("calling GetHosts: %w", err)
|
||||||
@ -102,17 +103,17 @@ var subCmdHostRemove = subCmd{
|
|||||||
descr: "Removes a host from the network",
|
descr: "Removes a host from the network",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
|
||||||
hostName hostNameFlag
|
hostName hostNameFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := ctx.flags.VarPF(
|
||||||
&hostName,
|
&hostName,
|
||||||
"hostname", "n",
|
"hostname", "n",
|
||||||
"Name of the host to remove",
|
"Name of the host to remove",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ var subCmdHostRemove = subCmd{
|
|||||||
return errors.New("--hostname is required")
|
return errors.New("--hostname is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.daemonRPC.RemoveHost(ctx, hostName.V); err != nil {
|
if err := newDaemonRPCClient().RemoveHost(ctx, hostName.V); err != nil {
|
||||||
return fmt.Errorf("calling RemoveHost: %w", err)
|
return fmt.Errorf("calling RemoveHost: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +56,8 @@ func main() {
|
|||||||
|
|
||||||
err := subCmdCtx{
|
err := subCmdCtx{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
args: os.Args[1:],
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
args: os.Args[1:],
|
||||||
}.doSubCmd(
|
}.doSubCmd(
|
||||||
subCmdDaemon,
|
subCmdDaemon,
|
||||||
subCmdGarage,
|
subCmdGarage,
|
||||||
|
@ -12,23 +12,21 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
name: "create-cert",
|
name: "create-cert",
|
||||||
descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout",
|
descr: "Creates a signed nebula certificate file for an existing host and writes it to stdout",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
|
||||||
flags = ctx.flagSet(false)
|
|
||||||
hostName hostNameFlag
|
|
||||||
)
|
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
var hostName hostNameFlag
|
||||||
|
hostNameF := ctx.flags.VarPF(
|
||||||
&hostName,
|
&hostName,
|
||||||
"hostname", "n",
|
"hostname", "n",
|
||||||
"Name of the host to generate a certificate for",
|
"Name of the host to generate a certificate for",
|
||||||
)
|
)
|
||||||
|
|
||||||
pubKeyPath := flags.StringP(
|
pubKeyPath := ctx.flags.StringP(
|
||||||
"public-key-path", "p", "",
|
"public-key-path", "p", "",
|
||||||
`Path to PEM file containing public key which will be embedded in the cert.`,
|
`Path to PEM file containing public key which will be embedded in the cert.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +44,7 @@ var subCmdNebulaCreateCert = subCmd{
|
|||||||
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := ctx.daemonRPC.CreateNebulaCertificate(
|
res, err := newDaemonRPCClient().CreateNebulaCertificate(
|
||||||
ctx, hostName.V, hostPub,
|
ctx, hostName.V, hostPub,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,9 +68,8 @@ 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: func(ctx subCmdCtx) error {
|
||||||
|
ctx, err := ctx.withParsedFlags()
|
||||||
flags := ctx.flagSet(false)
|
if err != nil {
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +78,7 @@ var subCmdNebulaShow = subCmd{
|
|||||||
return fmt.Errorf("getting hosts: %w", err)
|
return fmt.Errorf("getting hosts: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caPublicCreds, err := ctx.daemonRPC.GetNebulaCAPublicCredentials(ctx)
|
caPublicCreds, err := newDaemonRPCClient().GetNebulaCAPublicCredentials(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err)
|
return fmt.Errorf("calling GetNebulaCAPublicCredentials: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -8,39 +8,40 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var subCmdNetworkCreate = subCmd{
|
var subCmdNetworkCreate = subCmd{
|
||||||
name: "create",
|
name: "create",
|
||||||
descr: "Create's a new network, with this host being the first host in that network.",
|
descr: "Create's a new network, with this host being the first host in that network.",
|
||||||
|
noNetwork: true,
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
var (
|
||||||
flags = ctx.flagSet(false)
|
|
||||||
ipNet ipNetFlag
|
ipNet ipNetFlag
|
||||||
hostName hostNameFlag
|
hostName hostNameFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
name := flags.StringP(
|
name := ctx.flags.StringP(
|
||||||
"name", "N", "",
|
"name", "N", "",
|
||||||
"Human-readable name to identify the network as.",
|
"Human-readable name to identify the network as.",
|
||||||
)
|
)
|
||||||
|
|
||||||
domain := flags.StringP(
|
domain := ctx.flags.StringP(
|
||||||
"domain", "d", "",
|
"domain", "d", "",
|
||||||
"Domain name that should be used as the root domain in the network.",
|
"Domain name that should be used as the root domain in the network.",
|
||||||
)
|
)
|
||||||
|
|
||||||
ipNetF := flags.VarPF(
|
ipNetF := ctx.flags.VarPF(
|
||||||
&ipNet, "ip-net", "i",
|
&ipNet, "ip-net", "i",
|
||||||
`An IP subnet, in CIDR form, which will be the overall range of`+
|
`An IP subnet, in CIDR form, which will be the overall range of`+
|
||||||
` possible IPs in the network. The first IP in this network`+
|
` possible IPs in the network. The first IP in this network`+
|
||||||
` range will become this first host's IP.`,
|
` range will become this first host's IP.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
hostNameF := flags.VarPF(
|
hostNameF := ctx.flags.VarPF(
|
||||||
&hostName,
|
&hostName,
|
||||||
"hostname", "n",
|
"hostname", "n",
|
||||||
"Name of this host, which will be the first host in the network",
|
"Name of this host, which will be the first host in the network",
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ctx.daemonRPC.CreateNetwork(
|
err = newDaemonRPCClient().CreateNetwork(
|
||||||
ctx, *name, *domain, ipNet.V, hostName.V,
|
ctx, *name, *domain, ipNet.V, hostName.V,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,17 +64,16 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var subCmdNetworkJoin = subCmd{
|
var subCmdNetworkJoin = subCmd{
|
||||||
name: "join",
|
name: "join",
|
||||||
descr: "Joins this host to an existing network",
|
descr: "Joins this host to an existing network",
|
||||||
|
noNetwork: true,
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
var (
|
bootstrapPath := ctx.flags.StringP(
|
||||||
flags = ctx.flagSet(false)
|
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
||||||
bootstrapPath = flags.StringP(
|
|
||||||
"bootstrap-path", "b", "", "Path to a bootstrap.json file.",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := flags.Parse(ctx.args); err != nil {
|
ctx, err := ctx.withParsedFlags()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ var subCmdNetworkJoin = subCmd{
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.daemonRPC.JoinNetwork(ctx, newBootstrap)
|
return newDaemonRPCClient().JoinNetwork(ctx, newBootstrap)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -14,30 +13,8 @@ import (
|
|||||||
|
|
||||||
type flagSet struct {
|
type flagSet struct {
|
||||||
*pflag.FlagSet
|
*pflag.FlagSet
|
||||||
}
|
|
||||||
|
|
||||||
func (fs flagSet) Parse(args []string) error {
|
network string
|
||||||
fs.VisitAll(func(f *pflag.Flag) {
|
|
||||||
if f.Shorthand == "h" {
|
|
||||||
panic(fmt.Sprintf("flag %+v has reserved shorthand `-h`", f))
|
|
||||||
}
|
|
||||||
if f.Name == "help" {
|
|
||||||
panic(fmt.Sprintf("flag %+v has reserved name `--help`", f))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return fs.FlagSet.Parse(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// subCmdCtx contains all information available to a subCmd's do method.
|
|
||||||
type subCmdCtx struct {
|
|
||||||
context.Context
|
|
||||||
|
|
||||||
subCmd subCmd // the subCmd itself
|
|
||||||
args []string // command-line arguments, excluding the subCmd itself.
|
|
||||||
subCmdNames []string // names of subCmds so far, including this one
|
|
||||||
|
|
||||||
logger *mlog.Logger
|
|
||||||
daemonRPC daemon.RPC
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type subCmd struct {
|
type subCmd struct {
|
||||||
@ -47,11 +24,74 @@ type subCmd struct {
|
|||||||
|
|
||||||
// If set then the name will be allowed to be suffixed with this string.
|
// If set then the name will be allowed to be suffixed with this string.
|
||||||
plural string
|
plural string
|
||||||
|
|
||||||
|
// noNetwork, if true, means the call doesn't require a network to be
|
||||||
|
// specified on the command-line if there are more than one networks
|
||||||
|
// configured.
|
||||||
|
noNetwork bool
|
||||||
|
|
||||||
|
// Extra arguments on the command-line will be passed through to some
|
||||||
|
// underlying command.
|
||||||
|
passthroughArgs bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) usagePrefix() string {
|
// subCmdCtx contains all information available to a subCmd's do method.
|
||||||
|
type subCmdCtx struct {
|
||||||
|
context.Context
|
||||||
|
logger *mlog.Logger
|
||||||
|
|
||||||
subCmdNamesStr := strings.Join(ctx.subCmdNames, " ")
|
subCmd subCmd // the subCmd itself
|
||||||
|
args []string // command-line arguments, excluding the subCmd itself.
|
||||||
|
subCmdNames []string // names of subCmds so far, including this one
|
||||||
|
|
||||||
|
flags flagSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSubCmdCtx(
|
||||||
|
ctx context.Context,
|
||||||
|
logger *mlog.Logger,
|
||||||
|
subCmd subCmd,
|
||||||
|
args []string,
|
||||||
|
subCmdNames []string,
|
||||||
|
) subCmdCtx {
|
||||||
|
flags := pflag.NewFlagSet(subCmd.name, pflag.ExitOnError)
|
||||||
|
flags.Usage = func() {
|
||||||
|
var passthroughStr string
|
||||||
|
if subCmd.passthroughArgs {
|
||||||
|
passthroughStr = " [--] [args...]"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(
|
||||||
|
os.Stderr, "%s[-h|--help] [%s flags...]%s\n\n",
|
||||||
|
usagePrefix(subCmdNames), subCmd.name, passthroughStr,
|
||||||
|
)
|
||||||
|
fmt.Fprintf(os.Stderr, "%s FLAGS:\n\n", strings.ToUpper(subCmd.name))
|
||||||
|
fmt.Fprintln(os.Stderr, flags.FlagUsages())
|
||||||
|
|
||||||
|
os.Stderr.Sync()
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flagSet{FlagSet: flags}
|
||||||
|
|
||||||
|
if !subCmd.noNetwork {
|
||||||
|
fs.FlagSet.StringVar(
|
||||||
|
&fs.network, "network", "", "Which network to perform the command against, if more than one is joined. Can be ID, name, or domain",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return subCmdCtx{
|
||||||
|
Context: ctx,
|
||||||
|
logger: logger,
|
||||||
|
subCmd: subCmd,
|
||||||
|
args: args,
|
||||||
|
subCmdNames: subCmdNames,
|
||||||
|
flags: fs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usagePrefix(subCmdNames []string) string {
|
||||||
|
subCmdNamesStr := strings.Join(subCmdNames, " ")
|
||||||
if subCmdNamesStr != "" {
|
if subCmdNamesStr != "" {
|
||||||
subCmdNamesStr += " "
|
subCmdNamesStr += " "
|
||||||
}
|
}
|
||||||
@ -59,26 +99,22 @@ func (ctx subCmdCtx) usagePrefix() string {
|
|||||||
return fmt.Sprintf("\nUSAGE: %s %s", os.Args[0], subCmdNamesStr)
|
return fmt.Sprintf("\nUSAGE: %s %s", os.Args[0], subCmdNamesStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) flagSet(withPassthrough bool) flagSet {
|
func (ctx subCmdCtx) withParsedFlags() (subCmdCtx, error) {
|
||||||
flags := pflag.NewFlagSet(ctx.subCmd.name, pflag.ExitOnError)
|
ctx.flags.VisitAll(func(f *pflag.Flag) {
|
||||||
flags.Usage = func() {
|
if f.Shorthand == "h" {
|
||||||
|
panic(fmt.Sprintf("flag %+v has reserved shorthand `-h`", f))
|
||||||
var passthroughStr string
|
|
||||||
if withPassthrough {
|
|
||||||
passthroughStr = " [--] [args...]"
|
|
||||||
}
|
}
|
||||||
|
if f.Name == "help" {
|
||||||
|
panic(fmt.Sprintf("flag %+v has reserved name `--help`", f))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
fmt.Fprintf(
|
if err := ctx.flags.Parse(ctx.args); err != nil {
|
||||||
os.Stderr, "%s[-h|--help] [%s flags...]%s\n\n",
|
return ctx, err
|
||||||
ctx.usagePrefix(), ctx.subCmd.name, passthroughStr,
|
|
||||||
)
|
|
||||||
fmt.Fprintf(os.Stderr, "%s FLAGS:\n\n", strings.ToUpper(ctx.subCmd.name))
|
|
||||||
fmt.Fprintln(os.Stderr, flags.FlagUsages())
|
|
||||||
|
|
||||||
os.Stderr.Sync()
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
return flagSet{flags}
|
|
||||||
|
ctx.Context = daemon.WithNetwork(ctx.Context, ctx.flags.network)
|
||||||
|
return ctx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
||||||
@ -90,7 +126,7 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
fmt.Fprintf(
|
fmt.Fprintf(
|
||||||
os.Stderr,
|
os.Stderr,
|
||||||
"%s<subCmd> [-h|--help] [sub-command flags...]\n",
|
"%s<subCmd> [-h|--help] [sub-command flags...]\n",
|
||||||
ctx.usagePrefix(),
|
usagePrefix(ctx.subCmdNames),
|
||||||
)
|
)
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "\nSUB-COMMANDS:\n\n")
|
fmt.Fprintf(os.Stderr, "\nSUB-COMMANDS:\n\n")
|
||||||
@ -123,28 +159,21 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subCmdName, args := args[0], args[1:]
|
subCmdName, args := args[0], args[1:]
|
||||||
subCmd, ok := subCmdsMap[subCmdName]
|
|
||||||
|
|
||||||
|
subCmd, ok := subCmdsMap[subCmdName]
|
||||||
if !ok {
|
if !ok {
|
||||||
printUsageExit(subCmdName)
|
printUsageExit(subCmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
daemonRPC := daemon.RPCFromClient(
|
nextSubCmdCtx := newSubCmdCtx(
|
||||||
jsonrpc2.NewUnixHTTPClient(
|
ctx.Context,
|
||||||
daemon.HTTPSocketPath(), daemonHTTPRPCPath,
|
ctx.logger,
|
||||||
),
|
subCmd,
|
||||||
|
args,
|
||||||
|
append(ctx.subCmdNames, subCmdName),
|
||||||
)
|
)
|
||||||
|
|
||||||
err := subCmd.do(subCmdCtx{
|
if err := subCmd.do(nextSubCmdCtx); err != nil {
|
||||||
Context: ctx.Context,
|
|
||||||
subCmd: subCmd,
|
|
||||||
args: args,
|
|
||||||
subCmdNames: append(ctx.subCmdNames, subCmdName),
|
|
||||||
logger: ctx.logger,
|
|
||||||
daemonRPC: daemonRPC,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var subCmdVersion = subCmd{
|
var subCmdVersion = subCmd{
|
||||||
name: "version",
|
name: "version",
|
||||||
descr: "Dumps version and build info to stdout",
|
descr: "Dumps version and build info to stdout",
|
||||||
|
noNetwork: true,
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
|
|
||||||
versionPath := filepath.Join(envAppDirPath, "share/version")
|
versionPath := filepath.Join(envAppDirPath, "share/version")
|
||||||
|
Loading…
Reference in New Issue
Block a user