Skip to content

Commit

Permalink
refactor: Move from global state to functions
Browse files Browse the repository at this point in the history
This commit represents a few experiments of features I've used in Cobra

1. Uses cli.GenericFlag to encapsulate parsing and validation of flag
   values at parse time. This removes the burden from the individual
   CLI commands to parse and validate args and options.

2. Add influxid.ID that may be used by any flag that requires an
   Influx ID. influxid.ID parses and validates string value is a valid
   ID, removing this burden from individual commands and ensuring valid
   values before the command actions begins.

3. Binds cli.Flags directly to params structures to directly capture
   the values when parsing flags.

4. Moves from global state to local builder functions for the majority
   of the commands. This allows the commands to bind to flag variables
   reducing the repeated ctx.String(), ctx.Int(), etc

5. Leverages the BeforeFunc to create middleware and inject the CLI and
   API client into commands, saving the repeated boilerplate across
   all of the instantiated commands. This is extensible, so additional
   middleware can be appends using the middleware.WithBeforeFns
  • Loading branch information
stuartcarnie committed Apr 29, 2021
1 parent f3dcbfa commit 571b1de
Show file tree
Hide file tree
Showing 12 changed files with 1,048 additions and 549 deletions.
412 changes: 212 additions & 200 deletions cmd/influx/bucket.go

Large diffs are not rendered by default.

65 changes: 56 additions & 9 deletions cmd/influx/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/influxdata/influx-cli/v2/internal/api"
"github.com/influxdata/influx-cli/v2/internal/config"
"github.com/influxdata/influx-cli/v2/internal/stdio"
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/influxdata/influx-cli/v2/pkg/signals"
"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -185,26 +186,72 @@ func newApiClient(ctx *cli.Context, cli *internal.CLI, injectToken bool) (*api.A
return api.NewAPIClient(apiConfig), nil
}

// standardCtx returns a context that will cancel on SIGINT and SIGTERM.
func standardCtx(ctx *cli.Context) context.Context {
return signals.WithStandardSignals(ctx.Context)
}

var app = cli.App{
Name: "influx",
Usage: "Influx Client",
UsageText: "influx [command]",
Commands: []*cli.Command{
&versionCmd,
&pingCmd,
&setupCmd,
&writeCmd,
&bucketCmd,
newSetupCmd(),
newWriteCmd(),
newBucketCmd(),
},
}

func withCli() cli.BeforeFunc {
return func(ctx *cli.Context) error {
c, err := newCli(ctx)
if err != nil {
return err
}
ctx.App.Metadata["cli"] = c
return nil
}
}

func getCLI(ctx *cli.Context) *internal.CLI {
i, ok := ctx.App.Metadata["cli"].(*internal.CLI)
if !ok {
panic("missing CLI")
}
return i
}

func withApi() cli.BeforeFunc {
makeFn := func(key string, injectToken bool) cli.BeforeFunc {
return func(ctx *cli.Context) error {
c := getCLI(ctx)
apiClient, err := newApiClient(ctx, c, injectToken)
if err != nil {
return err
}
ctx.App.Metadata[key] = apiClient
return nil
}
}
return middleware.WithBeforeFns(makeFn("api", true), makeFn("api-no-token", false))
}

func getAPI(ctx *cli.Context) *api.APIClient {
i, ok := ctx.App.Metadata["api"].(*api.APIClient)
if !ok {
panic("missing APIClient with token")
}
return i
}

func getAPINoToken(ctx *cli.Context) *api.APIClient {
i, ok := ctx.App.Metadata["api-no-token"].(*api.APIClient)
if !ok {
panic("missing APIClient without token")
}
return i
}

