refactor: split flat internal/ module into modules-per-cmd (#64)
This commit is contained in:
@ -1,35 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func withBucketsClient() cli.BeforeFunc {
|
|
||||||
return middleware.WithBeforeFns(
|
|
||||||
withCli(),
|
|
||||||
withApi(true),
|
|
||||||
func(ctx *cli.Context) error {
|
|
||||||
client := getAPI(ctx)
|
|
||||||
ctx.App.Metadata["bucketsClient"] = internal.BucketsClients{
|
|
||||||
BucketApi: client.BucketsApi,
|
|
||||||
OrgApi: client.OrganizationsApi,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getBucketsClient(ctx *cli.Context) internal.BucketsClients {
|
|
||||||
i, ok := ctx.App.Metadata["bucketsClient"].(internal.BucketsClients)
|
|
||||||
if !ok {
|
|
||||||
panic("missing buckets client")
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBucketCmd() *cli.Command {
|
func newBucketCmd() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "bucket",
|
Name: "bucket",
|
||||||
@ -45,13 +22,16 @@ func newBucketCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newBucketCreateCmd() *cli.Command {
|
func newBucketCreateCmd() *cli.Command {
|
||||||
params := internal.BucketsCreateParams{
|
params := bucket.BucketsCreateParams{
|
||||||
SchemaType: api.SCHEMATYPE_IMPLICIT,
|
SchemaType: api.SCHEMATYPE_IMPLICIT,
|
||||||
}
|
}
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "create",
|
Name: "create",
|
||||||
Usage: "Create bucket",
|
Usage: "Create bucket",
|
||||||
Before: withBucketsClient(),
|
Before: middleware.WithBeforeFns(
|
||||||
|
withCli(),
|
||||||
|
withApi(true),
|
||||||
|
),
|
||||||
Flags: append(
|
Flags: append(
|
||||||
commonFlags,
|
commonFlags,
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
@ -102,14 +82,19 @@ func newBucketCreateCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
clients := getBucketsClient(ctx)
|
api := getAPI(ctx)
|
||||||
return getCLI(ctx).BucketsCreate(ctx.Context, &clients, ¶ms)
|
client := bucket.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
BucketsApi: api.BucketsApi,
|
||||||
|
OrganizationsApi: api.OrganizationsApi,
|
||||||
|
}
|
||||||
|
return client.Create(ctx.Context, ¶ms)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBucketDeleteCmd() *cli.Command {
|
func newBucketDeleteCmd() *cli.Command {
|
||||||
var params internal.BucketsDeleteParams
|
var params bucket.BucketsDeleteParams
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "delete",
|
Name: "delete",
|
||||||
Usage: "Delete bucket",
|
Usage: "Delete bucket",
|
||||||
@ -143,13 +128,19 @@ func newBucketDeleteCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
return getCLI(ctx).BucketsDelete(ctx.Context, getAPI(ctx).BucketsApi, ¶ms)
|
api := getAPI(ctx)
|
||||||
|
client := bucket.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
BucketsApi: api.BucketsApi,
|
||||||
|
OrganizationsApi: api.OrganizationsApi,
|
||||||
|
}
|
||||||
|
return client.Delete(ctx.Context, ¶ms)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBucketListCmd() *cli.Command {
|
func newBucketListCmd() *cli.Command {
|
||||||
var params internal.BucketsListParams
|
var params bucket.BucketsListParams
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "List buckets",
|
Usage: "List buckets",
|
||||||
@ -184,13 +175,19 @@ func newBucketListCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
return getCLI(ctx).BucketsList(ctx.Context, getAPI(ctx).BucketsApi, ¶ms)
|
api := getAPI(ctx)
|
||||||
|
client := bucket.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
BucketsApi: api.BucketsApi,
|
||||||
|
OrganizationsApi: api.OrganizationsApi,
|
||||||
|
}
|
||||||
|
return client.List(ctx.Context, ¶ms)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBucketUpdateCmd() *cli.Command {
|
func newBucketUpdateCmd() *cli.Command {
|
||||||
var params internal.BucketsUpdateParams
|
var params bucket.BucketsUpdateParams
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Update bucket",
|
Usage: "Update bucket",
|
||||||
@ -231,7 +228,13 @@ func newBucketUpdateCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
return getCLI(ctx).BucketsUpdate(ctx.Context, getAPI(ctx).BucketsApi, ¶ms)
|
api := getAPI(ctx)
|
||||||
|
client := bucket.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
BucketsApi: api.BucketsApi,
|
||||||
|
OrganizationsApi: api.OrganizationsApi,
|
||||||
|
}
|
||||||
|
return client.Update(ctx.Context, ¶ms)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
||||||
@ -13,12 +13,11 @@ func withBucketSchemaClient() cli.BeforeFunc {
|
|||||||
withCli(),
|
withCli(),
|
||||||
withApi(true),
|
withApi(true),
|
||||||
func(ctx *cli.Context) error {
|
func(ctx *cli.Context) error {
|
||||||
c := getCLI(ctx)
|
|
||||||
client := getAPI(ctx)
|
client := getAPI(ctx)
|
||||||
ctx.App.Metadata["measurement_schema"] = bucket_schema.Client{
|
ctx.App.Metadata["measurement_schema"] = bucket_schema.Client{
|
||||||
BucketApi: client.BucketsApi,
|
BucketsApi: client.BucketsApi,
|
||||||
BucketSchemasApi: client.BucketSchemasApi,
|
BucketSchemasApi: client.BucketSchemasApi,
|
||||||
CLI: c,
|
CLI: getCLI(ctx),
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -46,7 +45,7 @@ func newBucketSchemaCmd() *cli.Command {
|
|||||||
|
|
||||||
func newBucketSchemaCreateCmd() *cli.Command {
|
func newBucketSchemaCreateCmd() *cli.Command {
|
||||||
var params struct {
|
var params struct {
|
||||||
internal.OrgBucketParams
|
cmd.OrgBucketParams
|
||||||
Name string
|
Name string
|
||||||
ColumnsFile string
|
ColumnsFile string
|
||||||
ColumnsFormat bucket_schema.ColumnsFormat
|
ColumnsFormat bucket_schema.ColumnsFormat
|
||||||
@ -100,7 +99,7 @@ func newBucketSchemaCreateCmd() *cli.Command {
|
|||||||
|
|
||||||
func newBucketSchemaUpdateCmd() *cli.Command {
|
func newBucketSchemaUpdateCmd() *cli.Command {
|
||||||
var params struct {
|
var params struct {
|
||||||
internal.OrgBucketParams
|
cmd.OrgBucketParams
|
||||||
ID influxid.ID
|
ID influxid.ID
|
||||||
Name string
|
Name string
|
||||||
ColumnsFile string
|
ColumnsFile string
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/config"
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/stdio"
|
"github.com/influxdata/influx-cli/v2/internal/stdio"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
@ -114,26 +114,26 @@ var commonFlags = append(commonFlagsNoToken, &commonTokenFlag)
|
|||||||
|
|
||||||
// newCli builds a CLI core that reads from stdin, writes to stdout/stderr, manages a local config store,
|
// newCli builds a CLI core that reads from stdin, writes to stdout/stderr, manages a local config store,
|
||||||
// and optionally tracks a trace ID specified over the CLI.
|
// and optionally tracks a trace ID specified over the CLI.
|
||||||
func newCli(ctx *cli.Context) (*internal.CLI, error) {
|
func newCli(ctx *cli.Context) (cmd.CLI, error) {
|
||||||
configPath := ctx.String(configPathFlag)
|
configPath := ctx.String(configPathFlag)
|
||||||
var err error
|
var err error
|
||||||
if configPath == "" {
|
if configPath == "" {
|
||||||
configPath, err = config.DefaultPath()
|
configPath, err = config.DefaultPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return cmd.CLI{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configSvc := config.NewLocalConfigService(configPath)
|
configSvc := config.NewLocalConfigService(configPath)
|
||||||
var activeConfig config.Config
|
var activeConfig config.Config
|
||||||
if ctx.IsSet(configNameFlag) {
|
if ctx.IsSet(configNameFlag) {
|
||||||
if activeConfig, err = configSvc.SwitchActive(ctx.String(configNameFlag)); err != nil {
|
if activeConfig, err = configSvc.SwitchActive(ctx.String(configNameFlag)); err != nil {
|
||||||
return nil, err
|
return cmd.CLI{}, err
|
||||||
}
|
}
|
||||||
} else if activeConfig, err = configSvc.Active(); err != nil {
|
} else if activeConfig, err = configSvc.Active(); err != nil {
|
||||||
return nil, err
|
return cmd.CLI{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &internal.CLI{
|
return cmd.CLI{
|
||||||
StdIO: stdio.TerminalStdio,
|
StdIO: stdio.TerminalStdio,
|
||||||
PrintAsJSON: ctx.Bool(printJsonFlag),
|
PrintAsJSON: ctx.Bool(printJsonFlag),
|
||||||
HideTableHeaders: ctx.Bool(hideHeadersFlag),
|
HideTableHeaders: ctx.Bool(hideHeadersFlag),
|
||||||
@ -144,8 +144,8 @@ func newCli(ctx *cli.Context) (*internal.CLI, error) {
|
|||||||
|
|
||||||
// newApiClient returns an API client configured to communicate with a remote InfluxDB instance over HTTP.
|
// newApiClient returns an API client configured to communicate with a remote InfluxDB instance over HTTP.
|
||||||
// Client parameters are pulled from the CLI context.
|
// Client parameters are pulled from the CLI context.
|
||||||
func newApiClient(ctx *cli.Context, cli *internal.CLI, injectToken bool) (*api.APIClient, error) {
|
func newApiClient(ctx *cli.Context, configSvc config.Service, injectToken bool) (*api.APIClient, error) {
|
||||||
cfg, err := cli.ConfigService.Active()
|
cfg, err := configSvc.Active()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -213,8 +213,8 @@ func withCli() cli.BeforeFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCLI(ctx *cli.Context) *internal.CLI {
|
func getCLI(ctx *cli.Context) cmd.CLI {
|
||||||
i, ok := ctx.App.Metadata["cli"].(*internal.CLI)
|
i, ok := ctx.App.Metadata["cli"].(cmd.CLI)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("missing CLI")
|
panic("missing CLI")
|
||||||
}
|
}
|
||||||
@ -229,7 +229,7 @@ func withApi(injectToken bool) cli.BeforeFunc {
|
|||||||
|
|
||||||
makeFn := func(ctx *cli.Context) error {
|
makeFn := func(ctx *cli.Context) error {
|
||||||
c := getCLI(ctx)
|
c := getCLI(ctx)
|
||||||
apiClient, err := newApiClient(ctx, c, injectToken)
|
apiClient, err := newApiClient(ctx, c.ConfigService, injectToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getOrgBucketFlags(c *internal.OrgBucketParams) []cli.Flag {
|
func getOrgBucketFlags(c *cmd.OrgBucketParams) []cli.Flag {
|
||||||
return []cli.Flag{
|
return []cli.Flag{
|
||||||
&cli.GenericFlag{
|
&cli.GenericFlag{
|
||||||
Name: "bucket-id",
|
Name: "bucket-id",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/ping"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -12,7 +13,11 @@ func newPingCmd() *cli.Command {
|
|||||||
Before: middleware.WithBeforeFns(withCli(), withApi(false)),
|
Before: middleware.WithBeforeFns(withCli(), withApi(false)),
|
||||||
Flags: coreFlags,
|
Flags: coreFlags,
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
return getCLI(ctx).Ping(ctx.Context, getAPINoToken(ctx).HealthApi)
|
client := ping.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
HealthApi: getAPINoToken(ctx).HealthApi,
|
||||||
|
}
|
||||||
|
return client.Ping(ctx.Context)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/setup"
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newSetupCmd() *cli.Command {
|
func newSetupCmd() *cli.Command {
|
||||||
var params internal.SetupParams
|
var params setup.Params
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "setup",
|
Name: "setup",
|
||||||
Usage: "Setup instance with initial user, org, bucket",
|
Usage: "Setup instance with initial user, org, bucket",
|
||||||
@ -67,7 +67,11 @@ func newSetupCmd() *cli.Command {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
return getCLI(ctx).Setup(ctx.Context, getAPINoToken(ctx).SetupApi, ¶ms)
|
client := setup.Client{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
SetupApi: getAPINoToken(ctx).SetupApi,
|
||||||
|
}
|
||||||
|
return client.Setup(ctx.Context, ¶ms)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/batcher"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/linereader"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/throttler"
|
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -18,8 +15,8 @@ import (
|
|||||||
type writeParams struct {
|
type writeParams struct {
|
||||||
Files cli.StringSlice
|
Files cli.StringSlice
|
||||||
URLs cli.StringSlice
|
URLs cli.StringSlice
|
||||||
Format linereader.InputFormat
|
Format write.InputFormat
|
||||||
Compression linereader.InputCompression
|
Compression write.InputCompression
|
||||||
Encoding string
|
Encoding string
|
||||||
|
|
||||||
// CSV-specific options.
|
// CSV-specific options.
|
||||||
@ -31,13 +28,13 @@ type writeParams struct {
|
|||||||
|
|
||||||
ErrorsFile string
|
ErrorsFile string
|
||||||
MaxLineLength int
|
MaxLineLength int
|
||||||
RateLimit throttler.BytesPerSec
|
RateLimit write.BytesPerSec
|
||||||
|
|
||||||
internal.WriteParams
|
write.Params
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *writeParams) makeLineReader(args []string, errorOut io.Writer) *linereader.MultiInputLineReader {
|
func (p *writeParams) makeLineReader(args []string, errorOut io.Writer) *write.MultiInputLineReader {
|
||||||
return &linereader.MultiInputLineReader{
|
return &write.MultiInputLineReader{
|
||||||
StdIn: os.Stdin,
|
StdIn: os.Stdin,
|
||||||
HttpClient: http.DefaultClient,
|
HttpClient: http.DefaultClient,
|
||||||
ErrorOut: errorOut,
|
ErrorOut: errorOut,
|
||||||
@ -183,7 +180,7 @@ func (p *writeParams) Flags() []cli.Flag {
|
|||||||
|
|
||||||
func newWriteCmd() *cli.Command {
|
func newWriteCmd() *cli.Command {
|
||||||
params := writeParams{
|
params := writeParams{
|
||||||
WriteParams: internal.WriteParams{
|
Params: write.Params{
|
||||||
Precision: api.WRITEPRECISION_NS,
|
Precision: api.WRITEPRECISION_NS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -200,19 +197,19 @@ func newWriteCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer func() { _ = errorFile.Close() }()
|
defer func() { _ = errorFile.Close() }()
|
||||||
|
|
||||||
client := getAPI(ctx)
|
client := &write.Client{
|
||||||
writeClients := &internal.WriteClients{
|
CLI: getCLI(ctx),
|
||||||
Client: client.WriteApi,
|
WriteApi: getAPI(ctx).WriteApi,
|
||||||
Reader: params.makeLineReader(ctx.Args().Slice(), errorFile),
|
LineReader: params.makeLineReader(ctx.Args().Slice(), errorFile),
|
||||||
Throttler: throttler.NewThrottler(params.RateLimit),
|
RateLimiter: write.NewThrottler(params.RateLimit),
|
||||||
Writer: &batcher.BufferBatcher{
|
BatchWriter: &write.BufferBatcher{
|
||||||
MaxFlushBytes: batcher.DefaultMaxBytes,
|
MaxFlushBytes: write.DefaultMaxBytes,
|
||||||
MaxFlushInterval: batcher.DefaultInterval,
|
MaxFlushInterval: write.DefaultInterval,
|
||||||
MaxLineLength: params.MaxLineLength,
|
MaxLineLength: params.MaxLineLength,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCLI(ctx).Write(ctx.Context, writeClients, ¶ms.WriteParams)
|
return client.Write(ctx.Context, ¶ms.Params)
|
||||||
},
|
},
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
newWriteDryRun(),
|
newWriteDryRun(),
|
||||||
@ -222,7 +219,7 @@ func newWriteCmd() *cli.Command {
|
|||||||
|
|
||||||
func newWriteDryRun() *cli.Command {
|
func newWriteDryRun() *cli.Command {
|
||||||
params := writeParams{
|
params := writeParams{
|
||||||
WriteParams: internal.WriteParams{
|
Params: write.Params{
|
||||||
Precision: api.WRITEPRECISION_NS,
|
Precision: api.WRITEPRECISION_NS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -240,7 +237,11 @@ func newWriteDryRun() *cli.Command {
|
|||||||
}
|
}
|
||||||
defer func() { _ = errorFile.Close() }()
|
defer func() { _ = errorFile.Close() }()
|
||||||
|
|
||||||
return getCLI(ctx).WriteDryRun(ctx.Context, params.makeLineReader(ctx.Args().Slice(), errorFile))
|
client := write.DryRunClient{
|
||||||
|
CLI: getCLI(ctx),
|
||||||
|
LineReader: params.makeLineReader(ctx.Args().Slice(), errorFile),
|
||||||
|
}
|
||||||
|
return client.WriteDryRun(ctx.Context)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,306 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/duration"
|
|
||||||
)
|
|
||||||
|
|
||||||
const InfiniteRetention = 0
|
|
||||||
|
|
||||||
type BucketsClients struct {
|
|
||||||
OrgApi api.OrganizationsApi
|
|
||||||
BucketApi api.BucketsApi
|
|
||||||
}
|
|
||||||
|
|
||||||
type BucketsCreateParams struct {
|
|
||||||
OrgID string
|
|
||||||
OrgName string
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
Retention string
|
|
||||||
ShardGroupDuration string
|
|
||||||
SchemaType api.SchemaType
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrMustSpecifyOrg = errors.New("must specify org ID or org name")
|
|
||||||
ErrMustSpecifyOrgDeleteByName = errors.New("must specify org ID or org name when deleting bucket by name")
|
|
||||||
ErrMustSpecifyBucket = errors.New("must specify bucket ID or bucket name")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *CLI) BucketsCreate(ctx context.Context, clients *BucketsClients, params *BucketsCreateParams) error {
|
|
||||||
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
|
||||||
return ErrMustSpecifyOrg
|
|
||||||
}
|
|
||||||
|
|
||||||
rp, err := duration.RawDurationToTimeDuration(params.Retention)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBody := api.PostBucketRequest{
|
|
||||||
OrgID: params.OrgID,
|
|
||||||
Name: params.Name,
|
|
||||||
RetentionRules: []api.RetentionRule{},
|
|
||||||
SchemaType: ¶ms.SchemaType,
|
|
||||||
}
|
|
||||||
if params.Description != "" {
|
|
||||||
reqBody.Description = ¶ms.Description
|
|
||||||
}
|
|
||||||
// Only append a retention rule if the user wants to explicitly set
|
|
||||||
// a parameter on the rule.
|
|
||||||
//
|
|
||||||
// This is for backwards-compatibility with older versions of the API,
|
|
||||||
// which didn't support setting shard-group durations and used an empty
|
|
||||||
// array of rules to represent infinite retention.
|
|
||||||
if rp > 0 || sgd > 0 {
|
|
||||||
rule := api.NewRetentionRuleWithDefaults()
|
|
||||||
if rp > 0 {
|
|
||||||
rule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second))
|
|
||||||
}
|
|
||||||
if sgd > 0 {
|
|
||||||
rule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second))
|
|
||||||
}
|
|
||||||
reqBody.RetentionRules = append(reqBody.RetentionRules, *rule)
|
|
||||||
}
|
|
||||||
if reqBody.OrgID == "" {
|
|
||||||
name := params.OrgName
|
|
||||||
if name == "" {
|
|
||||||
name = c.ActiveConfig.Org
|
|
||||||
}
|
|
||||||
resp, err := clients.OrgApi.GetOrgs(ctx).Org(name).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to lookup ID of org %q: %w", name, err)
|
|
||||||
}
|
|
||||||
orgs := resp.GetOrgs()
|
|
||||||
if len(orgs) == 0 {
|
|
||||||
return fmt.Errorf("no organization found with name %q", name)
|
|
||||||
}
|
|
||||||
reqBody.OrgID = orgs[0].GetId()
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket, err := clients.BucketApi.PostBuckets(ctx).PostBucketRequest(reqBody).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create bucket: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.printBuckets(bucketPrintOptions{bucket: &bucket})
|
|
||||||
}
|
|
||||||
|
|
||||||
type BucketsListParams struct {
|
|
||||||
OrgID string
|
|
||||||
OrgName string
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) BucketsList(ctx context.Context, client api.BucketsApi, params *BucketsListParams) error {
|
|
||||||
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
|
||||||
return ErrMustSpecifyOrg
|
|
||||||
}
|
|
||||||
|
|
||||||
req := client.GetBuckets(ctx)
|
|
||||||
if params.OrgID != "" {
|
|
||||||
req = req.OrgID(params.OrgID)
|
|
||||||
}
|
|
||||||
if params.OrgName != "" {
|
|
||||||
req = req.Org(params.OrgName)
|
|
||||||
}
|
|
||||||
if params.OrgID == "" && params.OrgName == "" {
|
|
||||||
req = req.Org(c.ActiveConfig.Org)
|
|
||||||
}
|
|
||||||
if params.Name != "" {
|
|
||||||
req = req.Name(params.Name)
|
|
||||||
}
|
|
||||||
if params.ID != "" {
|
|
||||||
req = req.Id(params.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
buckets, err := req.Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to list buckets: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
printOpts := bucketPrintOptions{}
|
|
||||||
if buckets.Buckets != nil {
|
|
||||||
printOpts.buckets = *buckets.Buckets
|
|
||||||
}
|
|
||||||
return c.printBuckets(printOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
type BucketsUpdateParams struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
Retention string
|
|
||||||
ShardGroupDuration string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) BucketsUpdate(ctx context.Context, client api.BucketsApi, params *BucketsUpdateParams) error {
|
|
||||||
reqBody := api.PatchBucketRequest{}
|
|
||||||
if params.Name != "" {
|
|
||||||
reqBody.SetName(params.Name)
|
|
||||||
}
|
|
||||||
if params.Description != "" {
|
|
||||||
reqBody.SetDescription(params.Description)
|
|
||||||
}
|
|
||||||
if params.Retention != "" || params.ShardGroupDuration != "" {
|
|
||||||
patchRule := api.NewPatchRetentionRuleWithDefaults()
|
|
||||||
if params.Retention != "" {
|
|
||||||
rp, err := duration.RawDurationToTimeDuration(params.Retention)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
patchRule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second))
|
|
||||||
}
|
|
||||||
if params.ShardGroupDuration != "" {
|
|
||||||
sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
patchRule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second))
|
|
||||||
}
|
|
||||||
reqBody.SetRetentionRules([]api.PatchRetentionRule{*patchRule})
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket, err := client.PatchBucketsID(ctx, params.ID).PatchBucketRequest(reqBody).Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update bucket %q: %w", params.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.printBuckets(bucketPrintOptions{bucket: &bucket})
|
|
||||||
}
|
|
||||||
|
|
||||||
type BucketsDeleteParams struct {
|
|
||||||
ID string
|
|
||||||
Name string
|
|
||||||
OrgID string
|
|
||||||
OrgName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) BucketsDelete(ctx context.Context, client api.BucketsApi, params *BucketsDeleteParams) error {
|
|
||||||
if params.ID == "" && params.Name == "" {
|
|
||||||
return ErrMustSpecifyBucket
|
|
||||||
}
|
|
||||||
|
|
||||||
var bucket api.Bucket
|
|
||||||
var getReq api.ApiGetBucketsRequest
|
|
||||||
if params.ID != "" {
|
|
||||||
getReq = client.GetBuckets(ctx).Id(params.ID)
|
|
||||||
} else {
|
|
||||||
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
|
||||||
return ErrMustSpecifyOrgDeleteByName
|
|
||||||
}
|
|
||||||
getReq = client.GetBuckets(ctx)
|
|
||||||
getReq = getReq.Name(params.Name)
|
|
||||||
if params.OrgID != "" {
|
|
||||||
getReq = getReq.OrgID(params.OrgID)
|
|
||||||
}
|
|
||||||
if params.OrgName != "" {
|
|
||||||
getReq = getReq.Org(params.OrgName)
|
|
||||||
}
|
|
||||||
if params.OrgID == "" && params.OrgName == "" {
|
|
||||||
getReq = getReq.Org(c.ActiveConfig.Org)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displayId := params.ID
|
|
||||||
if displayId == "" {
|
|
||||||
displayId = params.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := getReq.Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to find bucket %q: %w", displayId, err)
|
|
||||||
}
|
|
||||||
buckets := resp.GetBuckets()
|
|
||||||
if len(buckets) == 0 {
|
|
||||||
return fmt.Errorf("bucket %q not found", displayId)
|
|
||||||
}
|
|
||||||
bucket = buckets[0]
|
|
||||||
|
|
||||||
if err := client.DeleteBucketsID(ctx, bucket.GetId()).Execute(); err != nil {
|
|
||||||
return fmt.Errorf("failed to delete bucket %q: %w", displayId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.printBuckets(bucketPrintOptions{bucket: &bucket, deleted: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
type bucketPrintOptions struct {
|
|
||||||
deleted bool
|
|
||||||
bucket *api.Bucket
|
|
||||||
buckets []api.Bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CLI) printBuckets(options bucketPrintOptions) error {
|
|
||||||
if c.PrintAsJSON {
|
|
||||||
var v interface{}
|
|
||||||
if options.bucket != nil {
|
|
||||||
v = options.bucket
|
|
||||||
} else {
|
|
||||||
v = options.buckets
|
|
||||||
}
|
|
||||||
return c.PrintJSON(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
headers := []string{"ID", "Name", "Retention", "Shard group duration", "Organization ID", "Schema Type"}
|
|
||||||
if options.deleted {
|
|
||||||
headers = append(headers, "Deleted")
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.bucket != nil {
|
|
||||||
options.buckets = append(options.buckets, *options.bucket)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []map[string]interface{}
|
|
||||||
for _, bkt := range options.buckets {
|
|
||||||
var rpDuration time.Duration // zero value implies infinite retention policy
|
|
||||||
var sgDuration time.Duration // zero value implies the server should pick a value
|
|
||||||
|
|
||||||
if rules := bkt.GetRetentionRules(); len(rules) > 0 {
|
|
||||||
rpDuration = time.Duration(rules[0].GetEverySeconds()) * time.Second
|
|
||||||
sgDuration = time.Duration(rules[0].GetShardGroupDurationSeconds()) * time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
rp := rpDuration.String()
|
|
||||||
if rpDuration == InfiniteRetention {
|
|
||||||
rp = "infinite"
|
|
||||||
}
|
|
||||||
sgDur := sgDuration.String()
|
|
||||||
// ShardGroupDuration will be zero if listing buckets from InfluxDB Cloud.
|
|
||||||
// Show something more useful here in that case.
|
|
||||||
if sgDuration == 0 {
|
|
||||||
sgDur = "n/a"
|
|
||||||
}
|
|
||||||
|
|
||||||
schemaType := bkt.GetSchemaType()
|
|
||||||
if schemaType == "" {
|
|
||||||
// schemaType will be empty when querying OSS.
|
|
||||||
schemaType = api.SCHEMATYPE_IMPLICIT
|
|
||||||
}
|
|
||||||
|
|
||||||
row := map[string]interface{}{
|
|
||||||
"ID": bkt.GetId(),
|
|
||||||
"Name": bkt.GetName(),
|
|
||||||
"Retention": rp,
|
|
||||||
"Shard group duration": sgDur,
|
|
||||||
"Organization ID": bkt.GetOrgID(),
|
|
||||||
"Schema Type": schemaType,
|
|
||||||
}
|
|
||||||
if options.deleted {
|
|
||||||
row["Deleted"] = true
|
|
||||||
}
|
|
||||||
rows = append(rows, row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.PrintTable(headers, rows...)
|
|
||||||
}
|
|
||||||
@ -1,865 +0,0 @@
|
|||||||
package internal_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/config"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
tmock "github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBucketsCreate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
configOrgName string
|
|
||||||
params internal.BucketsCreateParams
|
|
||||||
registerOrgExpectations func(*testing.T, *mock.MockOrganizationsApi)
|
|
||||||
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
|
||||||
expectedStdoutPattern string
|
|
||||||
expectedInErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "minimal",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
OrgID: "123",
|
|
||||||
Name: "my-bucket",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Empty(t, body.RetentionRules)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
RetentionRules: nil,
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "456\\s+my-bucket\\s+infinite\\s+n/a\\s+123",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "fully specified",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
OrgID: "123",
|
|
||||||
Name: "my-bucket",
|
|
||||||
Description: "my cool bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
ShardGroupDuration: "1h",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Equal(t, "my cool bucket", *body.Description) &&
|
|
||||||
assert.Len(t, body.RetentionRules, 1) &&
|
|
||||||
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
|
||||||
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "456\\s+my-bucket\\s+24h0m0s\\s+1h0m0s\\s+123",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "retention but not shard-group duration",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
OrgID: "123",
|
|
||||||
Name: "my-bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Len(t, body.RetentionRules, 1) &&
|
|
||||||
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
|
||||||
assert.Nil(t, body.RetentionRules[0].ShardGroupDurationSeconds)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
RetentionRules: []api.RetentionRule{{EverySeconds: 86400}},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create bucket with explicit schema",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
OrgID: "123",
|
|
||||||
Name: "my-bucket",
|
|
||||||
SchemaType: api.SCHEMATYPE_EXPLICIT,
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Empty(t, body.RetentionRules) &&
|
|
||||||
assert.Equal(t, api.SCHEMATYPE_EXPLICIT, *body.SchemaType)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
SchemaType: api.SCHEMATYPE_EXPLICIT.Ptr(),
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: `456\s+my-bucket\s+infinite\s+n/a\s+123\s+explicit`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "look up org by name",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
OrgName: "my-org",
|
|
||||||
Name: "my-bucket",
|
|
||||||
Description: "my cool bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
ShardGroupDuration: "1h",
|
|
||||||
},
|
|
||||||
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
|
||||||
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
|
||||||
orgApi.EXPECT().GetOrgsExecute(tmock.MatchedBy(func(in api.ApiGetOrgsRequest) bool {
|
|
||||||
return assert.Equal(t, "my-org", *in.GetOrg())
|
|
||||||
})).
|
|
||||||
Return(api.Organizations{
|
|
||||||
Orgs: &[]api.Organization{{Id: api.PtrString("123")}},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Equal(t, "my cool bucket", *body.Description) &&
|
|
||||||
assert.Len(t, body.RetentionRules, 1) &&
|
|
||||||
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
|
||||||
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "456\\s+my-bucket\\s+24h0m0s\\s+1h0m0s\\s+123",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "look up org by name from config",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
Description: "my cool bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
ShardGroupDuration: "1h",
|
|
||||||
},
|
|
||||||
configOrgName: "my-org",
|
|
||||||
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
|
||||||
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
|
||||||
orgApi.EXPECT().GetOrgsExecute(tmock.MatchedBy(func(in api.ApiGetOrgsRequest) bool {
|
|
||||||
return assert.Equal(t, "my-org", *in.GetOrg())
|
|
||||||
})).
|
|
||||||
Return(api.Organizations{
|
|
||||||
Orgs: &[]api.Organization{{Id: api.PtrString("123")}},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().
|
|
||||||
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
|
||||||
body := in.GetPostBucketRequest()
|
|
||||||
return assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "123", body.OrgID) &&
|
|
||||||
assert.Equal(t, "my-bucket", body.Name) &&
|
|
||||||
assert.Equal(t, "my cool bucket", *body.Description) &&
|
|
||||||
assert.Len(t, body.RetentionRules, 1) &&
|
|
||||||
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
|
||||||
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
|
||||||
})).
|
|
||||||
Return(api.Bucket{
|
|
||||||
Id: api.PtrString("456"),
|
|
||||||
OrgID: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "456\\s+my-bucket\\s+24h0m0s\\s+1h0m0s\\s+123",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no org specified",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
Description: "my cool bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
ShardGroupDuration: "1h",
|
|
||||||
},
|
|
||||||
expectedInErr: "must specify org ID or org name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no such org",
|
|
||||||
params: internal.BucketsCreateParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgName: "fake-org",
|
|
||||||
Description: "my cool bucket",
|
|
||||||
Retention: "24h",
|
|
||||||
ShardGroupDuration: "1h",
|
|
||||||
},
|
|
||||||
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
|
||||||
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
|
||||||
orgApi.EXPECT().GetOrgsExecute(gomock.Any()).Return(api.Organizations{}, nil)
|
|
||||||
},
|
|
||||||
expectedInErr: "no organization found",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
|
|
||||||
orgApi := mock.NewMockOrganizationsApi(ctrl)
|
|
||||||
if tc.registerOrgExpectations != nil {
|
|
||||||
tc.registerOrgExpectations(t, orgApi)
|
|
||||||
}
|
|
||||||
bucketApi := mock.NewMockBucketsApi(ctrl)
|
|
||||||
if tc.registerBucketExpectations != nil {
|
|
||||||
tc.registerBucketExpectations(t, bucketApi)
|
|
||||||
}
|
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio}
|
|
||||||
clients := internal.BucketsClients{
|
|
||||||
OrgApi: orgApi,
|
|
||||||
BucketApi: bucketApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cli.BucketsCreate(context.Background(), &clients, &tc.params)
|
|
||||||
if tc.expectedInErr != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), tc.expectedInErr)
|
|
||||||
require.Empty(t, writtenBytes.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
outLines := strings.Split(writtenBytes.String(), "\n")
|
|
||||||
if outLines[len(outLines)-1] == "" {
|
|
||||||
outLines = outLines[:len(outLines)-1]
|
|
||||||
}
|
|
||||||
require.Equal(t, 2, len(outLines))
|
|
||||||
require.Regexp(t, "ID\\s+Name\\s+Retention\\s+Shard group duration\\s+Organization ID", outLines[0])
|
|
||||||
require.Regexp(t, tc.expectedStdoutPattern, outLines[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBucketsList(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
configOrgName string
|
|
||||||
params internal.BucketsListParams
|
|
||||||
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
|
||||||
expectedStdoutPatterns []string
|
|
||||||
expectedInErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "by ID",
|
|
||||||
params: internal.BucketsListParams{
|
|
||||||
ID: "123",
|
|
||||||
},
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "123", *in.GetId()) &&
|
|
||||||
assert.Equal(t, "my-default-org", *in.GetOrg()) &&
|
|
||||||
assert.Nil(t, in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPatterns: []string{
|
|
||||||
"123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "by name",
|
|
||||||
params: internal.BucketsListParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
},
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "my-bucket", *in.GetName()) &&
|
|
||||||
assert.Equal(t, "my-default-org", *in.GetOrg()) &&
|
|
||||||
assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPatterns: []string{
|
|
||||||
"123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "override org by ID",
|
|
||||||
params: internal.BucketsListParams{
|
|
||||||
OrgID: "456",
|
|
||||||
},
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "456", *in.GetOrgID()) &&
|
|
||||||
assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Nil(t, in.GetOrg()) &&
|
|
||||||
assert.Nil(t, in.GetName())
|
|
||||||
})).Return(api.Buckets{}, nil)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "override org by name",
|
|
||||||
params: internal.BucketsListParams{
|
|
||||||
OrgName: "my-org",
|
|
||||||
},
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "my-org", *in.GetOrg()) &&
|
|
||||||
assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Nil(t, in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Id: api.PtrString("999"),
|
|
||||||
Name: "bucket2",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 0, ShardGroupDurationSeconds: api.PtrInt64(60)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPatterns: []string{
|
|
||||||
"123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456",
|
|
||||||
"999\\s+bucket2\\s+infinite\\s+1m0s\\s+456",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list multiple bucket schema types",
|
|
||||||
params: internal.BucketsListParams{
|
|
||||||
OrgName: "my-org",
|
|
||||||
},
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "my-org", *in.GetOrg()) &&
|
|
||||||
assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Nil(t, in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("001"),
|
|
||||||
Name: "omit-schema-type",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Id: api.PtrString("002"),
|
|
||||||
Name: "implicit-schema-type",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
SchemaType: api.SCHEMATYPE_IMPLICIT.Ptr(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Id: api.PtrString("003"),
|
|
||||||
Name: "explicit-schema-type",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
SchemaType: api.SCHEMATYPE_EXPLICIT.Ptr(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPatterns: []string{
|
|
||||||
`001\s+omit-schema-type\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
|
||||||
`002\s+implicit-schema-type\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
|
||||||
`003\s+explicit-schema-type\s+1h0m0s\s+n/a\s+456\s+explicit`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no org specified",
|
|
||||||
expectedInErr: "must specify org ID or org name",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
|
||||||
bytesWritten := bytes.Buffer{}
|
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio}
|
|
||||||
|
|
||||||
client := mock.NewMockBucketsApi(ctrl)
|
|
||||||
if tc.registerBucketExpectations != nil {
|
|
||||||
tc.registerBucketExpectations(t, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cli.BucketsList(context.Background(), client, &tc.params)
|
|
||||||
if tc.expectedInErr != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), tc.expectedInErr)
|
|
||||||
require.Empty(t, bytesWritten.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
outLines := strings.Split(bytesWritten.String(), "\n")
|
|
||||||
if outLines[len(outLines)-1] == "" {
|
|
||||||
outLines = outLines[:len(outLines)-1]
|
|
||||||
}
|
|
||||||
require.Equal(t, len(tc.expectedStdoutPatterns)+1, len(outLines))
|
|
||||||
require.Regexp(t, "ID\\s+Name\\s+Retention\\s+Shard group duration\\s+Organization ID", outLines[0])
|
|
||||||
for i, pattern := range tc.expectedStdoutPatterns {
|
|
||||||
require.Regexp(t, pattern, outLines[i+1])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBucketsUpdate(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
params internal.BucketsUpdateParams
|
|
||||||
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
|
||||||
expectedStdoutPattern string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "name",
|
|
||||||
params: internal.BucketsUpdateParams{
|
|
||||||
ID: "123",
|
|
||||||
Name: "cold-storage",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
|
||||||
body := in.GetPatchBucketRequest()
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID()) &&
|
|
||||||
assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "cold-storage", body.GetName()) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Empty(t, body.GetRetentionRules())
|
|
||||||
})).Return(api.Bucket{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "cold-storage",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+cold-storage\\s+infinite\\s+n/a\\s+456",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
params: internal.BucketsUpdateParams{
|
|
||||||
ID: "123",
|
|
||||||
Description: "a very useful description",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
|
||||||
body := in.GetPatchBucketRequest()
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID()) &&
|
|
||||||
assert.NotNil(t, body) &&
|
|
||||||
assert.Equal(t, "a very useful description", body.GetDescription()) &&
|
|
||||||
assert.Nil(t, body.Name) &&
|
|
||||||
assert.Empty(t, body.GetRetentionRules())
|
|
||||||
})).Return(api.Bucket{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
Description: api.PtrString("a very useful description"),
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+infinite\\s+n/a\\s+456",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "retention",
|
|
||||||
params: internal.BucketsUpdateParams{
|
|
||||||
ID: "123",
|
|
||||||
Retention: "3w",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
|
||||||
body := in.GetPatchBucketRequest()
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID()) &&
|
|
||||||
assert.NotNil(t, body) &&
|
|
||||||
assert.Nil(t, body.Name) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Len(t, body.GetRetentionRules(), 1) &&
|
|
||||||
assert.Nil(t, body.GetRetentionRules()[0].ShardGroupDurationSeconds) &&
|
|
||||||
assert.Equal(t, int64(3*7*24*3600), *body.GetRetentionRules()[0].EverySeconds)
|
|
||||||
})).Return(api.Bucket{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: int64(3 * 7 * 24 * 3600)},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+504h0m0s\\s+n/a\\s+456",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "shard-group duration",
|
|
||||||
params: internal.BucketsUpdateParams{
|
|
||||||
ID: "123",
|
|
||||||
ShardGroupDuration: "10h30m",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
|
||||||
body := in.GetPatchBucketRequest()
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID()) &&
|
|
||||||
assert.NotNil(t, body) &&
|
|
||||||
assert.Nil(t, body.Name) &&
|
|
||||||
assert.Nil(t, body.Description) &&
|
|
||||||
assert.Len(t, body.GetRetentionRules(), 1) &&
|
|
||||||
assert.Nil(t, body.GetRetentionRules()[0].EverySeconds) &&
|
|
||||||
assert.Equal(t, int64(10*3600+30*60), *body.GetRetentionRules()[0].ShardGroupDurationSeconds)
|
|
||||||
})).Return(api.Bucket{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{ShardGroupDurationSeconds: api.PtrInt64(10*3600 + 30*60)},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+infinite\\s+10h30m0s\\s+456",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
cli := internal.CLI{StdIO: stdio}
|
|
||||||
client := mock.NewMockBucketsApi(ctrl)
|
|
||||||
if tc.registerBucketExpectations != nil {
|
|
||||||
tc.registerBucketExpectations(t, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cli.BucketsUpdate(context.Background(), client, &tc.params)
|
|
||||||
require.NoError(t, err)
|
|
||||||
outLines := strings.Split(writtenBytes.String(), "\n")
|
|
||||||
if outLines[len(outLines)-1] == "" {
|
|
||||||
outLines = outLines[:len(outLines)-1]
|
|
||||||
}
|
|
||||||
require.Equal(t, 2, len(outLines))
|
|
||||||
require.Regexp(t, "ID\\s+Name\\s+Retention\\s+Shard group duration\\s+Organization ID", outLines[0])
|
|
||||||
require.Regexp(t, tc.expectedStdoutPattern, outLines[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBucketsDelete(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
name string
|
|
||||||
configOrgName string
|
|
||||||
params internal.BucketsDeleteParams
|
|
||||||
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
|
||||||
expectedStdoutPattern string
|
|
||||||
expectedInErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "by ID",
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
ID: "123",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Equal(t, "123", *in.GetId()) &&
|
|
||||||
assert.Nil(t, in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID()) &&
|
|
||||||
assert.Nil(t, in.GetOrg())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID())
|
|
||||||
})).Return(nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456\\s+implicit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "by name and org ID",
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: "456",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
|
||||||
assert.Equal(t, "456", *in.GetOrgID()) &&
|
|
||||||
assert.Nil(t, in.GetOrg())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID())
|
|
||||||
})).Return(nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456\\s+implicit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "by name and org name",
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgName: "my-org",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID()) &&
|
|
||||||
assert.Equal(t, "my-org", *in.GetOrg())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID())
|
|
||||||
})).Return(nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456\\s+implicit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "by name in default org",
|
|
||||||
configOrgName: "my-default-org",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return assert.Nil(t, in.GetId()) &&
|
|
||||||
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
|
||||||
assert.Nil(t, in.GetOrgID()) &&
|
|
||||||
assert.Equal(t, "my-default-org", *in.GetOrg())
|
|
||||||
})).Return(api.Buckets{
|
|
||||||
Buckets: &[]api.Bucket{
|
|
||||||
{
|
|
||||||
Id: api.PtrString("123"),
|
|
||||||
Name: "my-bucket",
|
|
||||||
OrgID: api.PtrString("456"),
|
|
||||||
RetentionRules: []api.RetentionRule{
|
|
||||||
{EverySeconds: 3600},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
|
||||||
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
|
||||||
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
|
||||||
return assert.Equal(t, "123", in.GetBucketID())
|
|
||||||
})).Return(nil)
|
|
||||||
},
|
|
||||||
expectedStdoutPattern: "123\\s+my-bucket\\s+1h0m0s\\s+n/a\\s+456\\s+implicit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "by name without org",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
Name: "my-bucket",
|
|
||||||
},
|
|
||||||
expectedInErr: "must specify org ID or org name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no such bucket",
|
|
||||||
params: internal.BucketsDeleteParams{
|
|
||||||
ID: "123",
|
|
||||||
},
|
|
||||||
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
|
||||||
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
|
||||||
bucketsApi.EXPECT().GetBucketsExecute(gomock.Any()).Return(api.Buckets{}, nil)
|
|
||||||
},
|
|
||||||
expectedInErr: "not found",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio}
|
|
||||||
client := mock.NewMockBucketsApi(ctrl)
|
|
||||||
if tc.registerBucketExpectations != nil {
|
|
||||||
tc.registerBucketExpectations(t, client)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := cli.BucketsDelete(context.Background(), client, &tc.params)
|
|
||||||
if tc.expectedInErr != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Contains(t, err.Error(), tc.expectedInErr)
|
|
||||||
require.Empty(t, writtenBytes.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
require.NoError(t, err)
|
|
||||||
outLines := strings.Split(writtenBytes.String(), "\n")
|
|
||||||
if outLines[len(outLines)-1] == "" {
|
|
||||||
outLines = outLines[:len(outLines)-1]
|
|
||||||
}
|
|
||||||
require.Regexp(t, `ID\s+Name\s+Retention\s+Shard group duration\s+Organization ID\s+Schema Type\s+Deleted`, outLines[0])
|
|
||||||
require.Regexp(t, tc.expectedStdoutPattern+"\\s+true", outLines[1])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
93
internal/cmd/bucket/client.go
Normal file
93
internal/cmd/bucket/client.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package bucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const InfiniteRetention = 0
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrMustSpecifyOrg = errors.New("must specify org ID or org name")
|
||||||
|
ErrMustSpecifyOrgDeleteByName = errors.New("must specify org ID or org name when deleting bucket by name")
|
||||||
|
ErrMustSpecifyBucket = errors.New("must specify bucket ID or bucket name")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
cmd.CLI
|
||||||
|
api.OrganizationsApi
|
||||||
|
api.BucketsApi
|
||||||
|
}
|
||||||
|
|
||||||
|
type bucketPrintOptions struct {
|
||||||
|
deleted bool
|
||||||
|
bucket *api.Bucket
|
||||||
|
buckets []api.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) printBuckets(options bucketPrintOptions) error {
|
||||||
|
if c.PrintAsJSON {
|
||||||
|
var v interface{}
|
||||||
|
if options.bucket != nil {
|
||||||
|
v = options.bucket
|
||||||
|
} else {
|
||||||
|
v = options.buckets
|
||||||
|
}
|
||||||
|
return c.PrintJSON(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"ID", "Name", "Retention", "Shard group duration", "Organization ID", "Schema Type"}
|
||||||
|
if options.deleted {
|
||||||
|
headers = append(headers, "Deleted")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.bucket != nil {
|
||||||
|
options.buckets = append(options.buckets, *options.bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rows []map[string]interface{}
|
||||||
|
for _, bkt := range options.buckets {
|
||||||
|
var rpDuration time.Duration // zero value implies infinite retention policy
|
||||||
|
var sgDuration time.Duration // zero value implies the server should pick a value
|
||||||
|
|
||||||
|
if rules := bkt.GetRetentionRules(); len(rules) > 0 {
|
||||||
|
rpDuration = time.Duration(rules[0].GetEverySeconds()) * time.Second
|
||||||
|
sgDuration = time.Duration(rules[0].GetShardGroupDurationSeconds()) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := rpDuration.String()
|
||||||
|
if rpDuration == InfiniteRetention {
|
||||||
|
rp = "infinite"
|
||||||
|
}
|
||||||
|
sgDur := sgDuration.String()
|
||||||
|
// ShardGroupDuration will be zero if listing buckets from InfluxDB Cloud.
|
||||||
|
// Show something more useful here in that case.
|
||||||
|
if sgDuration == 0 {
|
||||||
|
sgDur = "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
schemaType := bkt.GetSchemaType()
|
||||||
|
if schemaType == "" {
|
||||||
|
// schemaType will be empty when querying OSS.
|
||||||
|
schemaType = api.SCHEMATYPE_IMPLICIT
|
||||||
|
}
|
||||||
|
|
||||||
|
row := map[string]interface{}{
|
||||||
|
"ID": bkt.GetId(),
|
||||||
|
"Name": bkt.GetName(),
|
||||||
|
"Retention": rp,
|
||||||
|
"Shard group duration": sgDur,
|
||||||
|
"Organization ID": bkt.GetOrgID(),
|
||||||
|
"Schema Type": schemaType,
|
||||||
|
}
|
||||||
|
if options.deleted {
|
||||||
|
row["Deleted"] = true
|
||||||
|
}
|
||||||
|
rows = append(rows, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PrintTable(headers, rows...)
|
||||||
|
}
|
||||||
83
internal/cmd/bucket/create.go
Normal file
83
internal/cmd/bucket/create.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package bucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/duration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketsCreateParams struct {
|
||||||
|
OrgID string
|
||||||
|
OrgName string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Retention string
|
||||||
|
ShardGroupDuration string
|
||||||
|
SchemaType api.SchemaType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Create(ctx context.Context, params *BucketsCreateParams) error {
|
||||||
|
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||||
|
return ErrMustSpecifyOrg
|
||||||
|
}
|
||||||
|
|
||||||
|
rp, err := duration.RawDurationToTimeDuration(params.Retention)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := api.PostBucketRequest{
|
||||||
|
OrgID: params.OrgID,
|
||||||
|
Name: params.Name,
|
||||||
|
RetentionRules: []api.RetentionRule{},
|
||||||
|
SchemaType: ¶ms.SchemaType,
|
||||||
|
}
|
||||||
|
if params.Description != "" {
|
||||||
|
reqBody.Description = ¶ms.Description
|
||||||
|
}
|
||||||
|
// Only append a retention rule if the user wants to explicitly set
|
||||||
|
// a parameter on the rule.
|
||||||
|
//
|
||||||
|
// This is for backwards-compatibility with older versions of the API,
|
||||||
|
// which didn't support setting shard-group durations and used an empty
|
||||||
|
// array of rules to represent infinite retention.
|
||||||
|
if rp > 0 || sgd > 0 {
|
||||||
|
rule := api.NewRetentionRuleWithDefaults()
|
||||||
|
if rp > 0 {
|
||||||
|
rule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second))
|
||||||
|
}
|
||||||
|
if sgd > 0 {
|
||||||
|
rule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second))
|
||||||
|
}
|
||||||
|
reqBody.RetentionRules = append(reqBody.RetentionRules, *rule)
|
||||||
|
}
|
||||||
|
if reqBody.OrgID == "" {
|
||||||
|
name := params.OrgName
|
||||||
|
if name == "" {
|
||||||
|
name = c.ActiveConfig.Org
|
||||||
|
}
|
||||||
|
resp, err := c.GetOrgs(ctx).Org(name).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup ID of org %q: %w", name, err)
|
||||||
|
}
|
||||||
|
orgs := resp.GetOrgs()
|
||||||
|
if len(orgs) == 0 {
|
||||||
|
return fmt.Errorf("no organization found with name %q", name)
|
||||||
|
}
|
||||||
|
reqBody.OrgID = orgs[0].GetId()
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := c.PostBuckets(ctx).PostBucketRequest(reqBody).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create bucket: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.printBuckets(bucketPrintOptions{bucket: &bucket})
|
||||||
|
}
|
||||||
297
internal/cmd/bucket/create_test.go
Normal file
297
internal/cmd/bucket/create_test.go
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
package bucket_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketsCreate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
configOrgName string
|
||||||
|
params bucket.BucketsCreateParams
|
||||||
|
registerOrgExpectations func(*testing.T, *mock.MockOrganizationsApi)
|
||||||
|
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
||||||
|
expectedStdoutPattern string
|
||||||
|
expectedInErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "minimal",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
OrgID: "123",
|
||||||
|
Name: "my-bucket",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Empty(t, body.RetentionRules)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
RetentionRules: nil,
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `456\s+my-bucket\s+infinite\s+n/a\s+123`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fully specified",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
OrgID: "123",
|
||||||
|
Name: "my-bucket",
|
||||||
|
Description: "my cool bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
ShardGroupDuration: "1h",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Equal(t, "my cool bucket", *body.Description) &&
|
||||||
|
assert.Len(t, body.RetentionRules, 1) &&
|
||||||
|
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
||||||
|
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `456\s+my-bucket\s+24h0m0s\s+1h0m0s\s+123`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "retention but not shard-group duration",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
OrgID: "123",
|
||||||
|
Name: "my-bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Len(t, body.RetentionRules, 1) &&
|
||||||
|
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
||||||
|
assert.Nil(t, body.RetentionRules[0].ShardGroupDurationSeconds)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
RetentionRules: []api.RetentionRule{{EverySeconds: 86400}},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create bucket with explicit schema",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
OrgID: "123",
|
||||||
|
Name: "my-bucket",
|
||||||
|
SchemaType: api.SCHEMATYPE_EXPLICIT,
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Empty(t, body.RetentionRules) &&
|
||||||
|
assert.Equal(t, api.SCHEMATYPE_EXPLICIT, *body.SchemaType)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
SchemaType: api.SCHEMATYPE_EXPLICIT.Ptr(),
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `456\s+my-bucket\s+infinite\s+n/a\s+123\s+explicit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "look up org by name",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
OrgName: "my-org",
|
||||||
|
Name: "my-bucket",
|
||||||
|
Description: "my cool bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
ShardGroupDuration: "1h",
|
||||||
|
},
|
||||||
|
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
||||||
|
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
||||||
|
orgApi.EXPECT().GetOrgsExecute(tmock.MatchedBy(func(in api.ApiGetOrgsRequest) bool {
|
||||||
|
return assert.Equal(t, "my-org", *in.GetOrg())
|
||||||
|
})).
|
||||||
|
Return(api.Organizations{
|
||||||
|
Orgs: &[]api.Organization{{Id: api.PtrString("123")}},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Equal(t, "my cool bucket", *body.Description) &&
|
||||||
|
assert.Len(t, body.RetentionRules, 1) &&
|
||||||
|
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
||||||
|
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `456\s+my-bucket\s+24h0m0s\s+1h0m0s\s+123`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "look up org by name from config",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
Description: "my cool bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
ShardGroupDuration: "1h",
|
||||||
|
},
|
||||||
|
configOrgName: "my-org",
|
||||||
|
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
||||||
|
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
||||||
|
orgApi.EXPECT().GetOrgsExecute(tmock.MatchedBy(func(in api.ApiGetOrgsRequest) bool {
|
||||||
|
return assert.Equal(t, "my-org", *in.GetOrg())
|
||||||
|
})).
|
||||||
|
Return(api.Organizations{
|
||||||
|
Orgs: &[]api.Organization{{Id: api.PtrString("123")}},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PostBuckets(gomock.Any()).Return(api.ApiPostBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().
|
||||||
|
PostBucketsExecute(tmock.MatchedBy(func(in api.ApiPostBucketsRequest) bool {
|
||||||
|
body := in.GetPostBucketRequest()
|
||||||
|
return assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "123", body.OrgID) &&
|
||||||
|
assert.Equal(t, "my-bucket", body.Name) &&
|
||||||
|
assert.Equal(t, "my cool bucket", *body.Description) &&
|
||||||
|
assert.Len(t, body.RetentionRules, 1) &&
|
||||||
|
assert.Equal(t, int64(86400), body.RetentionRules[0].EverySeconds) &&
|
||||||
|
assert.Equal(t, int64(3600), *body.RetentionRules[0].ShardGroupDurationSeconds)
|
||||||
|
})).
|
||||||
|
Return(api.Bucket{
|
||||||
|
Id: api.PtrString("456"),
|
||||||
|
OrgID: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 86400, ShardGroupDurationSeconds: api.PtrInt64(3600)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `456\s+my-bucket\s+24h0m0s\s+1h0m0s\s+123`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no org specified",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
Description: "my cool bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
ShardGroupDuration: "1h",
|
||||||
|
},
|
||||||
|
expectedInErr: "must specify org ID or org name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no such org",
|
||||||
|
params: bucket.BucketsCreateParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgName: "fake-org",
|
||||||
|
Description: "my cool bucket",
|
||||||
|
Retention: "24h",
|
||||||
|
ShardGroupDuration: "1h",
|
||||||
|
},
|
||||||
|
registerOrgExpectations: func(t *testing.T, orgApi *mock.MockOrganizationsApi) {
|
||||||
|
orgApi.EXPECT().GetOrgs(gomock.Any()).Return(api.ApiGetOrgsRequest{ApiService: orgApi})
|
||||||
|
orgApi.EXPECT().GetOrgsExecute(gomock.Any()).Return(api.Organizations{}, nil)
|
||||||
|
},
|
||||||
|
expectedInErr: "no organization found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
|
||||||
|
orgApi := mock.NewMockOrganizationsApi(ctrl)
|
||||||
|
if tc.registerOrgExpectations != nil {
|
||||||
|
tc.registerOrgExpectations(t, orgApi)
|
||||||
|
}
|
||||||
|
bucketApi := mock.NewMockBucketsApi(ctrl)
|
||||||
|
if tc.registerBucketExpectations != nil {
|
||||||
|
tc.registerBucketExpectations(t, bucketApi)
|
||||||
|
}
|
||||||
|
cli := bucket.Client{
|
||||||
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio},
|
||||||
|
OrganizationsApi: orgApi,
|
||||||
|
BucketsApi: bucketApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cli.Create(context.Background(), &tc.params)
|
||||||
|
if tc.expectedInErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedInErr)
|
||||||
|
require.Empty(t, writtenBytes.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, []string{
|
||||||
|
`ID\s+Name\s+Retention\s+Shard group duration\s+Organization ID\s+Schema Type`,
|
||||||
|
tc.expectedStdoutPattern,
|
||||||
|
}, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
63
internal/cmd/bucket/delete.go
Normal file
63
internal/cmd/bucket/delete.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package bucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketsDeleteParams struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
OrgID string
|
||||||
|
OrgName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Delete(ctx context.Context, params *BucketsDeleteParams) error {
|
||||||
|
if params.ID == "" && params.Name == "" {
|
||||||
|
return ErrMustSpecifyBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucket api.Bucket
|
||||||
|
var getReq api.ApiGetBucketsRequest
|
||||||
|
if params.ID != "" {
|
||||||
|
getReq = c.GetBuckets(ctx).Id(params.ID)
|
||||||
|
} else {
|
||||||
|
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||||
|
return ErrMustSpecifyOrgDeleteByName
|
||||||
|
}
|
||||||
|
getReq = c.GetBuckets(ctx)
|
||||||
|
getReq = getReq.Name(params.Name)
|
||||||
|
if params.OrgID != "" {
|
||||||
|
getReq = getReq.OrgID(params.OrgID)
|
||||||
|
}
|
||||||
|
if params.OrgName != "" {
|
||||||
|
getReq = getReq.Org(params.OrgName)
|
||||||
|
}
|
||||||
|
if params.OrgID == "" && params.OrgName == "" {
|
||||||
|
getReq = getReq.Org(c.ActiveConfig.Org)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayId := params.ID
|
||||||
|
if displayId == "" {
|
||||||
|
displayId = params.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := getReq.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to find bucket %q: %w", displayId, err)
|
||||||
|
}
|
||||||
|
buckets := resp.GetBuckets()
|
||||||
|
if len(buckets) == 0 {
|
||||||
|
return fmt.Errorf("bucket %q not found", displayId)
|
||||||
|
}
|
||||||
|
bucket = buckets[0]
|
||||||
|
|
||||||
|
if err := c.DeleteBucketsID(ctx, bucket.GetId()).Execute(); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete bucket %q: %w", displayId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.printBuckets(bucketPrintOptions{bucket: &bucket, deleted: true})
|
||||||
|
}
|
||||||
222
internal/cmd/bucket/delete_test.go
Normal file
222
internal/cmd/bucket/delete_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package bucket_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketsDelete(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
configOrgName string
|
||||||
|
params bucket.BucketsDeleteParams
|
||||||
|
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
||||||
|
expectedStdoutPattern string
|
||||||
|
expectedInErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "by ID",
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
ID: "123",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "123", *in.GetId()) &&
|
||||||
|
assert.Nil(t, in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID()) &&
|
||||||
|
assert.Nil(t, in.GetOrg())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID())
|
||||||
|
})).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "by name and org ID",
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: "456",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
||||||
|
assert.Equal(t, "456", *in.GetOrgID()) &&
|
||||||
|
assert.Nil(t, in.GetOrg())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID())
|
||||||
|
})).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "by name and org name",
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgName: "my-org",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID()) &&
|
||||||
|
assert.Equal(t, "my-org", *in.GetOrg())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID())
|
||||||
|
})).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "by name in default org",
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Equal(t, "my-bucket", *in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID()) &&
|
||||||
|
assert.Equal(t, "my-default-org", *in.GetOrg())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiDeleteBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().DeleteBucketsIDExecute(tmock.MatchedBy(func(in api.ApiDeleteBucketsIDRequest) bool {
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID())
|
||||||
|
})).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "by name without org",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
},
|
||||||
|
expectedInErr: "must specify org ID or org name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no such bucket",
|
||||||
|
params: bucket.BucketsDeleteParams{
|
||||||
|
ID: "123",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(gomock.Any()).Return(api.Buckets{}, nil)
|
||||||
|
},
|
||||||
|
expectedInErr: "not found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
client := mock.NewMockBucketsApi(ctrl)
|
||||||
|
if tc.registerBucketExpectations != nil {
|
||||||
|
tc.registerBucketExpectations(t, client)
|
||||||
|
}
|
||||||
|
cli := bucket.Client{
|
||||||
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio},
|
||||||
|
BucketsApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cli.Delete(context.Background(), &tc.params)
|
||||||
|
if tc.expectedInErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedInErr)
|
||||||
|
require.Empty(t, writtenBytes.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, []string{
|
||||||
|
`ID\s+Name\s+Retention\s+Shard group duration\s+Organization ID\s+Schema Type\s+Deleted`,
|
||||||
|
tc.expectedStdoutPattern+`\s+true`,
|
||||||
|
}, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
47
internal/cmd/bucket/list.go
Normal file
47
internal/cmd/bucket/list.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package bucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketsListParams struct {
|
||||||
|
OrgID string
|
||||||
|
OrgName string
|
||||||
|
Name string
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) List(ctx context.Context, params *BucketsListParams) error {
|
||||||
|
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||||
|
return ErrMustSpecifyOrg
|
||||||
|
}
|
||||||
|
|
||||||
|
req := c.GetBuckets(ctx)
|
||||||
|
if params.OrgID != "" {
|
||||||
|
req = req.OrgID(params.OrgID)
|
||||||
|
}
|
||||||
|
if params.OrgName != "" {
|
||||||
|
req = req.Org(params.OrgName)
|
||||||
|
}
|
||||||
|
if params.OrgID == "" && params.OrgName == "" {
|
||||||
|
req = req.Org(c.ActiveConfig.Org)
|
||||||
|
}
|
||||||
|
if params.Name != "" {
|
||||||
|
req = req.Name(params.Name)
|
||||||
|
}
|
||||||
|
if params.ID != "" {
|
||||||
|
req = req.Id(params.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets, err := req.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list buckets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
printOpts := bucketPrintOptions{}
|
||||||
|
if buckets.Buckets != nil {
|
||||||
|
printOpts.buckets = *buckets.Buckets
|
||||||
|
}
|
||||||
|
return c.printBuckets(printOpts)
|
||||||
|
}
|
||||||
235
internal/cmd/bucket/list_test.go
Normal file
235
internal/cmd/bucket/list_test.go
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
package bucket_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketsList(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
configOrgName string
|
||||||
|
params bucket.BucketsListParams
|
||||||
|
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
||||||
|
expectedStdoutPatterns []string
|
||||||
|
expectedInErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "by ID",
|
||||||
|
params: bucket.BucketsListParams{
|
||||||
|
ID: "123",
|
||||||
|
},
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "123", *in.GetId()) &&
|
||||||
|
assert.Equal(t, "my-default-org", *in.GetOrg()) &&
|
||||||
|
assert.Nil(t, in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPatterns: []string{
|
||||||
|
`123\s+my-bucket\s+1h0m0s\s+n/a\s+456`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "by name",
|
||||||
|
params: bucket.BucketsListParams{
|
||||||
|
Name: "my-bucket",
|
||||||
|
},
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "my-bucket", *in.GetName()) &&
|
||||||
|
assert.Equal(t, "my-default-org", *in.GetOrg()) &&
|
||||||
|
assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPatterns: []string{
|
||||||
|
`123\s+my-bucket\s+1h0m0s\s+n/a\s+456`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "override org by ID",
|
||||||
|
params: bucket.BucketsListParams{
|
||||||
|
OrgID: "456",
|
||||||
|
},
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "456", *in.GetOrgID()) &&
|
||||||
|
assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Nil(t, in.GetOrg()) &&
|
||||||
|
assert.Nil(t, in.GetName())
|
||||||
|
})).Return(api.Buckets{}, nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "override org by name",
|
||||||
|
params: bucket.BucketsListParams{
|
||||||
|
OrgName: "my-org",
|
||||||
|
},
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "my-org", *in.GetOrg()) &&
|
||||||
|
assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Nil(t, in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: api.PtrString("999"),
|
||||||
|
Name: "bucket2",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 0, ShardGroupDurationSeconds: api.PtrInt64(60)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPatterns: []string{
|
||||||
|
`123\s+my-bucket\s+1h0m0s\s+n/a\s+456`,
|
||||||
|
`999\s+bucket2\s+infinite\s+1m0s\s+456`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list multiple bucket schema types",
|
||||||
|
params: bucket.BucketsListParams{
|
||||||
|
OrgName: "my-org",
|
||||||
|
},
|
||||||
|
configOrgName: "my-default-org",
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().GetBuckets(gomock.Any()).Return(api.ApiGetBucketsRequest{ApiService: bucketsApi})
|
||||||
|
bucketsApi.EXPECT().GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return assert.Equal(t, "my-org", *in.GetOrg()) &&
|
||||||
|
assert.Nil(t, in.GetId()) &&
|
||||||
|
assert.Nil(t, in.GetName()) &&
|
||||||
|
assert.Nil(t, in.GetOrgID())
|
||||||
|
})).Return(api.Buckets{
|
||||||
|
Buckets: &[]api.Bucket{
|
||||||
|
{
|
||||||
|
Id: api.PtrString("001"),
|
||||||
|
Name: "omit-schema-type",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: api.PtrString("002"),
|
||||||
|
Name: "implicit-schema-type",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
SchemaType: api.SCHEMATYPE_IMPLICIT.Ptr(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: api.PtrString("003"),
|
||||||
|
Name: "explicit-schema-type",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: 3600},
|
||||||
|
},
|
||||||
|
SchemaType: api.SCHEMATYPE_EXPLICIT.Ptr(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPatterns: []string{
|
||||||
|
`001\s+omit-schema-type\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
`002\s+implicit-schema-type\s+1h0m0s\s+n/a\s+456\s+implicit`,
|
||||||
|
`003\s+explicit-schema-type\s+1h0m0s\s+n/a\s+456\s+explicit`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no org specified",
|
||||||
|
expectedInErr: "must specify org ID or org name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
client := mock.NewMockBucketsApi(ctrl)
|
||||||
|
if tc.registerBucketExpectations != nil {
|
||||||
|
tc.registerBucketExpectations(t, client)
|
||||||
|
}
|
||||||
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
|
bytesWritten := bytes.Buffer{}
|
||||||
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
||||||
|
cli := bucket.Client{
|
||||||
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: tc.configOrgName}, StdIO: stdio},
|
||||||
|
BucketsApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cli.List(context.Background(), &tc.params)
|
||||||
|
if tc.expectedInErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.expectedInErr)
|
||||||
|
require.Empty(t, bytesWritten.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, append(
|
||||||
|
[]string{`ID\s+Name\s+Retention\s+Shard group duration\s+Organization ID\s+Schema Type`},
|
||||||
|
tc.expectedStdoutPatterns...,
|
||||||
|
), strings.Split(bytesWritten.String(), "\n"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
53
internal/cmd/bucket/update.go
Normal file
53
internal/cmd/bucket/update.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package bucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/duration"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BucketsUpdateParams struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Retention string
|
||||||
|
ShardGroupDuration string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Update(ctx context.Context, params *BucketsUpdateParams) error {
|
||||||
|
reqBody := api.PatchBucketRequest{}
|
||||||
|
if params.Name != "" {
|
||||||
|
reqBody.SetName(params.Name)
|
||||||
|
}
|
||||||
|
if params.Description != "" {
|
||||||
|
reqBody.SetDescription(params.Description)
|
||||||
|
}
|
||||||
|
if params.Retention != "" || params.ShardGroupDuration != "" {
|
||||||
|
patchRule := api.NewPatchRetentionRuleWithDefaults()
|
||||||
|
if params.Retention != "" {
|
||||||
|
rp, err := duration.RawDurationToTimeDuration(params.Retention)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
patchRule.SetEverySeconds(int64(rp.Round(time.Second) / time.Second))
|
||||||
|
}
|
||||||
|
if params.ShardGroupDuration != "" {
|
||||||
|
sgd, err := duration.RawDurationToTimeDuration(params.ShardGroupDuration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
patchRule.SetShardGroupDurationSeconds(int64(sgd.Round(time.Second) / time.Second))
|
||||||
|
}
|
||||||
|
reqBody.SetRetentionRules([]api.PatchRetentionRule{*patchRule})
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := c.PatchBucketsID(ctx, params.ID).PatchBucketRequest(reqBody).Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update bucket %q: %w", params.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.printBuckets(bucketPrintOptions{bucket: &bucket})
|
||||||
|
}
|
||||||
164
internal/cmd/bucket/update_test.go
Normal file
164
internal/cmd/bucket/update_test.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package bucket_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBucketsUpdate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var testCases = []struct {
|
||||||
|
name string
|
||||||
|
params bucket.BucketsUpdateParams
|
||||||
|
registerBucketExpectations func(*testing.T, *mock.MockBucketsApi)
|
||||||
|
expectedStdoutPattern string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
params: bucket.BucketsUpdateParams{
|
||||||
|
ID: "123",
|
||||||
|
Name: "cold-storage",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
||||||
|
body := in.GetPatchBucketRequest()
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID()) &&
|
||||||
|
assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "cold-storage", body.GetName()) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Empty(t, body.GetRetentionRules())
|
||||||
|
})).Return(api.Bucket{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "cold-storage",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+cold-storage\s+infinite\s+n/a\s+456`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
params: bucket.BucketsUpdateParams{
|
||||||
|
ID: "123",
|
||||||
|
Description: "a very useful description",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
||||||
|
body := in.GetPatchBucketRequest()
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID()) &&
|
||||||
|
assert.NotNil(t, body) &&
|
||||||
|
assert.Equal(t, "a very useful description", body.GetDescription()) &&
|
||||||
|
assert.Nil(t, body.Name) &&
|
||||||
|
assert.Empty(t, body.GetRetentionRules())
|
||||||
|
})).Return(api.Bucket{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
Description: api.PtrString("a very useful description"),
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+infinite\s+n/a\s+456`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "retention",
|
||||||
|
params: bucket.BucketsUpdateParams{
|
||||||
|
ID: "123",
|
||||||
|
Retention: "3w",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
||||||
|
body := in.GetPatchBucketRequest()
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID()) &&
|
||||||
|
assert.NotNil(t, body) &&
|
||||||
|
assert.Nil(t, body.Name) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Len(t, body.GetRetentionRules(), 1) &&
|
||||||
|
assert.Nil(t, body.GetRetentionRules()[0].ShardGroupDurationSeconds) &&
|
||||||
|
assert.Equal(t, int64(3*7*24*3600), *body.GetRetentionRules()[0].EverySeconds)
|
||||||
|
})).Return(api.Bucket{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{EverySeconds: int64(3 * 7 * 24 * 3600)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+504h0m0s\s+n/a\s+456`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "shard-group duration",
|
||||||
|
params: bucket.BucketsUpdateParams{
|
||||||
|
ID: "123",
|
||||||
|
ShardGroupDuration: "10h30m",
|
||||||
|
},
|
||||||
|
registerBucketExpectations: func(t *testing.T, bucketsApi *mock.MockBucketsApi) {
|
||||||
|
bucketsApi.EXPECT().PatchBucketsID(gomock.Any(), gomock.Eq("123")).
|
||||||
|
Return(api.ApiPatchBucketsIDRequest{ApiService: bucketsApi}.BucketID("123"))
|
||||||
|
bucketsApi.EXPECT().PatchBucketsIDExecute(tmock.MatchedBy(func(in api.ApiPatchBucketsIDRequest) bool {
|
||||||
|
body := in.GetPatchBucketRequest()
|
||||||
|
return assert.Equal(t, "123", in.GetBucketID()) &&
|
||||||
|
assert.NotNil(t, body) &&
|
||||||
|
assert.Nil(t, body.Name) &&
|
||||||
|
assert.Nil(t, body.Description) &&
|
||||||
|
assert.Len(t, body.GetRetentionRules(), 1) &&
|
||||||
|
assert.Nil(t, body.GetRetentionRules()[0].EverySeconds) &&
|
||||||
|
assert.Equal(t, int64(10*3600+30*60), *body.GetRetentionRules()[0].ShardGroupDurationSeconds)
|
||||||
|
})).Return(api.Bucket{
|
||||||
|
Id: api.PtrString("123"),
|
||||||
|
Name: "my-bucket",
|
||||||
|
OrgID: api.PtrString("456"),
|
||||||
|
RetentionRules: []api.RetentionRule{
|
||||||
|
{ShardGroupDurationSeconds: api.PtrInt64(10*3600 + 30*60)},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
},
|
||||||
|
expectedStdoutPattern: `123\s+my-bucket\s+infinite\s+10h30m0s\s+456`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
client := mock.NewMockBucketsApi(ctrl)
|
||||||
|
if tc.registerBucketExpectations != nil {
|
||||||
|
tc.registerBucketExpectations(t, client)
|
||||||
|
}
|
||||||
|
cli := bucket.Client{
|
||||||
|
CLI: cmd.CLI{StdIO: stdio},
|
||||||
|
BucketsApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cli.Update(context.Background(), &tc.params)
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, []string{
|
||||||
|
`ID\s+Name\s+Retention\s+Shard group duration\s+Organization ID\s+Schema Type`,
|
||||||
|
tc.expectedStdoutPattern,
|
||||||
|
}, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,14 +8,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
BucketApi api.BucketsApi
|
api.BucketsApi
|
||||||
BucketSchemasApi api.BucketSchemasApi
|
api.BucketSchemasApi
|
||||||
CLI *internal.CLI
|
cmd.CLI
|
||||||
}
|
}
|
||||||
|
|
||||||
type orgBucketID struct {
|
type orgBucketID struct {
|
||||||
@ -24,8 +24,7 @@ type orgBucketID struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) resolveMeasurement(ctx context.Context, ids orgBucketID, name string) (string, error) {
|
func (c Client) resolveMeasurement(ctx context.Context, ids orgBucketID, name string) (string, error) {
|
||||||
res, err := c.BucketSchemasApi.
|
res, err := c.GetMeasurementSchemas(ctx, ids.BucketID).
|
||||||
GetMeasurementSchemas(ctx, ids.BucketID).
|
|
||||||
OrgID(ids.OrgID).
|
OrgID(ids.OrgID).
|
||||||
Name(name).
|
Name(name).
|
||||||
Execute()
|
Execute()
|
||||||
@ -40,7 +39,7 @@ func (c Client) resolveMeasurement(ctx context.Context, ids orgBucketID, name st
|
|||||||
return res.MeasurementSchemas[0].Id, nil
|
return res.MeasurementSchemas[0].Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) resolveOrgBucketIds(ctx context.Context, params internal.OrgBucketParams) (*orgBucketID, error) {
|
func (c Client) resolveOrgBucketIds(ctx context.Context, params cmd.OrgBucketParams) (*orgBucketID, error) {
|
||||||
if params.OrgID.Valid() && params.BucketID.Valid() {
|
if params.OrgID.Valid() && params.BucketID.Valid() {
|
||||||
return &orgBucketID{OrgID: params.OrgID.String(), BucketID: params.BucketID.String()}, nil
|
return &orgBucketID{OrgID: params.OrgID.String(), BucketID: params.BucketID.String()}, nil
|
||||||
}
|
}
|
||||||
@ -49,17 +48,17 @@ func (c Client) resolveOrgBucketIds(ctx context.Context, params internal.OrgBuck
|
|||||||
return nil, errors.New("bucket missing: specify bucket ID or bucket name")
|
return nil, errors.New("bucket missing: specify bucket ID or bucket name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.OrgID.Valid() && params.OrgName == "" && c.CLI.ActiveConfig.Org == "" {
|
if !params.OrgID.Valid() && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||||
return nil, errors.New("org missing: specify org ID or org name")
|
return nil, errors.New("org missing: specify org ID or org name")
|
||||||
}
|
}
|
||||||
|
|
||||||
req := c.BucketApi.GetBuckets(ctx).Name(params.BucketName)
|
req := c.GetBuckets(ctx).Name(params.BucketName)
|
||||||
if params.OrgID.Valid() {
|
if params.OrgID.Valid() {
|
||||||
req = req.OrgID(params.OrgID.String())
|
req = req.OrgID(params.OrgID.String())
|
||||||
} else if params.OrgName != "" {
|
} else if params.OrgName != "" {
|
||||||
req = req.Org(params.OrgName)
|
req = req.Org(params.OrgName)
|
||||||
} else {
|
} else {
|
||||||
req = req.Org(c.CLI.ActiveConfig.Org)
|
req = req.Org(c.ActiveConfig.Org)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := req.Execute()
|
resp, err := req.Execute()
|
||||||
@ -100,117 +99,6 @@ func (c Client) readColumns(stdin io.Reader, f ColumnsFormat, path string) ([]ap
|
|||||||
return reader(r)
|
return reader(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateParams struct {
|
|
||||||
internal.OrgBucketParams
|
|
||||||
Name string
|
|
||||||
Stdin io.Reader
|
|
||||||
ColumnsFile string
|
|
||||||
ColumnsFormat ColumnsFormat
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Client) Create(ctx context.Context, params CreateParams) error {
|
|
||||||
cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := c.BucketSchemasApi.
|
|
||||||
CreateMeasurementSchema(ctx, ids.BucketID).
|
|
||||||
OrgID(ids.OrgID).
|
|
||||||
MeasurementSchemaCreateRequest(api.MeasurementSchemaCreateRequest{
|
|
||||||
Name: params.Name,
|
|
||||||
Columns: cols,
|
|
||||||
}).
|
|
||||||
Execute()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create measurement: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateParams struct {
|
|
||||||
internal.OrgBucketParams
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
Stdin io.Reader
|
|
||||||
ColumnsFile string
|
|
||||||
ColumnsFormat ColumnsFormat
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Client) Update(ctx context.Context, params UpdateParams) error {
|
|
||||||
cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var id string
|
|
||||||
if params.ID == "" && params.Name == "" {
|
|
||||||
return errors.New("measurement id or name required")
|
|
||||||
} else if params.ID != "" {
|
|
||||||
id = params.ID
|
|
||||||
} else {
|
|
||||||
id, err = c.resolveMeasurement(ctx, *ids, params.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := c.BucketSchemasApi.
|
|
||||||
UpdateMeasurementSchema(ctx, ids.BucketID, id).
|
|
||||||
OrgID(ids.OrgID).
|
|
||||||
MeasurementSchemaUpdateRequest(api.MeasurementSchemaUpdateRequest{
|
|
||||||
Columns: cols,
|
|
||||||
}).
|
|
||||||
Execute()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update measurement schema: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListParams struct {
|
|
||||||
internal.OrgBucketParams
|
|
||||||
Name string
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Client) List(ctx context.Context, params ListParams) error {
|
|
||||||
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := c.BucketSchemasApi.
|
|
||||||
GetMeasurementSchemas(ctx, ids.BucketID).
|
|
||||||
OrgID(ids.OrgID)
|
|
||||||
|
|
||||||
if params.Name != "" {
|
|
||||||
req = req.Name(params.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := req.Execute()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to list measurement schemas: %w", err)
|
|
||||||
}
|
|
||||||
return c.printMeasurements(ids.BucketID, res.MeasurementSchemas, params.ExtendedOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants for table column headers
|
// Constants for table column headers
|
||||||
const (
|
const (
|
||||||
IDHdr = "ID"
|
IDHdr = "ID"
|
||||||
@ -226,8 +114,8 @@ func (c Client) printMeasurements(bucketID string, m []api.MeasurementSchema, ex
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.CLI.PrintAsJSON {
|
if c.PrintAsJSON {
|
||||||
return c.CLI.PrintJSON(m)
|
return c.PrintJSON(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
var headers []string
|
var headers []string
|
||||||
@ -261,7 +149,7 @@ func (c Client) printMeasurements(bucketID string, m []api.MeasurementSchema, ex
|
|||||||
rows = append(rows, makeRow(bucketID, &m[i])...)
|
rows = append(rows, makeRow(bucketID, &m[i])...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.CLI.PrintTable(headers, rows...)
|
return c.PrintTable(headers, rows...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type measurementRowFn func(bucketID string, m *api.MeasurementSchema) []map[string]interface{}
|
type measurementRowFn func(bucketID string, m *api.MeasurementSchema) []map[string]interface{}
|
||||||
|
|||||||
@ -1,753 +0,0 @@
|
|||||||
package bucket_schema_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
tmock "github.com/stretchr/testify/mock"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func matchLines(t *testing.T, expectedLines []string, lines []string) {
|
|
||||||
var nonEmptyLines []string
|
|
||||||
for _, l := range lines {
|
|
||||||
if l != "" {
|
|
||||||
nonEmptyLines = append(nonEmptyLines, l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.Equal(t, len(expectedLines), len(nonEmptyLines))
|
|
||||||
for i, expected := range expectedLines {
|
|
||||||
require.Regexp(t, expected, nonEmptyLines[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_Create(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
orgID = "dead"
|
|
||||||
bucketID = "f00d"
|
|
||||||
measurementID = "1010"
|
|
||||||
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
|
||||||
)
|
|
||||||
|
|
||||||
type setupArgs struct {
|
|
||||||
buckets *mock.MockBucketsApi
|
|
||||||
schemas *mock.MockBucketSchemasApi
|
|
||||||
cli *internal.CLI
|
|
||||||
params bucket_schema.CreateParams
|
|
||||||
cols []api.MeasurementSchemaColumn
|
|
||||||
stdio *mock.MockStdIO
|
|
||||||
}
|
|
||||||
|
|
||||||
type optFn func(t *testing.T, a *setupArgs)
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
OrgName string
|
|
||||||
BucketName string
|
|
||||||
Name string
|
|
||||||
ColumnsFile string
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
withArgs := func(args args) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
var colFile string
|
|
||||||
if args.ColumnsFile != "" {
|
|
||||||
colFile = filepath.Join("testdata", args.ColumnsFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.params = bucket_schema.CreateParams{
|
|
||||||
OrgBucketParams: internal.OrgBucketParams{
|
|
||||||
OrgParams: internal.OrgParams{OrgName: args.OrgName},
|
|
||||||
BucketParams: internal.BucketParams{BucketName: args.BucketName},
|
|
||||||
},
|
|
||||||
Name: args.Name,
|
|
||||||
ColumnsFile: colFile,
|
|
||||||
ExtendedOutput: args.ExtendedOutput,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expGetBuckets := func(n ...string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
|
||||||
var buckets []api.Bucket
|
|
||||||
if len(n) == 1 {
|
|
||||||
bucket := api.NewBucket(n[0], nil)
|
|
||||||
bucket.SetOrgID(orgID)
|
|
||||||
bucket.SetId(bucketID)
|
|
||||||
bucket.SetName(n[0])
|
|
||||||
buckets = []api.Bucket{*bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBuckets(gomock.Any()).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return cmp.Equal(in.GetOrg(), &a.params.OrgName) && cmp.Equal(in.GetName(), &a.params.BucketName)
|
|
||||||
})).
|
|
||||||
Return(api.Buckets{Buckets: &buckets}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withCols := func(p string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
data, err := os.ReadFile(filepath.Join("testdata", p))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var f bucket_schema.ColumnsFormat
|
|
||||||
decoder, err := f.DecoderFn(p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
cols, err := decoder(bytes.NewReader(data))
|
|
||||||
require.NoError(t, err)
|
|
||||||
a.cols = cols
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expCreate := func() optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
req := api.ApiCreateMeasurementSchemaRequest{ApiService: a.schemas}.BucketID(bucketID)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
CreateMeasurementSchema(gomock.Any(), bucketID).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
CreateMeasurementSchemaExecute(tmock.MatchedBy(func(in api.ApiCreateMeasurementSchemaRequest) bool {
|
|
||||||
return cmp.Equal(in.GetOrgID(), &orgID) && cmp.Equal(in.GetBucketID(), bucketID)
|
|
||||||
})).
|
|
||||||
Return(api.MeasurementSchema{
|
|
||||||
Id: measurementID,
|
|
||||||
Name: a.params.Name,
|
|
||||||
Columns: a.cols,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: createdAt,
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := func(opts ...optFn) []optFn { return opts }
|
|
||||||
|
|
||||||
lines := func(lines ...string) []string { return lines }
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
opts []optFn
|
|
||||||
expErr string
|
|
||||||
expLines []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "unable to guess from stdin",
|
|
||||||
expErr: `unable to guess format for file "stdin"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "org arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
|
||||||
),
|
|
||||||
expErr: "org missing: specify org ID or org name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", ColumnsFile: "columns.csv"}),
|
|
||||||
),
|
|
||||||
expErr: "bucket missing: specify bucket ID or bucket name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket not found",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
|
||||||
expGetBuckets(),
|
|
||||||
),
|
|
||||||
expErr: `bucket "my-bucket" not found`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create succeeds with csv",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv"}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expCreate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create succeeds with json",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.json"}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expCreate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create succeeds with ndjson",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.ndjson"}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expCreate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create succeeds with extended output",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv", ExtendedOutput: true}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expCreate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockIO := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
|
|
||||||
args := &setupArgs{
|
|
||||||
buckets: mock.NewMockBucketsApi(ctrl),
|
|
||||||
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
|
||||||
stdio: mockIO,
|
|
||||||
cli: &internal.CLI{StdIO: mockIO},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range tc.opts {
|
|
||||||
opt(t, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := bucket_schema.Client{
|
|
||||||
BucketApi: args.buckets,
|
|
||||||
BucketSchemasApi: args.schemas,
|
|
||||||
CLI: args.cli,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := c.Create(ctx, args.params)
|
|
||||||
if tc.expErr != "" {
|
|
||||||
assert.EqualError(t, err, tc.expErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
matchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_Update(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
orgID = "dead"
|
|
||||||
bucketID = "f00d"
|
|
||||||
measurementID = "1010"
|
|
||||||
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
|
||||||
updatedAt = time.Date(2009, 9, 1, 2, 15, 0, 0, time.UTC)
|
|
||||||
)
|
|
||||||
|
|
||||||
type setupArgs struct {
|
|
||||||
buckets *mock.MockBucketsApi
|
|
||||||
schemas *mock.MockBucketSchemasApi
|
|
||||||
cli *internal.CLI
|
|
||||||
params bucket_schema.UpdateParams
|
|
||||||
cols []api.MeasurementSchemaColumn
|
|
||||||
stdio *mock.MockStdIO
|
|
||||||
}
|
|
||||||
|
|
||||||
type optFn func(t *testing.T, a *setupArgs)
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
OrgName string
|
|
||||||
BucketName string
|
|
||||||
Name string
|
|
||||||
ColumnsFile string
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
withArgs := func(args args) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
var colFile string
|
|
||||||
if args.ColumnsFile != "" {
|
|
||||||
colFile = filepath.Join("testdata", args.ColumnsFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
a.params = bucket_schema.UpdateParams{
|
|
||||||
OrgBucketParams: internal.OrgBucketParams{
|
|
||||||
OrgParams: internal.OrgParams{OrgName: args.OrgName},
|
|
||||||
BucketParams: internal.BucketParams{BucketName: args.BucketName},
|
|
||||||
},
|
|
||||||
Name: args.Name,
|
|
||||||
ColumnsFile: colFile,
|
|
||||||
ExtendedOutput: args.ExtendedOutput,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expGetBuckets := func(n ...string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
|
||||||
var buckets []api.Bucket
|
|
||||||
if len(n) == 1 {
|
|
||||||
bucket := api.NewBucket(n[0], nil)
|
|
||||||
bucket.SetOrgID(orgID)
|
|
||||||
bucket.SetId(bucketID)
|
|
||||||
bucket.SetName(n[0])
|
|
||||||
buckets = []api.Bucket{*bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBuckets(gomock.Any()).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return (in.GetOrg() != nil && *in.GetOrg() == a.params.OrgName) &&
|
|
||||||
(in.GetName() != nil && *in.GetName() == a.params.BucketName)
|
|
||||||
})).
|
|
||||||
Return(api.Buckets{Buckets: &buckets}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withCols := func(p string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
data, err := os.ReadFile(filepath.Join("testdata", p))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var f bucket_schema.ColumnsFormat
|
|
||||||
decoder, err := f.DecoderFn(p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
cols, err := decoder(bytes.NewReader(data))
|
|
||||||
require.NoError(t, err)
|
|
||||||
a.cols = cols
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expGetMeasurementSchema := func() optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
req := api.ApiGetMeasurementSchemasRequest{ApiService: a.schemas}.BucketID(bucketID)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
GetMeasurementSchemas(gomock.Any(), bucketID).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
GetMeasurementSchemasExecute(tmock.MatchedBy(func(in api.ApiGetMeasurementSchemasRequest) bool {
|
|
||||||
return (in.GetOrgID() != nil && *in.GetOrgID() == orgID) &&
|
|
||||||
in.GetBucketID() == bucketID &&
|
|
||||||
(in.GetName() != nil && *in.GetName() == a.params.Name)
|
|
||||||
})).
|
|
||||||
Return(api.MeasurementSchemaList{
|
|
||||||
MeasurementSchemas: []api.MeasurementSchema{
|
|
||||||
{
|
|
||||||
Id: measurementID,
|
|
||||||
Name: a.params.Name,
|
|
||||||
Columns: a.cols,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expUpdate := func() optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
req := api.ApiUpdateMeasurementSchemaRequest{ApiService: a.schemas}.BucketID(bucketID).MeasurementID(measurementID)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
UpdateMeasurementSchema(gomock.Any(), bucketID, measurementID).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
UpdateMeasurementSchemaExecute(tmock.MatchedBy(func(in api.ApiUpdateMeasurementSchemaRequest) bool {
|
|
||||||
return cmp.Equal(in.GetOrgID(), &orgID) &&
|
|
||||||
cmp.Equal(in.GetBucketID(), bucketID) &&
|
|
||||||
cmp.Equal(in.GetMeasurementID(), measurementID) &&
|
|
||||||
cmp.Equal(in.GetMeasurementSchemaUpdateRequest().Columns, a.cols)
|
|
||||||
})).
|
|
||||||
Return(api.MeasurementSchema{
|
|
||||||
Id: measurementID,
|
|
||||||
Name: a.params.Name,
|
|
||||||
Columns: a.cols,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := func(opts ...optFn) []optFn { return opts }
|
|
||||||
|
|
||||||
lines := func(lines ...string) []string { return lines }
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
opts []optFn
|
|
||||||
expErr string
|
|
||||||
expLines []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "unable to guess from stdin",
|
|
||||||
expErr: `unable to guess format for file "stdin"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "org arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
|
||||||
),
|
|
||||||
expErr: "org missing: specify org ID or org name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", ColumnsFile: "columns.csv"}),
|
|
||||||
),
|
|
||||||
expErr: "bucket missing: specify bucket ID or bucket name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket not found",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
|
||||||
expGetBuckets(),
|
|
||||||
),
|
|
||||||
expErr: `bucket "my-bucket" not found`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update succeeds",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv"}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expGetMeasurementSchema(),
|
|
||||||
expUpdate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "update succeeds extended output",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv", ExtendedOutput: true}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expGetMeasurementSchema(),
|
|
||||||
expUpdate(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
|
||||||
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockIO := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
|
|
||||||
args := &setupArgs{
|
|
||||||
buckets: mock.NewMockBucketsApi(ctrl),
|
|
||||||
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
|
||||||
stdio: mockIO,
|
|
||||||
cli: &internal.CLI{StdIO: mockIO},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range tc.opts {
|
|
||||||
opt(t, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := bucket_schema.Client{
|
|
||||||
BucketApi: args.buckets,
|
|
||||||
BucketSchemasApi: args.schemas,
|
|
||||||
CLI: args.cli,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := c.Update(ctx, args.params)
|
|
||||||
if tc.expErr != "" {
|
|
||||||
assert.EqualError(t, err, tc.expErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
matchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_List(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
orgID = "dead"
|
|
||||||
bucketID = "f00d"
|
|
||||||
measurementID = "1010"
|
|
||||||
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
|
||||||
updatedAt = time.Date(2009, 9, 1, 2, 15, 0, 0, time.UTC)
|
|
||||||
)
|
|
||||||
|
|
||||||
type setupArgs struct {
|
|
||||||
buckets *mock.MockBucketsApi
|
|
||||||
schemas *mock.MockBucketSchemasApi
|
|
||||||
cli *internal.CLI
|
|
||||||
params bucket_schema.ListParams
|
|
||||||
cols []api.MeasurementSchemaColumn
|
|
||||||
stdio *mock.MockStdIO
|
|
||||||
}
|
|
||||||
|
|
||||||
type optFn func(t *testing.T, a *setupArgs)
|
|
||||||
|
|
||||||
type args struct {
|
|
||||||
OrgName string
|
|
||||||
BucketName string
|
|
||||||
Name string
|
|
||||||
ExtendedOutput bool
|
|
||||||
}
|
|
||||||
|
|
||||||
withArgs := func(args args) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
a.params = bucket_schema.ListParams{
|
|
||||||
OrgBucketParams: internal.OrgBucketParams{
|
|
||||||
OrgParams: internal.OrgParams{OrgName: args.OrgName},
|
|
||||||
BucketParams: internal.BucketParams{BucketName: args.BucketName},
|
|
||||||
},
|
|
||||||
Name: args.Name,
|
|
||||||
ExtendedOutput: args.ExtendedOutput,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expGetBuckets := func(n ...string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
|
||||||
var buckets []api.Bucket
|
|
||||||
if len(n) == 1 {
|
|
||||||
bucket := api.NewBucket(n[0], nil)
|
|
||||||
bucket.SetOrgID(orgID)
|
|
||||||
bucket.SetId(bucketID)
|
|
||||||
bucket.SetName(n[0])
|
|
||||||
buckets = []api.Bucket{*bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBuckets(gomock.Any()).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.buckets.EXPECT().
|
|
||||||
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
|
||||||
return (in.GetOrg() != nil && *in.GetOrg() == a.params.OrgName) &&
|
|
||||||
(in.GetName() != nil && *in.GetName() == a.params.BucketName)
|
|
||||||
})).
|
|
||||||
Return(api.Buckets{Buckets: &buckets}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
withCols := func(p string) optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
data, err := os.ReadFile(filepath.Join("testdata", p))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var f bucket_schema.ColumnsFormat
|
|
||||||
decoder, err := f.DecoderFn(p)
|
|
||||||
require.NoError(t, err)
|
|
||||||
cols, err := decoder(bytes.NewReader(data))
|
|
||||||
require.NoError(t, err)
|
|
||||||
a.cols = cols
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expGetMeasurementSchemas := func() optFn {
|
|
||||||
return func(t *testing.T, a *setupArgs) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
req := api.ApiGetMeasurementSchemasRequest{ApiService: a.schemas}.BucketID(bucketID)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
GetMeasurementSchemas(gomock.Any(), bucketID).
|
|
||||||
Return(req)
|
|
||||||
|
|
||||||
a.schemas.EXPECT().
|
|
||||||
GetMeasurementSchemasExecute(tmock.MatchedBy(func(in api.ApiGetMeasurementSchemasRequest) bool {
|
|
||||||
return (in.GetOrgID() != nil && *in.GetOrgID() == orgID) &&
|
|
||||||
in.GetBucketID() == bucketID &&
|
|
||||||
(in.GetName() != nil && *in.GetName() == a.params.Name)
|
|
||||||
})).
|
|
||||||
Return(api.MeasurementSchemaList{
|
|
||||||
MeasurementSchemas: []api.MeasurementSchema{
|
|
||||||
{
|
|
||||||
Id: measurementID,
|
|
||||||
Name: a.params.Name,
|
|
||||||
Columns: a.cols,
|
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := func(opts ...optFn) []optFn { return opts }
|
|
||||||
|
|
||||||
lines := func(lines ...string) []string { return lines }
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
opts []optFn
|
|
||||||
expErr string
|
|
||||||
expLines []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "org arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{BucketName: "my-bucket"}),
|
|
||||||
),
|
|
||||||
expErr: "org missing: specify org ID or org name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket arg missing",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org"}),
|
|
||||||
),
|
|
||||||
expErr: "bucket missing: specify bucket ID or bucket name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "bucket not found",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket"}),
|
|
||||||
expGetBuckets(),
|
|
||||||
),
|
|
||||||
expErr: `bucket "my-bucket" not found`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list succeeds",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu"}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expGetMeasurementSchemas(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "list succeeds extended output",
|
|
||||||
opts: opts(
|
|
||||||
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ExtendedOutput: true}),
|
|
||||||
withCols("columns.csv"),
|
|
||||||
expGetBuckets("my-bucket"),
|
|
||||||
expGetMeasurementSchemas(),
|
|
||||||
),
|
|
||||||
expLines: lines(
|
|
||||||
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
|
||||||
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
|
||||||
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
mockIO := mock.NewMockStdIO(ctrl)
|
|
||||||
writtenBytes := bytes.Buffer{}
|
|
||||||
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
|
||||||
|
|
||||||
args := &setupArgs{
|
|
||||||
buckets: mock.NewMockBucketsApi(ctrl),
|
|
||||||
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
|
||||||
stdio: mockIO,
|
|
||||||
cli: &internal.CLI{StdIO: mockIO},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range tc.opts {
|
|
||||||
opt(t, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := bucket_schema.Client{
|
|
||||||
BucketApi: args.buckets,
|
|
||||||
BucketSchemasApi: args.schemas,
|
|
||||||
CLI: args.cli,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := c.List(ctx, args.params)
|
|
||||||
if tc.expErr != "" {
|
|
||||||
assert.EqualError(t, err, tc.expErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
matchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
internal/cmd/bucket_schema/create.go
Normal file
45
internal/cmd/bucket_schema/create.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package bucket_schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateParams struct {
|
||||||
|
cmd.OrgBucketParams
|
||||||
|
Name string
|
||||||
|
Stdin io.Reader
|
||||||
|
ColumnsFile string
|
||||||
|
ColumnsFormat ColumnsFormat
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Create(ctx context.Context, params CreateParams) error {
|
||||||
|
cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.CreateMeasurementSchema(ctx, ids.BucketID).
|
||||||
|
OrgID(ids.OrgID).
|
||||||
|
MeasurementSchemaCreateRequest(api.MeasurementSchemaCreateRequest{
|
||||||
|
Name: params.Name,
|
||||||
|
Columns: cols,
|
||||||
|
}).
|
||||||
|
Execute()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create measurement: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput)
|
||||||
|
}
|
||||||
266
internal/cmd/bucket_schema/create_test.go
Normal file
266
internal/cmd/bucket_schema/create_test.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
package bucket_schema_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_Create(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
orgID = "dead"
|
||||||
|
bucketID = "f00d"
|
||||||
|
measurementID = "1010"
|
||||||
|
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
type setupArgs struct {
|
||||||
|
buckets *mock.MockBucketsApi
|
||||||
|
schemas *mock.MockBucketSchemasApi
|
||||||
|
cli cmd.CLI
|
||||||
|
params bucket_schema.CreateParams
|
||||||
|
cols []api.MeasurementSchemaColumn
|
||||||
|
stdio *mock.MockStdIO
|
||||||
|
}
|
||||||
|
|
||||||
|
type optFn func(t *testing.T, a *setupArgs)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
OrgName string
|
||||||
|
BucketName string
|
||||||
|
Name string
|
||||||
|
ColumnsFile string
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
withArgs := func(args args) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
var colFile string
|
||||||
|
if args.ColumnsFile != "" {
|
||||||
|
colFile = filepath.Join("testdata", args.ColumnsFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.params = bucket_schema.CreateParams{
|
||||||
|
OrgBucketParams: cmd.OrgBucketParams{
|
||||||
|
OrgParams: cmd.OrgParams{OrgName: args.OrgName},
|
||||||
|
BucketParams: cmd.BucketParams{BucketName: args.BucketName},
|
||||||
|
},
|
||||||
|
Name: args.Name,
|
||||||
|
ColumnsFile: colFile,
|
||||||
|
ExtendedOutput: args.ExtendedOutput,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expGetBuckets := func(n ...string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
||||||
|
var buckets []api.Bucket
|
||||||
|
if len(n) == 1 {
|
||||||
|
bucket := api.NewBucket(n[0], nil)
|
||||||
|
bucket.SetOrgID(orgID)
|
||||||
|
bucket.SetId(bucketID)
|
||||||
|
bucket.SetName(n[0])
|
||||||
|
buckets = []api.Bucket{*bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBuckets(gomock.Any()).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return cmp.Equal(in.GetOrg(), &a.params.OrgName) && cmp.Equal(in.GetName(), &a.params.BucketName)
|
||||||
|
})).
|
||||||
|
Return(api.Buckets{Buckets: &buckets}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withCols := func(p string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
data, err := os.ReadFile(filepath.Join("testdata", p))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var f bucket_schema.ColumnsFormat
|
||||||
|
decoder, err := f.DecoderFn(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
cols, err := decoder(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
a.cols = cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expCreate := func() optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := api.ApiCreateMeasurementSchemaRequest{ApiService: a.schemas}.BucketID(bucketID)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
CreateMeasurementSchema(gomock.Any(), bucketID).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
CreateMeasurementSchemaExecute(tmock.MatchedBy(func(in api.ApiCreateMeasurementSchemaRequest) bool {
|
||||||
|
return cmp.Equal(in.GetOrgID(), &orgID) && cmp.Equal(in.GetBucketID(), bucketID)
|
||||||
|
})).
|
||||||
|
Return(api.MeasurementSchema{
|
||||||
|
Id: measurementID,
|
||||||
|
Name: a.params.Name,
|
||||||
|
Columns: a.cols,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: createdAt,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := func(opts ...optFn) []optFn { return opts }
|
||||||
|
|
||||||
|
lines := func(lines ...string) []string { return lines }
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
opts []optFn
|
||||||
|
expErr string
|
||||||
|
expLines []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unable to guess from stdin",
|
||||||
|
expErr: `unable to guess format for file "stdin"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "org arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
||||||
|
),
|
||||||
|
expErr: "org missing: specify org ID or org name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", ColumnsFile: "columns.csv"}),
|
||||||
|
),
|
||||||
|
expErr: "bucket missing: specify bucket ID or bucket name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket not found",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
||||||
|
expGetBuckets(),
|
||||||
|
),
|
||||||
|
expErr: `bucket "my-bucket" not found`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create succeeds with csv",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv"}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expCreate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create succeeds with json",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.json"}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expCreate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create succeeds with ndjson",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.ndjson"}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expCreate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create succeeds with extended output",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv", ExtendedOutput: true}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expCreate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockIO := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
|
||||||
|
args := &setupArgs{
|
||||||
|
buckets: mock.NewMockBucketsApi(ctrl),
|
||||||
|
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
||||||
|
stdio: mockIO,
|
||||||
|
cli: cmd.CLI{StdIO: mockIO},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range tc.opts {
|
||||||
|
opt(t, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := bucket_schema.Client{
|
||||||
|
BucketsApi: args.buckets,
|
||||||
|
BucketSchemasApi: args.schemas,
|
||||||
|
CLI: args.cli,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Create(context.Background(), args.params)
|
||||||
|
if tc.expErr != "" {
|
||||||
|
assert.EqualError(t, err, tc.expErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
33
internal/cmd/bucket_schema/list.go
Normal file
33
internal/cmd/bucket_schema/list.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package bucket_schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListParams struct {
|
||||||
|
cmd.OrgBucketParams
|
||||||
|
Name string
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) List(ctx context.Context, params ListParams) error {
|
||||||
|
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := c.GetMeasurementSchemas(ctx, ids.BucketID).OrgID(ids.OrgID)
|
||||||
|
|
||||||
|
if params.Name != "" {
|
||||||
|
req = req.Name(params.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := req.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to list measurement schemas: %w", err)
|
||||||
|
}
|
||||||
|
return c.printMeasurements(ids.BucketID, res.MeasurementSchemas, params.ExtendedOutput)
|
||||||
|
}
|
||||||
236
internal/cmd/bucket_schema/list_test.go
Normal file
236
internal/cmd/bucket_schema/list_test.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package bucket_schema_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_List(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
orgID = "dead"
|
||||||
|
bucketID = "f00d"
|
||||||
|
measurementID = "1010"
|
||||||
|
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
||||||
|
updatedAt = time.Date(2009, 9, 1, 2, 15, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
type setupArgs struct {
|
||||||
|
buckets *mock.MockBucketsApi
|
||||||
|
schemas *mock.MockBucketSchemasApi
|
||||||
|
cli cmd.CLI
|
||||||
|
params bucket_schema.ListParams
|
||||||
|
cols []api.MeasurementSchemaColumn
|
||||||
|
stdio *mock.MockStdIO
|
||||||
|
}
|
||||||
|
|
||||||
|
type optFn func(t *testing.T, a *setupArgs)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
OrgName string
|
||||||
|
BucketName string
|
||||||
|
Name string
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
withArgs := func(args args) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
a.params = bucket_schema.ListParams{
|
||||||
|
OrgBucketParams: cmd.OrgBucketParams{
|
||||||
|
OrgParams: cmd.OrgParams{OrgName: args.OrgName},
|
||||||
|
BucketParams: cmd.BucketParams{BucketName: args.BucketName},
|
||||||
|
},
|
||||||
|
Name: args.Name,
|
||||||
|
ExtendedOutput: args.ExtendedOutput,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expGetBuckets := func(n ...string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
||||||
|
var buckets []api.Bucket
|
||||||
|
if len(n) == 1 {
|
||||||
|
bucket := api.NewBucket(n[0], nil)
|
||||||
|
bucket.SetOrgID(orgID)
|
||||||
|
bucket.SetId(bucketID)
|
||||||
|
bucket.SetName(n[0])
|
||||||
|
buckets = []api.Bucket{*bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBuckets(gomock.Any()).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return (in.GetOrg() != nil && *in.GetOrg() == a.params.OrgName) &&
|
||||||
|
(in.GetName() != nil && *in.GetName() == a.params.BucketName)
|
||||||
|
})).
|
||||||
|
Return(api.Buckets{Buckets: &buckets}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withCols := func(p string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
data, err := os.ReadFile(filepath.Join("testdata", p))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var f bucket_schema.ColumnsFormat
|
||||||
|
decoder, err := f.DecoderFn(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
cols, err := decoder(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
a.cols = cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expGetMeasurementSchemas := func() optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := api.ApiGetMeasurementSchemasRequest{ApiService: a.schemas}.BucketID(bucketID)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
GetMeasurementSchemas(gomock.Any(), bucketID).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
GetMeasurementSchemasExecute(tmock.MatchedBy(func(in api.ApiGetMeasurementSchemasRequest) bool {
|
||||||
|
return (in.GetOrgID() != nil && *in.GetOrgID() == orgID) &&
|
||||||
|
in.GetBucketID() == bucketID &&
|
||||||
|
(in.GetName() != nil && *in.GetName() == a.params.Name)
|
||||||
|
})).
|
||||||
|
Return(api.MeasurementSchemaList{
|
||||||
|
MeasurementSchemas: []api.MeasurementSchema{
|
||||||
|
{
|
||||||
|
Id: measurementID,
|
||||||
|
Name: a.params.Name,
|
||||||
|
Columns: a.cols,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := func(opts ...optFn) []optFn { return opts }
|
||||||
|
|
||||||
|
lines := func(lines ...string) []string { return lines }
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
opts []optFn
|
||||||
|
expErr string
|
||||||
|
expLines []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "org arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{BucketName: "my-bucket"}),
|
||||||
|
),
|
||||||
|
expErr: "org missing: specify org ID or org name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org"}),
|
||||||
|
),
|
||||||
|
expErr: "bucket missing: specify bucket ID or bucket name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket not found",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket"}),
|
||||||
|
expGetBuckets(),
|
||||||
|
),
|
||||||
|
expErr: `bucket "my-bucket" not found`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list succeeds",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu"}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expGetMeasurementSchemas(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list succeeds extended output",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ExtendedOutput: true}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expGetMeasurementSchemas(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockIO := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
|
||||||
|
args := &setupArgs{
|
||||||
|
buckets: mock.NewMockBucketsApi(ctrl),
|
||||||
|
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
||||||
|
stdio: mockIO,
|
||||||
|
cli: cmd.CLI{StdIO: mockIO},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range tc.opts {
|
||||||
|
opt(t, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := bucket_schema.Client{
|
||||||
|
BucketsApi: args.buckets,
|
||||||
|
BucketSchemasApi: args.schemas,
|
||||||
|
CLI: args.cli,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.List(context.Background(), args.params)
|
||||||
|
if tc.expErr != "" {
|
||||||
|
assert.EqualError(t, err, tc.expErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
58
internal/cmd/bucket_schema/update.go
Normal file
58
internal/cmd/bucket_schema/update.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package bucket_schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateParams struct {
|
||||||
|
cmd.OrgBucketParams
|
||||||
|
Name string
|
||||||
|
ID string
|
||||||
|
Stdin io.Reader
|
||||||
|
ColumnsFile string
|
||||||
|
ColumnsFormat ColumnsFormat
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Update(ctx context.Context, params UpdateParams) error {
|
||||||
|
cols, err := c.readColumns(params.Stdin, params.ColumnsFormat, params.ColumnsFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := c.resolveOrgBucketIds(ctx, params.OrgBucketParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var id string
|
||||||
|
if params.ID == "" && params.Name == "" {
|
||||||
|
return errors.New("measurement id or name required")
|
||||||
|
} else if params.ID != "" {
|
||||||
|
id = params.ID
|
||||||
|
} else {
|
||||||
|
id, err = c.resolveMeasurement(ctx, *ids, params.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.UpdateMeasurementSchema(ctx, ids.BucketID, id).
|
||||||
|
OrgID(ids.OrgID).
|
||||||
|
MeasurementSchemaUpdateRequest(api.MeasurementSchemaUpdateRequest{
|
||||||
|
Columns: cols,
|
||||||
|
}).
|
||||||
|
Execute()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update measurement schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.printMeasurements(ids.BucketID, []api.MeasurementSchema{res}, params.ExtendedOutput)
|
||||||
|
}
|
||||||
279
internal/cmd/bucket_schema/update_test.go
Normal file
279
internal/cmd/bucket_schema/update_test.go
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package bucket_schema_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket_schema"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
tmock "github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClient_Update(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
orgID = "dead"
|
||||||
|
bucketID = "f00d"
|
||||||
|
measurementID = "1010"
|
||||||
|
createdAt = time.Date(2004, 4, 9, 2, 15, 0, 0, time.UTC)
|
||||||
|
updatedAt = time.Date(2009, 9, 1, 2, 15, 0, 0, time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
type setupArgs struct {
|
||||||
|
buckets *mock.MockBucketsApi
|
||||||
|
schemas *mock.MockBucketSchemasApi
|
||||||
|
cli cmd.CLI
|
||||||
|
params bucket_schema.UpdateParams
|
||||||
|
cols []api.MeasurementSchemaColumn
|
||||||
|
stdio *mock.MockStdIO
|
||||||
|
}
|
||||||
|
|
||||||
|
type optFn func(t *testing.T, a *setupArgs)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
OrgName string
|
||||||
|
BucketName string
|
||||||
|
Name string
|
||||||
|
ColumnsFile string
|
||||||
|
ExtendedOutput bool
|
||||||
|
}
|
||||||
|
|
||||||
|
withArgs := func(args args) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
var colFile string
|
||||||
|
if args.ColumnsFile != "" {
|
||||||
|
colFile = filepath.Join("testdata", args.ColumnsFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.params = bucket_schema.UpdateParams{
|
||||||
|
OrgBucketParams: cmd.OrgBucketParams{
|
||||||
|
OrgParams: cmd.OrgParams{OrgName: args.OrgName},
|
||||||
|
BucketParams: cmd.BucketParams{BucketName: args.BucketName},
|
||||||
|
},
|
||||||
|
Name: args.Name,
|
||||||
|
ColumnsFile: colFile,
|
||||||
|
ExtendedOutput: args.ExtendedOutput,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expGetBuckets := func(n ...string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
require.True(t, len(n) <= 1, "either zero or one bucket name")
|
||||||
|
var buckets []api.Bucket
|
||||||
|
if len(n) == 1 {
|
||||||
|
bucket := api.NewBucket(n[0], nil)
|
||||||
|
bucket.SetOrgID(orgID)
|
||||||
|
bucket.SetId(bucketID)
|
||||||
|
bucket.SetName(n[0])
|
||||||
|
buckets = []api.Bucket{*bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.ApiGetBucketsRequest{ApiService: a.buckets}
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBuckets(gomock.Any()).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.buckets.EXPECT().
|
||||||
|
GetBucketsExecute(tmock.MatchedBy(func(in api.ApiGetBucketsRequest) bool {
|
||||||
|
return (in.GetOrg() != nil && *in.GetOrg() == a.params.OrgName) &&
|
||||||
|
(in.GetName() != nil && *in.GetName() == a.params.BucketName)
|
||||||
|
})).
|
||||||
|
Return(api.Buckets{Buckets: &buckets}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withCols := func(p string) optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
data, err := os.ReadFile(filepath.Join("testdata", p))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var f bucket_schema.ColumnsFormat
|
||||||
|
decoder, err := f.DecoderFn(p)
|
||||||
|
require.NoError(t, err)
|
||||||
|
cols, err := decoder(bytes.NewReader(data))
|
||||||
|
require.NoError(t, err)
|
||||||
|
a.cols = cols
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expGetMeasurementSchema := func() optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := api.ApiGetMeasurementSchemasRequest{ApiService: a.schemas}.BucketID(bucketID)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
GetMeasurementSchemas(gomock.Any(), bucketID).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
GetMeasurementSchemasExecute(tmock.MatchedBy(func(in api.ApiGetMeasurementSchemasRequest) bool {
|
||||||
|
return (in.GetOrgID() != nil && *in.GetOrgID() == orgID) &&
|
||||||
|
in.GetBucketID() == bucketID &&
|
||||||
|
(in.GetName() != nil && *in.GetName() == a.params.Name)
|
||||||
|
})).
|
||||||
|
Return(api.MeasurementSchemaList{
|
||||||
|
MeasurementSchemas: []api.MeasurementSchema{
|
||||||
|
{
|
||||||
|
Id: measurementID,
|
||||||
|
Name: a.params.Name,
|
||||||
|
Columns: a.cols,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expUpdate := func() optFn {
|
||||||
|
return func(t *testing.T, a *setupArgs) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := api.ApiUpdateMeasurementSchemaRequest{ApiService: a.schemas}.BucketID(bucketID).MeasurementID(measurementID)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
UpdateMeasurementSchema(gomock.Any(), bucketID, measurementID).
|
||||||
|
Return(req)
|
||||||
|
|
||||||
|
a.schemas.EXPECT().
|
||||||
|
UpdateMeasurementSchemaExecute(tmock.MatchedBy(func(in api.ApiUpdateMeasurementSchemaRequest) bool {
|
||||||
|
return cmp.Equal(in.GetOrgID(), &orgID) &&
|
||||||
|
cmp.Equal(in.GetBucketID(), bucketID) &&
|
||||||
|
cmp.Equal(in.GetMeasurementID(), measurementID) &&
|
||||||
|
cmp.Equal(in.GetMeasurementSchemaUpdateRequest().Columns, a.cols)
|
||||||
|
})).
|
||||||
|
Return(api.MeasurementSchema{
|
||||||
|
Id: measurementID,
|
||||||
|
Name: a.params.Name,
|
||||||
|
Columns: a.cols,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := func(opts ...optFn) []optFn { return opts }
|
||||||
|
|
||||||
|
lines := func(lines ...string) []string { return lines }
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
opts []optFn
|
||||||
|
expErr string
|
||||||
|
expLines []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unable to guess from stdin",
|
||||||
|
expErr: `unable to guess format for file "stdin"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "org arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
||||||
|
),
|
||||||
|
expErr: "org missing: specify org ID or org name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket arg missing",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", ColumnsFile: "columns.csv"}),
|
||||||
|
),
|
||||||
|
expErr: "bucket missing: specify bucket ID or bucket name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket not found",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", ColumnsFile: "columns.csv"}),
|
||||||
|
expGetBuckets(),
|
||||||
|
),
|
||||||
|
expErr: `bucket "my-bucket" not found`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update succeeds",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv"}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expGetMeasurementSchema(),
|
||||||
|
expUpdate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+f00d$`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update succeeds extended output",
|
||||||
|
opts: opts(
|
||||||
|
withArgs(args{OrgName: "my-org", BucketName: "my-bucket", Name: "cpu", ColumnsFile: "columns.csv", ExtendedOutput: true}),
|
||||||
|
withCols("columns.csv"),
|
||||||
|
expGetBuckets("my-bucket"),
|
||||||
|
expGetMeasurementSchema(),
|
||||||
|
expUpdate(),
|
||||||
|
),
|
||||||
|
expLines: lines(
|
||||||
|
`^ID\s+Measurement Name\s+Column Name\s+Column Type\s+Column Data Type\s+Bucket ID$`,
|
||||||
|
`^1010\s+cpu\s+time\s+timestamp\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+host\s+tag\s+f00d$`,
|
||||||
|
`^1010\s+cpu\s+usage_user\s+field\s+float\s+f00d$`,
|
||||||
|
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
mockIO := mock.NewMockStdIO(ctrl)
|
||||||
|
writtenBytes := bytes.Buffer{}
|
||||||
|
mockIO.EXPECT().Write(gomock.Any()).DoAndReturn(writtenBytes.Write).AnyTimes()
|
||||||
|
|
||||||
|
args := &setupArgs{
|
||||||
|
buckets: mock.NewMockBucketsApi(ctrl),
|
||||||
|
schemas: mock.NewMockBucketSchemasApi(ctrl),
|
||||||
|
stdio: mockIO,
|
||||||
|
cli: cmd.CLI{StdIO: mockIO},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range tc.opts {
|
||||||
|
opt(t, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := bucket_schema.Client{
|
||||||
|
BucketsApi: args.buckets,
|
||||||
|
BucketSchemasApi: args.schemas,
|
||||||
|
CLI: args.cli,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Update(context.Background(), args.params)
|
||||||
|
if tc.expErr != "" {
|
||||||
|
assert.EqualError(t, err, tc.expErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
testutils.MatchLines(t, tc.expLines, strings.Split(writtenBytes.String(), "\n"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
||||||
22
internal/cmd/ping/ping.go
Normal file
22
internal/cmd/ping/ping.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
cmd.CLI
|
||||||
|
api.HealthApi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping checks the health of a remote InfluxDB instance.
|
||||||
|
func (c Client) Ping(ctx context.Context) error {
|
||||||
|
if _, err := c.GetHealth(ctx).Execute(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := c.StdIO.Write([]byte("OK\n"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal_test
|
package ping_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -7,8 +7,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/ping"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -24,9 +25,12 @@ func Test_PingSuccess(t *testing.T) {
|
|||||||
stdio := mock.NewMockStdIO(ctrl)
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
bytesWritten := bytes.Buffer{}
|
bytesWritten := bytes.Buffer{}
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
||||||
cli := &internal.CLI{StdIO: stdio}
|
cli := ping.Client{
|
||||||
|
CLI: cmd.CLI{StdIO: stdio},
|
||||||
|
HealthApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
require.NoError(t, cli.Ping(context.Background(), client))
|
require.NoError(t, cli.Ping(context.Background()))
|
||||||
require.Equal(t, "OK\n", bytesWritten.String())
|
require.Equal(t, "OK\n", bytesWritten.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +42,11 @@ func Test_PingFailedRequest(t *testing.T) {
|
|||||||
client := mock.NewMockHealthApi(ctrl)
|
client := mock.NewMockHealthApi(ctrl)
|
||||||
client.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: client})
|
client.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: client})
|
||||||
client.EXPECT().GetHealthExecute(gomock.Any()).Return(api.HealthCheck{}, errors.New(e))
|
client.EXPECT().GetHealthExecute(gomock.Any()).Return(api.HealthCheck{}, errors.New(e))
|
||||||
|
cli := ping.Client{
|
||||||
|
HealthApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
cli := &internal.CLI{}
|
err := cli.Ping(context.Background())
|
||||||
err := cli.Ping(context.Background(), client)
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), e)
|
require.Contains(t, err.Error(), e)
|
||||||
}
|
}
|
||||||
@ -54,9 +60,10 @@ func Test_PingFailedStatus(t *testing.T) {
|
|||||||
client.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: client})
|
client.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: client})
|
||||||
client.EXPECT().GetHealthExecute(gomock.Any()).
|
client.EXPECT().GetHealthExecute(gomock.Any()).
|
||||||
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Message: &e})
|
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Message: &e})
|
||||||
|
cli := ping.Client{
|
||||||
cli := &internal.CLI{}
|
HealthApi: client,
|
||||||
err := cli.Ping(context.Background(), client)
|
}
|
||||||
|
err := cli.Ping(context.Background())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), e)
|
require.Contains(t, err.Error(), e)
|
||||||
}
|
}
|
||||||
@ -71,8 +78,10 @@ func Test_PingFailedStatusNoMessage(t *testing.T) {
|
|||||||
client.EXPECT().GetHealthExecute(gomock.Any()).
|
client.EXPECT().GetHealthExecute(gomock.Any()).
|
||||||
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Name: name})
|
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Name: name})
|
||||||
|
|
||||||
cli := &internal.CLI{}
|
cli := ping.Client{
|
||||||
err := cli.Ping(context.Background(), client)
|
HealthApi: client,
|
||||||
|
}
|
||||||
|
err := cli.Ping(context.Background())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), name)
|
require.Contains(t, err.Error(), name)
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -9,21 +9,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/bucket"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/config"
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/duration"
|
"github.com/influxdata/influx-cli/v2/internal/duration"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SetupParams struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
AuthToken string
|
|
||||||
Org string
|
|
||||||
Bucket string
|
|
||||||
Retention string
|
|
||||||
Force bool
|
|
||||||
ConfigName string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrPasswordIsTooShort = errors.New("password is too short")
|
ErrPasswordIsTooShort = errors.New("password is too short")
|
||||||
ErrAlreadySetUp = errors.New("instance has already been set up")
|
ErrAlreadySetUp = errors.New("instance has already been set up")
|
||||||
@ -33,9 +24,25 @@ var (
|
|||||||
|
|
||||||
const MinPasswordLen = 8
|
const MinPasswordLen = 8
|
||||||
|
|
||||||
func (c *CLI) Setup(ctx context.Context, client api.SetupApi, params *SetupParams) error {
|
type Client struct {
|
||||||
|
cmd.CLI
|
||||||
|
api.SetupApi
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
AuthToken string
|
||||||
|
Org string
|
||||||
|
Bucket string
|
||||||
|
Retention string
|
||||||
|
Force bool
|
||||||
|
ConfigName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Client) Setup(ctx context.Context, params *Params) error {
|
||||||
// Check if setup is even allowed.
|
// Check if setup is even allowed.
|
||||||
checkResp, err := client.GetSetup(ctx).Execute()
|
checkResp, err := c.GetSetup(ctx).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check if already set up: %w", err)
|
return fmt.Errorf("failed to check if already set up: %w", err)
|
||||||
}
|
}
|
||||||
@ -54,7 +61,7 @@ func (c *CLI) Setup(ctx context.Context, client api.SetupApi, params *SetupParam
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := client.PostSetup(ctx).OnboardingRequest(setupBody).Execute()
|
resp, err := c.PostSetup(ctx).OnboardingRequest(setupBody).Execute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup instance: %w", err)
|
return fmt.Errorf("failed to setup instance: %w", err)
|
||||||
}
|
}
|
||||||
@ -91,7 +98,7 @@ func (c *CLI) Setup(ctx context.Context, client api.SetupApi, params *SetupParam
|
|||||||
// validateNoNameCollision checks that we will be able to write onboarding results to local config:
|
// validateNoNameCollision checks that we will be able to write onboarding results to local config:
|
||||||
// - If a custom name was given, check that it doesn't collide with existing config
|
// - If a custom name was given, check that it doesn't collide with existing config
|
||||||
// - If no custom name was given, check that we don't already have configs
|
// - If no custom name was given, check that we don't already have configs
|
||||||
func (c *CLI) validateNoNameCollision(configName string) error {
|
func (c Client) validateNoNameCollision(configName string) error {
|
||||||
existingConfigs, err := c.ConfigService.ListConfigs()
|
existingConfigs, err := c.ConfigService.ListConfigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error checking existing configs: %w", err)
|
return fmt.Errorf("error checking existing configs: %w", err)
|
||||||
@ -116,7 +123,7 @@ func (c *CLI) validateNoNameCollision(configName string) error {
|
|||||||
// onboardingRequest constructs a request body for the onboarding API.
|
// onboardingRequest constructs a request body for the onboarding API.
|
||||||
// Unless the 'force' parameter is set, the user will be prompted to enter any missing information
|
// Unless the 'force' parameter is set, the user will be prompted to enter any missing information
|
||||||
// and to confirm the final request parameters.
|
// and to confirm the final request parameters.
|
||||||
func (c *CLI) onboardingRequest(params *SetupParams) (req api.OnboardingRequest, err error) {
|
func (c Client) onboardingRequest(params *Params) (req api.OnboardingRequest, err error) {
|
||||||
if (params.Force || params.Password != "") && len(params.Password) < MinPasswordLen {
|
if (params.Force || params.Password != "") && len(params.Password) < MinPasswordLen {
|
||||||
return req, ErrPasswordIsTooShort
|
return req, ErrPasswordIsTooShort
|
||||||
}
|
}
|
||||||
@ -131,7 +138,7 @@ func (c *CLI) onboardingRequest(params *SetupParams) (req api.OnboardingRequest,
|
|||||||
if params.AuthToken != "" {
|
if params.AuthToken != "" {
|
||||||
req.Token = ¶ms.AuthToken
|
req.Token = ¶ms.AuthToken
|
||||||
}
|
}
|
||||||
rpSecs := int64(InfiniteRetention)
|
rpSecs := int64(bucket.InfiniteRetention)
|
||||||
if params.Retention != "" {
|
if params.Retention != "" {
|
||||||
dur, err := duration.RawDurationToTimeDuration(params.Retention)
|
dur, err := duration.RawDurationToTimeDuration(params.Retention)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -192,7 +199,7 @@ func (c *CLI) onboardingRequest(params *SetupParams) (req api.OnboardingRequest,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if params.Retention == "" {
|
if params.Retention == "" {
|
||||||
infiniteStr := strconv.Itoa(InfiniteRetention)
|
infiniteStr := strconv.Itoa(bucket.InfiniteRetention)
|
||||||
for {
|
for {
|
||||||
rpStr, err := c.StdIO.GetStringInput("Please type your retention period in hours, or 0 for infinite", infiniteStr)
|
rpStr, err := c.StdIO.GetStringInput("Please type your retention period in hours, or 0 for infinite", infiniteStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal_test
|
package setup_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -10,11 +10,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/setup"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/config"
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/duration"
|
"github.com/influxdata/influx-cli/v2/internal/duration"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/testutils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
tmock "github.com/stretchr/testify/mock"
|
tmock "github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -31,9 +33,12 @@ func Test_SetupConfigNameCollision(t *testing.T) {
|
|||||||
cfg := "foo"
|
cfg := "foo"
|
||||||
configSvc := mock.NewMockConfigService(ctrl)
|
configSvc := mock.NewMockConfigService(ctrl)
|
||||||
configSvc.EXPECT().ListConfigs().Return(map[string]config.Config{cfg: {}}, nil)
|
configSvc.EXPECT().ListConfigs().Return(map[string]config.Config{cfg: {}}, nil)
|
||||||
cli := &internal.CLI{ConfigService: configSvc}
|
cli := setup.Client{
|
||||||
|
CLI: cmd.CLI{ConfigService: configSvc},
|
||||||
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
err := cli.Setup(context.Background(), client, &internal.SetupParams{ConfigName: cfg})
|
err := cli.Setup(context.Background(), &setup.Params{ConfigName: cfg})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), cfg)
|
require.Contains(t, err.Error(), cfg)
|
||||||
require.Contains(t, err.Error(), "already exists")
|
require.Contains(t, err.Error(), "already exists")
|
||||||
@ -49,11 +54,14 @@ func Test_SetupConfigNameRequired(t *testing.T) {
|
|||||||
|
|
||||||
configSvc := mock.NewMockConfigService(ctrl)
|
configSvc := mock.NewMockConfigService(ctrl)
|
||||||
configSvc.EXPECT().ListConfigs().Return(map[string]config.Config{"foo": {}}, nil)
|
configSvc.EXPECT().ListConfigs().Return(map[string]config.Config{"foo": {}}, nil)
|
||||||
cli := &internal.CLI{ConfigService: configSvc}
|
cli := setup.Client{
|
||||||
|
CLI: cmd.CLI{ConfigService: configSvc},
|
||||||
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
err := cli.Setup(context.Background(), client, &internal.SetupParams{})
|
err := cli.Setup(context.Background(), &setup.Params{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, internal.ErrConfigNameRequired, err)
|
require.Equal(t, setup.ErrConfigNameRequired, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetupAlreadySetup(t *testing.T) {
|
func Test_SetupAlreadySetup(t *testing.T) {
|
||||||
@ -64,11 +72,14 @@ func Test_SetupAlreadySetup(t *testing.T) {
|
|||||||
client.EXPECT().GetSetupExecute(gomock.Any()).Return(api.InlineResponse200{Allowed: api.PtrBool(false)}, nil)
|
client.EXPECT().GetSetupExecute(gomock.Any()).Return(api.InlineResponse200{Allowed: api.PtrBool(false)}, nil)
|
||||||
|
|
||||||
configSvc := mock.NewMockConfigService(ctrl)
|
configSvc := mock.NewMockConfigService(ctrl)
|
||||||
cli := &internal.CLI{ConfigService: configSvc}
|
cli := setup.Client{
|
||||||
|
CLI: cmd.CLI{ConfigService: configSvc},
|
||||||
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
err := cli.Setup(context.Background(), client, &internal.SetupParams{})
|
err := cli.Setup(context.Background(), &setup.Params{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, internal.ErrAlreadySetUp, err)
|
require.Equal(t, setup.ErrAlreadySetUp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetupCheckFailed(t *testing.T) {
|
func Test_SetupCheckFailed(t *testing.T) {
|
||||||
@ -81,9 +92,12 @@ func Test_SetupCheckFailed(t *testing.T) {
|
|||||||
client.EXPECT().GetSetupExecute(gomock.Any()).Return(api.InlineResponse200{}, errors.New(e))
|
client.EXPECT().GetSetupExecute(gomock.Any()).Return(api.InlineResponse200{}, errors.New(e))
|
||||||
|
|
||||||
configSvc := mock.NewMockConfigService(ctrl)
|
configSvc := mock.NewMockConfigService(ctrl)
|
||||||
cli := &internal.CLI{ConfigService: configSvc}
|
cli := setup.Client{
|
||||||
|
CLI: cmd.CLI{ConfigService: configSvc},
|
||||||
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
|
||||||
err := cli.Setup(context.Background(), client, &internal.SetupParams{})
|
err := cli.Setup(context.Background(), &setup.Params{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Contains(t, err.Error(), e)
|
require.Contains(t, err.Error(), e)
|
||||||
}
|
}
|
||||||
@ -92,7 +106,7 @@ func Test_SetupSuccessNoninteractive(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retentionSecs := int64(duration.Week.Seconds())
|
retentionSecs := int64(duration.Week.Seconds())
|
||||||
params := internal.SetupParams{
|
params := setup.Params{
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "mysecretpassword",
|
Password: "mysecretpassword",
|
||||||
AuthToken: "mytoken",
|
AuthToken: "mytoken",
|
||||||
@ -140,14 +154,15 @@ func Test_SetupSuccessNoninteractive(t *testing.T) {
|
|||||||
stdio := mock.NewMockStdIO(ctrl)
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
bytesWritten := bytes.Buffer{}
|
bytesWritten := bytes.Buffer{}
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
||||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
cli := setup.Client{
|
||||||
require.NoError(t, cli.Setup(context.Background(), client, ¶ms))
|
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||||
|
SetupApi: client,
|
||||||
outLines := strings.Split(strings.TrimSpace(bytesWritten.String()), "\n")
|
}
|
||||||
require.Len(t, outLines, 2)
|
require.NoError(t, cli.Setup(context.Background(), ¶ms))
|
||||||
header, data := outLines[0], outLines[1]
|
testutils.MatchLines(t, []string{
|
||||||
require.Regexp(t, "User\\s+Organization\\s+Bucket", header)
|
`User\s+Organization\s+Bucket`,
|
||||||
require.Regexp(t, fmt.Sprintf("%s\\s+%s\\s+%s", params.Username, params.Org, params.Bucket), data)
|
fmt.Sprintf(`%s\s+%s\s+%s`, params.Username, params.Org, params.Bucket),
|
||||||
|
}, strings.Split(bytesWritten.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetupSuccessInteractive(t *testing.T) {
|
func Test_SetupSuccessInteractive(t *testing.T) {
|
||||||
@ -206,21 +221,22 @@ func Test_SetupSuccessInteractive(t *testing.T) {
|
|||||||
stdio.EXPECT().GetStringInput("Please type your primary bucket name", gomock.Any()).Return(bucket, nil)
|
stdio.EXPECT().GetStringInput("Please type your primary bucket name", gomock.Any()).Return(bucket, nil)
|
||||||
stdio.EXPECT().GetStringInput("Please type your retention period in hours, or 0 for infinite", gomock.Any()).Return(strconv.Itoa(retentionHrs), nil)
|
stdio.EXPECT().GetStringInput("Please type your retention period in hours, or 0 for infinite", gomock.Any()).Return(strconv.Itoa(retentionHrs), nil)
|
||||||
stdio.EXPECT().GetConfirm(gomock.Any()).Return(true)
|
stdio.EXPECT().GetConfirm(gomock.Any()).Return(true)
|
||||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
cli := setup.Client{
|
||||||
require.NoError(t, cli.Setup(context.Background(), client, &internal.SetupParams{}))
|
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||||
|
SetupApi: client,
|
||||||
outLines := strings.Split(strings.TrimSpace(bytesWritten.String()), "\n")
|
}
|
||||||
require.Len(t, outLines, 2)
|
require.NoError(t, cli.Setup(context.Background(), &setup.Params{}))
|
||||||
header, data := outLines[0], outLines[1]
|
testutils.MatchLines(t, []string{
|
||||||
require.Regexp(t, "User\\s+Organization\\s+Bucket", header)
|
`User\s+Organization\s+Bucket`,
|
||||||
require.Regexp(t, fmt.Sprintf("%s\\s+%s\\s+%s", username, org, bucket), data)
|
fmt.Sprintf(`%s\s+%s\s+%s`, username, org, bucket),
|
||||||
|
}, strings.Split(bytesWritten.String(), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetupPasswordParamToShort(t *testing.T) {
|
func Test_SetupPasswordParamToShort(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retentionSecs := int64(duration.Week.Seconds())
|
retentionSecs := int64(duration.Week.Seconds())
|
||||||
params := internal.SetupParams{
|
params := setup.Params{
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "2short",
|
Password: "2short",
|
||||||
AuthToken: "mytoken",
|
AuthToken: "mytoken",
|
||||||
@ -240,16 +256,19 @@ func Test_SetupPasswordParamToShort(t *testing.T) {
|
|||||||
configSvc.EXPECT().ListConfigs().Return(nil, nil)
|
configSvc.EXPECT().ListConfigs().Return(nil, nil)
|
||||||
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
cli := setup.Client{
|
||||||
err := cli.Setup(context.Background(), client, ¶ms)
|
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||||
require.Equal(t, internal.ErrPasswordIsTooShort, err)
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
err := cli.Setup(context.Background(), ¶ms)
|
||||||
|
require.Equal(t, setup.ErrPasswordIsTooShort, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetupCancelAtConfirmation(t *testing.T) {
|
func Test_SetupCancelAtConfirmation(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
retentionSecs := int64(duration.Week.Seconds())
|
retentionSecs := int64(duration.Week.Seconds())
|
||||||
params := internal.SetupParams{
|
params := setup.Params{
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "mysecretpassword",
|
Password: "mysecretpassword",
|
||||||
AuthToken: "mytoken",
|
AuthToken: "mytoken",
|
||||||
@ -272,7 +291,10 @@ func Test_SetupCancelAtConfirmation(t *testing.T) {
|
|||||||
stdio.EXPECT().Banner(gomock.Any())
|
stdio.EXPECT().Banner(gomock.Any())
|
||||||
stdio.EXPECT().GetConfirm(gomock.Any()).Return(false)
|
stdio.EXPECT().GetConfirm(gomock.Any()).Return(false)
|
||||||
|
|
||||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
cli := setup.Client{
|
||||||
err := cli.Setup(context.Background(), client, ¶ms)
|
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||||
require.Equal(t, internal.ErrSetupCanceled, err)
|
SetupApi: client,
|
||||||
|
}
|
||||||
|
err := cli.Setup(context.Background(), ¶ms)
|
||||||
|
require.Equal(t, setup.ErrSetupCanceled, err)
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package batcher
|
package write
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package batcher
|
package write
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package batcher_test
|
package write_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/batcher"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -45,7 +45,7 @@ func TestScanLines(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
scanner := bufio.NewScanner(strings.NewReader(tt.input))
|
scanner := bufio.NewScanner(strings.NewReader(tt.input))
|
||||||
scanner.Split(batcher.ScanLines)
|
scanner.Split(write.ScanLines)
|
||||||
got := []string{}
|
got := []string{}
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
got = append(got, scanner.Text())
|
got = append(got, scanner.Text())
|
||||||
@ -129,7 +129,7 @@ func TestBatcher_WriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
b := &batcher.BufferBatcher{
|
b := &write.BufferBatcher{
|
||||||
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
||||||
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ func TestBatcher_WriteTo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
// test the same data, but now with WriteBatches function
|
// test the same data, but now with WriteBatches function
|
||||||
t.Run("WriteTo_"+tt.name, func(t *testing.T) {
|
t.Run("WriteTo_"+tt.name, func(t *testing.T) {
|
||||||
b := &batcher.BufferBatcher{
|
b := &write.BufferBatcher{
|
||||||
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
||||||
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ func TestBatcher_WriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBatcher_WriteTimeout(t *testing.T) {
|
func TestBatcher_WriteTimeout(t *testing.T) {
|
||||||
b := &batcher.BufferBatcher{}
|
b := &write.BufferBatcher{}
|
||||||
|
|
||||||
// this mimics a reader like stdin that may never return data.
|
// this mimics a reader like stdin that may never return data.
|
||||||
r, _ := io.Pipe()
|
r, _ := io.Pipe()
|
||||||
29
internal/cmd/write/dryrun.go
Normal file
29
internal/cmd/write/dryrun.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package write
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DryRunClient struct {
|
||||||
|
cmd.CLI
|
||||||
|
LineReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DryRunClient) WriteDryRun(ctx context.Context) error {
|
||||||
|
r, closer, err := c.LineReader.Open(ctx)
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(c.StdIO, r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
42
internal/cmd/write/dryrun_test.go
Normal file
42
internal/cmd/write/dryrun_test.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package write_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteDryRun(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
inLines := `
|
||||||
|
fake line protocol 1
|
||||||
|
fake line protocol 2
|
||||||
|
fake line protocol 3
|
||||||
|
`
|
||||||
|
mockReader := bufferReader{}
|
||||||
|
_, err := io.Copy(&mockReader.buf, strings.NewReader(inLines))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
stdio := mock.NewMockStdIO(ctrl)
|
||||||
|
bytesWritten := bytes.Buffer{}
|
||||||
|
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
||||||
|
|
||||||
|
cli := write.DryRunClient{
|
||||||
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: "my-default-org"}, StdIO: stdio},
|
||||||
|
LineReader: &mockReader,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, cli.WriteDryRun(context.Background()))
|
||||||
|
require.Equal(t, inLines, bytesWritten.String())
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package linereader
|
package write
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package linereader_test
|
package write_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -14,7 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/linereader"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,8 +117,8 @@ func TestLineReader(t *testing.T) {
|
|||||||
args []string
|
args []string
|
||||||
files []string
|
files []string
|
||||||
urls []string
|
urls []string
|
||||||
format linereader.InputFormat
|
format write.InputFormat
|
||||||
compression linereader.InputCompression
|
compression write.InputCompression
|
||||||
encoding string
|
encoding string
|
||||||
headers []string
|
headers []string
|
||||||
skipHeader int
|
skipHeader int
|
||||||
@ -148,7 +148,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "read compressed LP data from file",
|
name: "read compressed LP data from file",
|
||||||
files: []string{gzipLpFileNoExt},
|
files: []string{gzipLpFileNoExt},
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
firstLineCorrection: 0,
|
firstLineCorrection: 0,
|
||||||
lines: []string{
|
lines: []string{
|
||||||
lpContents,
|
lpContents,
|
||||||
@ -157,7 +157,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "read compressed data from LP file using non-UTF encoding",
|
name: "read compressed data from LP file using non-UTF encoding",
|
||||||
files: []string{gzipLpFileNoExt},
|
files: []string{gzipLpFileNoExt},
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
encoding: "ISO_8859-1",
|
encoding: "ISO_8859-1",
|
||||||
firstLineCorrection: 0,
|
firstLineCorrection: 0,
|
||||||
lines: []string{
|
lines: []string{
|
||||||
@ -190,7 +190,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read compressed LP data from stdin",
|
name: "read compressed LP data from stdin",
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
stdIn: gzipStdin(stdInLpContents),
|
stdIn: gzipStdin(stdInLpContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
stdInLpContents,
|
stdInLpContents,
|
||||||
@ -206,7 +206,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read compressed LP data from stdin using '-' argument",
|
name: "read compressed LP data from stdin using '-' argument",
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
args: []string{"-"},
|
args: []string{"-"},
|
||||||
stdIn: gzipStdin(stdInLpContents),
|
stdIn: gzipStdin(stdInLpContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
@ -230,7 +230,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "read compressed LP data from URL",
|
name: "read compressed LP data from URL",
|
||||||
urls: []string{fmt.Sprintf("/a?data=%s&compress=true", url.QueryEscape(lpContents))},
|
urls: []string{fmt.Sprintf("/a?data=%s&compress=true", url.QueryEscape(lpContents))},
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
lines: []string{
|
lines: []string{
|
||||||
lpContents,
|
lpContents,
|
||||||
},
|
},
|
||||||
@ -260,7 +260,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "read compressed CSV data from file + transform to line protocol",
|
name: "read compressed CSV data from file + transform to line protocol",
|
||||||
files: []string{gzipCsvFileNoExt},
|
files: []string{gzipCsvFileNoExt},
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
firstLineCorrection: 0,
|
firstLineCorrection: 0,
|
||||||
lines: []string{
|
lines: []string{
|
||||||
lpContents,
|
lpContents,
|
||||||
@ -296,7 +296,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read CSV data from stdin + transform to line protocol",
|
name: "read CSV data from stdin + transform to line protocol",
|
||||||
format: linereader.InputFormatCSV,
|
format: write.InputFormatCSV,
|
||||||
stdIn: strings.NewReader(stdInCsvContents),
|
stdIn: strings.NewReader(stdInCsvContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
stdInLpContents,
|
stdInLpContents,
|
||||||
@ -304,8 +304,8 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read compressed CSV data from stdin + transform to line protocol",
|
name: "read compressed CSV data from stdin + transform to line protocol",
|
||||||
format: linereader.InputFormatCSV,
|
format: write.InputFormatCSV,
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
stdIn: gzipStdin(stdInCsvContents),
|
stdIn: gzipStdin(stdInCsvContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
stdInLpContents,
|
stdInLpContents,
|
||||||
@ -313,7 +313,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read CSV data from stdin using '-' argument + transform to line protocol",
|
name: "read CSV data from stdin using '-' argument + transform to line protocol",
|
||||||
format: linereader.InputFormatCSV,
|
format: write.InputFormatCSV,
|
||||||
args: []string{"-"},
|
args: []string{"-"},
|
||||||
stdIn: strings.NewReader(stdInCsvContents),
|
stdIn: strings.NewReader(stdInCsvContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
@ -322,8 +322,8 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read compressed CSV data from stdin using '-' argument + transform to line protocol",
|
name: "read compressed CSV data from stdin using '-' argument + transform to line protocol",
|
||||||
format: linereader.InputFormatCSV,
|
format: write.InputFormatCSV,
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
args: []string{"-"},
|
args: []string{"-"},
|
||||||
stdIn: gzipStdin(stdInCsvContents),
|
stdIn: gzipStdin(stdInCsvContents),
|
||||||
lines: []string{
|
lines: []string{
|
||||||
@ -332,7 +332,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "read CSV data from 1st argument + transform to line protocol",
|
name: "read CSV data from 1st argument + transform to line protocol",
|
||||||
format: linereader.InputFormatCSV,
|
format: write.InputFormatCSV,
|
||||||
args: []string{stdInCsvContents},
|
args: []string{stdInCsvContents},
|
||||||
lines: []string{
|
lines: []string{
|
||||||
stdInLpContents,
|
stdInLpContents,
|
||||||
@ -348,7 +348,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "read compressed CSV data from URL + transform to line protocol",
|
name: "read compressed CSV data from URL + transform to line protocol",
|
||||||
urls: []string{fmt.Sprintf("/a.csv?data=%s&compress=true", url.QueryEscape(csvContents))},
|
urls: []string{fmt.Sprintf("/a.csv?data=%s&compress=true", url.QueryEscape(csvContents))},
|
||||||
compression: linereader.InputCompressionGZIP,
|
compression: write.InputCompressionGZIP,
|
||||||
lines: []string{
|
lines: []string{
|
||||||
lpContents,
|
lpContents,
|
||||||
},
|
},
|
||||||
@ -394,7 +394,7 @@ func TestLineReader(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
r := &linereader.MultiInputLineReader{
|
r := &write.MultiInputLineReader{
|
||||||
StdIn: test.stdIn,
|
StdIn: test.stdIn,
|
||||||
HttpClient: &mockClient{t: t},
|
HttpClient: &mockClient{t: t},
|
||||||
Args: test.args,
|
Args: test.args,
|
||||||
@ -456,7 +456,7 @@ func TestLineReaderErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
r := linereader.MultiInputLineReader{
|
r := write.MultiInputLineReader{
|
||||||
HttpClient: &mockClient{t: t, fail: true},
|
HttpClient: &mockClient{t: t, fail: true},
|
||||||
Files: test.files,
|
Files: test.files,
|
||||||
URLs: test.urls,
|
URLs: test.urls,
|
||||||
@ -475,10 +475,10 @@ func TestLineReaderErrorOut(t *testing.T) {
|
|||||||
stdInContents := "_measurement,a|long:strict\nm,1\nm,1.1"
|
stdInContents := "_measurement,a|long:strict\nm,1\nm,1.1"
|
||||||
errorOut := bytes.Buffer{}
|
errorOut := bytes.Buffer{}
|
||||||
|
|
||||||
r := linereader.MultiInputLineReader{
|
r := write.MultiInputLineReader{
|
||||||
StdIn: strings.NewReader(stdInContents),
|
StdIn: strings.NewReader(stdInContents),
|
||||||
ErrorOut: &errorOut,
|
ErrorOut: &errorOut,
|
||||||
Format: linereader.InputFormatCSV,
|
Format: write.InputFormatCSV,
|
||||||
}
|
}
|
||||||
reader, closer, err := r.Open(context.Background())
|
reader, closer, err := r.Open(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package throttler
|
package write
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package throttler_test
|
package write_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/throttler"
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,9 +14,9 @@ func TestThrottlerPassthrough(t *testing.T) {
|
|||||||
// Hard to test that rate-limiting actually works, so we just check
|
// Hard to test that rate-limiting actually works, so we just check
|
||||||
// that no data is lost.
|
// that no data is lost.
|
||||||
in := "Hello world!"
|
in := "Hello world!"
|
||||||
bps, err := throttler.ToBytesPerSecond("1B/s")
|
bps, err := write.ToBytesPerSecond("1B/s")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
throttler := throttler.NewThrottler(bps)
|
throttler := write.NewThrottler(bps)
|
||||||
r := throttler.Throttle(context.Background(), strings.NewReader(in))
|
r := throttler.Throttle(context.Background(), strings.NewReader(in))
|
||||||
|
|
||||||
out := bytes.Buffer{}
|
out := bytes.Buffer{}
|
||||||
@ -71,7 +71,7 @@ func TestToBytesPerSecond(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.in, func(t *testing.T) {
|
t.Run(test.in, func(t *testing.T) {
|
||||||
bytesPerSec, err := throttler.ToBytesPerSecond(test.in)
|
bytesPerSec, err := write.ToBytesPerSecond(test.in)
|
||||||
if len(test.error) == 0 {
|
if len(test.error) == 0 {
|
||||||
require.Equal(t, test.out, float64(bytesPerSec))
|
require.Equal(t, test.out, float64(bytesPerSec))
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal
|
package write
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -7,28 +7,30 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LineReader interface {
|
type LineReader interface {
|
||||||
Open(ctx context.Context) (io.Reader, io.Closer, error)
|
Open(ctx context.Context) (io.Reader, io.Closer, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Throttler interface {
|
type RateLimiter interface {
|
||||||
Throttle(ctx context.Context, in io.Reader) io.Reader
|
Throttle(ctx context.Context, in io.Reader) io.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
type Batcher interface {
|
type BatchWriter interface {
|
||||||
WriteBatches(ctx context.Context, r io.Reader, writeFn func(batch []byte) error) error
|
WriteBatches(ctx context.Context, r io.Reader, writeFn func(batch []byte) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteClients struct {
|
type Client struct {
|
||||||
Reader LineReader
|
cmd.CLI
|
||||||
Throttler Throttler
|
api.WriteApi
|
||||||
Writer Batcher
|
LineReader
|
||||||
Client api.WriteApi
|
RateLimiter
|
||||||
|
BatchWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
type WriteParams struct {
|
type Params struct {
|
||||||
BucketID string
|
BucketID string
|
||||||
BucketName string
|
BucketName string
|
||||||
OrgID string
|
OrgID string
|
||||||
@ -38,7 +40,7 @@ type WriteParams struct {
|
|||||||
|
|
||||||
var ErrWriteCanceled = errors.New("write canceled")
|
var ErrWriteCanceled = errors.New("write canceled")
|
||||||
|
|
||||||
func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WriteParams) error {
|
func (c Client) Write(ctx context.Context, params *Params) error {
|
||||||
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
if params.OrgID == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||||
return errors.New("must specify org ID or org name")
|
return errors.New("must specify org ID or org name")
|
||||||
}
|
}
|
||||||
@ -46,7 +48,7 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
|||||||
return errors.New("must specify bucket ID or bucket name")
|
return errors.New("must specify bucket ID or bucket name")
|
||||||
}
|
}
|
||||||
|
|
||||||
r, closer, err := clients.Reader.Open(ctx)
|
r, closer, err := c.LineReader.Open(ctx)
|
||||||
if closer != nil {
|
if closer != nil {
|
||||||
defer closer.Close()
|
defer closer.Close()
|
||||||
}
|
}
|
||||||
@ -55,7 +57,7 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeBatch := func(batch []byte) error {
|
writeBatch := func(batch []byte) error {
|
||||||
req := clients.Client.PostWrite(ctx).Body(batch).ContentEncoding("gzip").Precision(params.Precision)
|
req := c.PostWrite(ctx).Body(batch).ContentEncoding("gzip").Precision(params.Precision)
|
||||||
if params.BucketID != "" {
|
if params.BucketID != "" {
|
||||||
req = req.Bucket(params.BucketID)
|
req = req.Bucket(params.BucketID)
|
||||||
} else {
|
} else {
|
||||||
@ -76,7 +78,7 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := clients.Writer.WriteBatches(ctx, clients.Throttler.Throttle(ctx, r), writeBatch); err == context.Canceled {
|
if err := c.BatchWriter.WriteBatches(ctx, c.RateLimiter.Throttle(ctx, r), writeBatch); err == context.Canceled {
|
||||||
return ErrWriteCanceled
|
return ErrWriteCanceled
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("failed to write data: %w", err)
|
return fmt.Errorf("failed to write data: %w", err)
|
||||||
@ -84,19 +86,3 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) WriteDryRun(ctx context.Context, reader LineReader) error {
|
|
||||||
r, closer, err := reader.Open(ctx)
|
|
||||||
if closer != nil {
|
|
||||||
defer closer.Close()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(c.StdIO, r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package internal_test
|
package write_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,8 +9,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/influxdata/influx-cli/v2/internal"
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||||
|
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/config"
|
"github.com/influxdata/influx-cli/v2/internal/config"
|
||||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -64,12 +65,11 @@ func TestWriteByIDs(t *testing.T) {
|
|||||||
mockThrottler := noopThrottler{}
|
mockThrottler := noopThrottler{}
|
||||||
mockBatcher := lineBatcher{}
|
mockBatcher := lineBatcher{}
|
||||||
|
|
||||||
params := internal.WriteParams{
|
params := write.Params{
|
||||||
OrgID: "12345",
|
OrgID: "12345",
|
||||||
BucketID: "98765",
|
BucketID: "98765",
|
||||||
Precision: api.WRITEPRECISION_S,
|
Precision: api.WRITEPRECISION_S,
|
||||||
}
|
}
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mock.NewMockWriteApi(ctrl)
|
client := mock.NewMockWriteApi(ctrl)
|
||||||
@ -85,14 +85,15 @@ func TestWriteByIDs(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}).Times(len(inLines))
|
}).Times(len(inLines))
|
||||||
|
|
||||||
clients := internal.WriteClients{
|
cli := write.Client{
|
||||||
Reader: &mockReader,
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: "my-default-org"}},
|
||||||
Throttler: &mockThrottler,
|
LineReader: &mockReader,
|
||||||
Writer: &mockBatcher,
|
RateLimiter: &mockThrottler,
|
||||||
Client: client,
|
BatchWriter: &mockBatcher,
|
||||||
|
WriteApi: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, cli.Write(context.Background(), &clients, ¶ms))
|
require.NoError(t, cli.Write(context.Background(), ¶ms))
|
||||||
require.Equal(t, inLines, writtenLines)
|
require.Equal(t, inLines, writtenLines)
|
||||||
require.True(t, mockThrottler.used)
|
require.True(t, mockThrottler.used)
|
||||||
}
|
}
|
||||||
@ -109,12 +110,11 @@ func TestWriteByNames(t *testing.T) {
|
|||||||
mockThrottler := noopThrottler{}
|
mockThrottler := noopThrottler{}
|
||||||
mockBatcher := lineBatcher{}
|
mockBatcher := lineBatcher{}
|
||||||
|
|
||||||
params := internal.WriteParams{
|
params := write.Params{
|
||||||
OrgName: "my-org",
|
OrgName: "my-org",
|
||||||
BucketName: "my-bucket",
|
BucketName: "my-bucket",
|
||||||
Precision: api.WRITEPRECISION_US,
|
Precision: api.WRITEPRECISION_US,
|
||||||
}
|
}
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mock.NewMockWriteApi(ctrl)
|
client := mock.NewMockWriteApi(ctrl)
|
||||||
@ -130,14 +130,15 @@ func TestWriteByNames(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}).Times(len(inLines))
|
}).Times(len(inLines))
|
||||||
|
|
||||||
clients := internal.WriteClients{
|
cli := write.Client{
|
||||||
Reader: &mockReader,
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: "my-default-org"}},
|
||||||
Throttler: &mockThrottler,
|
LineReader: &mockReader,
|
||||||
Writer: &mockBatcher,
|
RateLimiter: &mockThrottler,
|
||||||
Client: client,
|
BatchWriter: &mockBatcher,
|
||||||
|
WriteApi: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, cli.Write(context.Background(), &clients, ¶ms))
|
require.NoError(t, cli.Write(context.Background(), ¶ms))
|
||||||
require.Equal(t, inLines, writtenLines)
|
require.Equal(t, inLines, writtenLines)
|
||||||
require.True(t, mockThrottler.used)
|
require.True(t, mockThrottler.used)
|
||||||
}
|
}
|
||||||
@ -154,18 +155,18 @@ func TestWriteOrgFromConfig(t *testing.T) {
|
|||||||
mockThrottler := noopThrottler{}
|
mockThrottler := noopThrottler{}
|
||||||
mockBatcher := lineBatcher{}
|
mockBatcher := lineBatcher{}
|
||||||
|
|
||||||
params := internal.WriteParams{
|
params := write.Params{
|
||||||
BucketName: "my-bucket",
|
BucketName: "my-bucket",
|
||||||
Precision: api.WRITEPRECISION_US,
|
Precision: api.WRITEPRECISION_US,
|
||||||
}
|
}
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
|
||||||
|
|
||||||
|
defaultOrg := "my-default-org"
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
client := mock.NewMockWriteApi(ctrl)
|
client := mock.NewMockWriteApi(ctrl)
|
||||||
var writtenLines []string
|
var writtenLines []string
|
||||||
client.EXPECT().PostWrite(gomock.Any()).Return(api.ApiPostWriteRequest{ApiService: client}).Times(len(inLines))
|
client.EXPECT().PostWrite(gomock.Any()).Return(api.ApiPostWriteRequest{ApiService: client}).Times(len(inLines))
|
||||||
client.EXPECT().PostWriteExecute(tmock.MatchedBy(func(in api.ApiPostWriteRequest) bool {
|
client.EXPECT().PostWriteExecute(tmock.MatchedBy(func(in api.ApiPostWriteRequest) bool {
|
||||||
return assert.Equal(t, cli.ActiveConfig.Org, *in.GetOrg()) &&
|
return assert.Equal(t, defaultOrg, *in.GetOrg()) &&
|
||||||
assert.Equal(t, params.BucketName, *in.GetBucket()) &&
|
assert.Equal(t, params.BucketName, *in.GetBucket()) &&
|
||||||
assert.Equal(t, params.Precision, *in.GetPrecision()) &&
|
assert.Equal(t, params.Precision, *in.GetPrecision()) &&
|
||||||
assert.Equal(t, "gzip", *in.GetContentEncoding()) // Make sure the body is properly marked for compression.
|
assert.Equal(t, "gzip", *in.GetContentEncoding()) // Make sure the body is properly marked for compression.
|
||||||
@ -174,37 +175,15 @@ func TestWriteOrgFromConfig(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}).Times(len(inLines))
|
}).Times(len(inLines))
|
||||||
|
|
||||||
clients := internal.WriteClients{
|
cli := write.Client{
|
||||||
Reader: &mockReader,
|
CLI: cmd.CLI{ActiveConfig: config.Config{Org: defaultOrg}},
|
||||||
Throttler: &mockThrottler,
|
LineReader: &mockReader,
|
||||||
Writer: &mockBatcher,
|
RateLimiter: &mockThrottler,
|
||||||
Client: client,
|
BatchWriter: &mockBatcher,
|
||||||
|
WriteApi: client,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, cli.Write(context.Background(), &clients, ¶ms))
|
require.NoError(t, cli.Write(context.Background(), ¶ms))
|
||||||
require.Equal(t, inLines, writtenLines)
|
require.Equal(t, inLines, writtenLines)
|
||||||
require.True(t, mockThrottler.used)
|
require.True(t, mockThrottler.used)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteDryRun(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
inLines := `
|
|
||||||
fake line protocol 1
|
|
||||||
fake line protocol 2
|
|
||||||
fake line protocol 3
|
|
||||||
`
|
|
||||||
mockReader := bufferReader{}
|
|
||||||
_, err := io.Copy(&mockReader.buf, strings.NewReader(inLines))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
stdio := mock.NewMockStdIO(ctrl)
|
|
||||||
bytesWritten := bytes.Buffer{}
|
|
||||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
|
||||||
|
|
||||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}, StdIO: stdio}
|
|
||||||
|
|
||||||
require.NoError(t, cli.WriteDryRun(context.Background(), &mockReader))
|
|
||||||
require.Equal(t, inLines, bytesWritten.String())
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ping checks the health of a remote InfluxDB instance.
|
|
||||||
func (c *CLI) Ping(ctx context.Context, client api.HealthApi) error {
|
|
||||||
if _, err := client.GetHealth(ctx).Execute(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err := c.StdIO.Write([]byte("OK\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
20
internal/testutils/utils.go
Normal file
20
internal/testutils/utils.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MatchLines(t *testing.T, expectedLines []string, lines []string) {
|
||||||
|
var nonEmptyLines []string
|
||||||
|
for _, l := range lines {
|
||||||
|
if l != "" {
|
||||||
|
nonEmptyLines = append(nonEmptyLines, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Equal(t, len(expectedLines), len(nonEmptyLines))
|
||||||
|
for i, expected := range expectedLines {
|
||||||
|
require.Regexp(t, expected, nonEmptyLines[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user