func main() {
if err := app.Run(os.Args); err != nil {
ctx := signals.WithStandardSignals(context.Background())
if err := app.RunContext(ctx, os.Args); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
Expand Down
24 changes: 11 additions & 13 deletions cmd/influx/ping.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package main

import "github.com/urfave/cli/v2"
import (
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/urfave/cli/v2"
)

var pingCmd = cli.Command{
Name: "ping",
Usage: "Check the InfluxDB /health endpoint",
Flags: coreFlags,
Name: "ping",
Usage: "Check the InfluxDB /health endpoint",
Before: middleware.WithBeforeFns(withCli(), withApi()),
Flags: coreFlags,
Action: func(ctx *cli.Context) error {
cli, err := newCli(ctx)
if err != nil {
return err
}
client, err := newApiClient(ctx, cli, false)
if err != nil {
return err
}
return cli.Ping(standardCtx(ctx), client.HealthApi)
cli := getCLI(ctx)
client := getAPINoToken(ctx)
return cli.Ping(ctx.Context, client.HealthApi)
},
}
131 changes: 63 additions & 68 deletions cmd/influx/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,68 @@ import (
"github.com/urfave/cli/v2"
)

var setupCmd = cli.Command{
Name: "setup",
Usage: "Setup instance with initial user, org, bucket",
Flags: append(
commonFlagsNoToken,
&cli.StringFlag{
Name: "username",
Usage: "Name of initial user to create",
Aliases: []string{"u"},
func newSetupCmd() *cli.Command {
var params internal.SetupParams
return &cli.Command{
Name: "setup",
Usage: "Setup instance with initial user, org, bucket",
Flags: append(
commonFlagsNoToken,
&cli.StringFlag{
Name: "username",
Usage: "Name of initial user to create",
Aliases: []string{"u"},
Destination: &params.Username,
},
&cli.StringFlag{
Name: "password",
Usage: "Password to set on initial user",
Aliases: []string{"p"},
Destination: &params.Password,
},
&cli.StringFlag{
Name: tokenFlag,
Usage: "Auth token to set on the initial user",
Aliases: []string{"t"},
EnvVars: []string{"INFLUX_TOKEN"},
DefaultText: "auto-generated",
Destination: &params.AuthToken,
},
&cli.StringFlag{
Name: "org",
Usage: "Name of initial organization to create",
Aliases: []string{"o"},
Destination: &params.Org,
},
&cli.StringFlag{
Name: "bucket",
Usage: "Name of initial bucket to create",
Aliases: []string{"b"},
Destination: &params.Bucket,
},
&cli.StringFlag{
Name: "retention",
Usage: "Duration initial bucket will retain data, or 0 for infinite",
Aliases: []string{"r"},
DefaultText: "infinite",
Destination: &params.Retention,
},
&cli.BoolFlag{
Name: "force",
Usage: "Skip confirmation prompt",
Aliases: []string{"f"},
Destination: &params.Force,
},
&cli.StringFlag{
Name: "name",
Usage: "Name to set on CLI config generated for the InfluxDB instance, required if other configs exist",
Aliases: []string{"n"},
Destination: &params.ConfigName,
},
),
Action: func(ctx *cli.Context) error {
client := getAPINoToken(ctx)
return getCLI(ctx).Setup(ctx.Context, client.SetupApi, &params)
},
&cli.StringFlag{
Name: "password",
Usage: "Password to set on initial user",
Aliases: []string{"p"},
},
&cli.StringFlag{
Name: tokenFlag,
Usage: "Auth token to set on the initial user",
Aliases: []string{"t"},
EnvVars: []string{"INFLUX_TOKEN"},
DefaultText: "auto-generated",
},
&cli.StringFlag{
Name: "org",
Usage: "Name of initial organization to create",
Aliases: []string{"o"},
},
&cli.StringFlag{
Name: "bucket",
Usage: "Name of initial bucket to create",
Aliases: []string{"b"},
},
&cli.StringFlag{
Name: "retention",
Usage: "Duration initial bucket will retain data, or 0 for infinite",
Aliases: []string{"r"},
DefaultText: "infinite",
},
&cli.BoolFlag{
Name: "force",
Usage: "Skip confirmation prompt",
Aliases: []string{"f"},
},
&cli.StringFlag{
Name: "name",
Usage: "Name to set on CLI config generated for the InfluxDB instance, required if other configs exist",
Aliases: []string{"n"},
},
),
Action: func(ctx *cli.Context) error {
cli, err := newCli(ctx)
if err != nil {
return err
}
client, err := newApiClient(ctx, cli, false)
if err != nil {
return err
}
return cli.Setup(standardCtx(ctx), client.SetupApi, &internal.SetupParams{
Username: ctx.String("username"),
Password: ctx.String("password"),
AuthToken: ctx.String(tokenFlag),
Org: ctx.String("org"),
Bucket: ctx.String("bucket"),
Retention: ctx.String("retention"),
Force: ctx.Bool("force"),
ConfigName: ctx.String("name"),
})
},
}
}
Loading

0 comments on commit 571b1de

Please sign in to comment.