refactor: split flat internal/ module into modules-per-cmd (#64)
This commit is contained in:
parent
d995f7d182
commit
ead7e63d41
@ -1,35 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/influxdata/influx-cli/v2/internal"
|
||||
"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/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 {
|
||||
return &cli.Command{
|
||||
Name: "bucket",
|
||||
@ -45,13 +22,16 @@ func newBucketCmd() *cli.Command {
|
||||
}
|
||||
|
||||
func newBucketCreateCmd() *cli.Command {
|
||||
params := internal.BucketsCreateParams{
|
||||
params := bucket.BucketsCreateParams{
|
||||
SchemaType: api.SCHEMATYPE_IMPLICIT,
|
||||
}
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create bucket",
|
||||
Before: withBucketsClient(),
|
||||
Name: "create",
|
||||
Usage: "Create bucket",
|
||||
Before: middleware.WithBeforeFns(
|
||||
withCli(),
|
||||
withApi(true),
|
||||
),
|
||||
Flags: append(
|
||||
commonFlags,
|
||||
&cli.StringFlag{
|
||||
@ -102,14 +82,19 @@ func newBucketCreateCmd() *cli.Command {
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
clients := getBucketsClient(ctx)
|
||||
return getCLI(ctx).BucketsCreate(ctx.Context, &clients, ¶ms)
|
||||
api := getAPI(ctx)
|
||||
client := bucket.Client{
|
||||
CLI: getCLI(ctx),
|
||||
BucketsApi: api.BucketsApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.Create(ctx.Context, ¶ms)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newBucketDeleteCmd() *cli.Command {
|
||||
var params internal.BucketsDeleteParams
|
||||
var params bucket.BucketsDeleteParams
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete bucket",
|
||||
@ -143,13 +128,19 @@ func newBucketDeleteCmd() *cli.Command {
|
||||
},
|
||||
),
|
||||
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 {
|
||||
var params internal.BucketsListParams
|
||||
var params bucket.BucketsListParams
|
||||
return &cli.Command{
|
||||
Name: "list",
|
||||
Usage: "List buckets",
|
||||
@ -184,13 +175,19 @@ func newBucketListCmd() *cli.Command {
|
||||
},
|
||||
),
|
||||
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 {
|
||||
var params internal.BucketsUpdateParams
|
||||
var params bucket.BucketsUpdateParams
|
||||
return &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update bucket",
|
||||
@ -231,7 +228,13 @@ func newBucketUpdateCmd() *cli.Command {
|
||||
},
|
||||
),
|
||||
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
|
||||
|
||||
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/pkg/cli/middleware"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
||||
@ -13,12 +13,11 @@ func withBucketSchemaClient() cli.BeforeFunc {
|
||||
withCli(),
|
||||
withApi(true),
|
||||
func(ctx *cli.Context) error {
|
||||
c := getCLI(ctx)
|
||||
client := getAPI(ctx)
|
||||
ctx.App.Metadata["measurement_schema"] = bucket_schema.Client{
|
||||
BucketApi: client.BucketsApi,
|
||||
BucketsApi: client.BucketsApi,
|
||||
BucketSchemasApi: client.BucketSchemasApi,
|
||||
CLI: c,
|
||||
CLI: getCLI(ctx),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -46,7 +45,7 @@ func newBucketSchemaCmd() *cli.Command {
|
||||
|
||||
func newBucketSchemaCreateCmd() *cli.Command {
|
||||
var params struct {
|
||||
internal.OrgBucketParams
|
||||
cmd.OrgBucketParams
|
||||
Name string
|
||||
ColumnsFile string
|
||||
ColumnsFormat bucket_schema.ColumnsFormat
|
||||
@ -100,7 +99,7 @@ func newBucketSchemaCreateCmd() *cli.Command {
|
||||
|
||||
func newBucketSchemaUpdateCmd() *cli.Command {
|
||||
var params struct {
|
||||
internal.OrgBucketParams
|
||||
cmd.OrgBucketParams
|
||||
ID influxid.ID
|
||||
Name string
|
||||
ColumnsFile string
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal"
|
||||
"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/stdio"
|
||||
"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,
|
||||
// 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)
|
||||
var err error
|
||||
if configPath == "" {
|
||||
configPath, err = config.DefaultPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return cmd.CLI{}, err
|
||||
}
|
||||
}
|
||||
configSvc := config.NewLocalConfigService(configPath)
|
||||
var activeConfig config.Config
|
||||
if ctx.IsSet(configNameFlag) {
|
||||
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 {
|
||||
return nil, err
|
||||
return cmd.CLI{}, err
|
||||
}
|
||||
|
||||
return &internal.CLI{
|
||||
return cmd.CLI{
|
||||
StdIO: stdio.TerminalStdio,
|
||||
PrintAsJSON: ctx.Bool(printJsonFlag),
|
||||
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.
|
||||
// Client parameters are pulled from the CLI context.
|
||||
func newApiClient(ctx *cli.Context, cli *internal.CLI, injectToken bool) (*api.APIClient, error) {
|
||||
cfg, err := cli.ConfigService.Active()
|
||||
func newApiClient(ctx *cli.Context, configSvc config.Service, injectToken bool) (*api.APIClient, error) {
|
||||
cfg, err := configSvc.Active()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -213,8 +213,8 @@ func withCli() cli.BeforeFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func getCLI(ctx *cli.Context) *internal.CLI {
|
||||
i, ok := ctx.App.Metadata["cli"].(*internal.CLI)
|
||||
func getCLI(ctx *cli.Context) cmd.CLI {
|
||||
i, ok := ctx.App.Metadata["cli"].(cmd.CLI)
|
||||
if !ok {
|
||||
panic("missing CLI")
|
||||
}
|
||||
@ -229,7 +229,7 @@ func withApi(injectToken bool) cli.BeforeFunc {
|
||||
|
||||
makeFn := func(ctx *cli.Context) error {
|
||||
c := getCLI(ctx)
|
||||
apiClient, err := newApiClient(ctx, c, injectToken)
|
||||
apiClient, err := newApiClient(ctx, c.ConfigService, injectToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/influxdata/influx-cli/v2/internal"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func getOrgBucketFlags(c *internal.OrgBucketParams) []cli.Flag {
|
||||
func getOrgBucketFlags(c *cmd.OrgBucketParams) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.GenericFlag{
|
||||
Name: "bucket-id",
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd/ping"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -12,7 +13,11 @@ func newPingCmd() *cli.Command {
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(false)),
|
||||
Flags: coreFlags,
|
||||
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
|
||||
|
||||
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/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newSetupCmd() *cli.Command {
|
||||
var params internal.SetupParams
|
||||
var params setup.Params
|
||||
return &cli.Command{
|
||||
Name: "setup",
|
||||
Usage: "Setup instance with initial user, org, bucket",
|
||||
@ -67,7 +67,11 @@ func newSetupCmd() *cli.Command {
|
||||
},
|
||||
),
|
||||
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"
|
||||
"os"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal"
|
||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||
"github.com/influxdata/influx-cli/v2/internal/batcher"
|
||||
"github.com/influxdata/influx-cli/v2/internal/linereader"
|
||||
"github.com/influxdata/influx-cli/v2/internal/throttler"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -18,8 +15,8 @@ import (
|
||||
type writeParams struct {
|
||||
Files cli.StringSlice
|
||||
URLs cli.StringSlice
|
||||
Format linereader.InputFormat
|
||||
Compression linereader.InputCompression
|
||||
Format write.InputFormat
|
||||
Compression write.InputCompression
|
||||
Encoding string
|
||||
|
||||
// CSV-specific options.
|
||||
@ -31,13 +28,13 @@ type writeParams struct {
|
||||
|
||||
ErrorsFile string
|
||||
MaxLineLength int
|
||||
RateLimit throttler.BytesPerSec
|
||||
RateLimit write.BytesPerSec
|
||||
|
||||
internal.WriteParams
|
||||
write.Params
|
||||
}
|
||||
|
||||
func (p *writeParams) makeLineReader(args []string, errorOut io.Writer) *linereader.MultiInputLineReader {
|
||||
return &linereader.MultiInputLineReader{
|
||||
func (p *writeParams) makeLineReader(args []string, errorOut io.Writer) *write.MultiInputLineReader {
|
||||
return &write.MultiInputLineReader{
|
||||
StdIn: os.Stdin,
|
||||
HttpClient: http.DefaultClient,
|
||||
ErrorOut: errorOut,
|
||||
@ -183,7 +180,7 @@ func (p *writeParams) Flags() []cli.Flag {
|
||||
|
||||
func newWriteCmd() *cli.Command {
|
||||
params := writeParams{
|
||||
WriteParams: internal.WriteParams{
|
||||
Params: write.Params{
|
||||
Precision: api.WRITEPRECISION_NS,
|
||||
},
|
||||
}
|
||||
@ -200,19 +197,19 @@ func newWriteCmd() *cli.Command {
|
||||
}
|
||||
defer func() { _ = errorFile.Close() }()
|
||||
|
||||
client := getAPI(ctx)
|
||||
writeClients := &internal.WriteClients{
|
||||
Client: client.WriteApi,
|
||||
Reader: params.makeLineReader(ctx.Args().Slice(), errorFile),
|
||||
Throttler: throttler.NewThrottler(params.RateLimit),
|
||||
Writer: &batcher.BufferBatcher{
|
||||
MaxFlushBytes: batcher.DefaultMaxBytes,
|
||||
MaxFlushInterval: batcher.DefaultInterval,
|
||||
client := &write.Client{
|
||||
CLI: getCLI(ctx),
|
||||
WriteApi: getAPI(ctx).WriteApi,
|
||||
LineReader: params.makeLineReader(ctx.Args().Slice(), errorFile),
|
||||
RateLimiter: write.NewThrottler(params.RateLimit),
|
||||
BatchWriter: &write.BufferBatcher{
|
||||
MaxFlushBytes: write.DefaultMaxBytes,
|
||||
MaxFlushInterval: write.DefaultInterval,
|
||||
MaxLineLength: params.MaxLineLength,
|
||||
},
|
||||
}
|
||||
|
||||
return getCLI(ctx).Write(ctx.Context, writeClients, ¶ms.WriteParams)
|
||||
return client.Write(ctx.Context, ¶ms.Params)
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
newWriteDryRun(),
|
||||
@ -222,7 +219,7 @@ func newWriteCmd() *cli.Command {
|
||||
|
||||
func newWriteDryRun() *cli.Command {
|
||||
params := writeParams{
|
||||
WriteParams: internal.WriteParams{
|
||||
Params: write.Params{
|
||||
Precision: api.WRITEPRECISION_NS,
|
||||
},
|
||||
}
|
||||
@ -240,7 +237,11 @@ func newWriteDryRun() *cli.Command {
|
||||
}
|
||||
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"
|
||||
"os"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal"
|
||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BucketApi api.BucketsApi
|
||||
BucketSchemasApi api.BucketSchemasApi
|
||||
CLI *internal.CLI
|
||||
api.BucketsApi
|
||||
api.BucketSchemasApi
|
||||
cmd.CLI
|
||||
}
|
||||
|
||||
type orgBucketID struct {
|
||||
@ -24,8 +24,7 @@ type orgBucketID struct {
|
||||
}
|
||||
|
||||
func (c Client) resolveMeasurement(ctx context.Context, ids orgBucketID, name string) (string, error) {
|
||||
res, err := c.BucketSchemasApi.
|
||||
GetMeasurementSchemas(ctx, ids.BucketID).
|
||||
res, err := c.GetMeasurementSchemas(ctx, ids.BucketID).
|
||||
OrgID(ids.OrgID).
|
||||
Name(name).
|
||||
Execute()
|
||||
@ -40,7 +39,7 @@ func (c Client) resolveMeasurement(ctx context.Context, ids orgBucketID, name st
|
||||
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() {
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
req := c.BucketApi.GetBuckets(ctx).Name(params.BucketName)
|
||||
req := c.GetBuckets(ctx).Name(params.BucketName)
|
||||
if params.OrgID.Valid() {
|
||||
req = req.OrgID(params.OrgID.String())
|
||||
} else if params.OrgName != "" {
|
||||
req = req.Org(params.OrgName)
|
||||
} else {
|
||||
req = req.Org(c.CLI.ActiveConfig.Org)
|
||||
req = req.Org(c.ActiveConfig.Org)
|
||||
}
|
||||
|
||||
resp, err := req.Execute()
|
||||
@ -100,117 +99,6 @@ func (c Client) readColumns(stdin io.Reader, f ColumnsFormat, path string) ([]ap
|
||||
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
|
||||
const (
|
||||
IDHdr = "ID"
|
||||
@ -226,8 +114,8 @@ func (c Client) printMeasurements(bucketID string, m []api.MeasurementSchema, ex
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.CLI.PrintAsJSON {
|
||||
return c.CLI.PrintJSON(m)
|
||||
if c.PrintAsJSON {
|
||||
return c.PrintJSON(m)
|
||||
}
|
||||
|
||||
var headers []string
|
||||
@ -261,7 +149,7 @@ func (c Client) printMeasurements(bucketID string, m []api.MeasurementSchema, ex
|
||||
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{}
|
||||
|
@ -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 (
|
||||
"encoding/json"
|
@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"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 (
|
||||
"bytes"
|
||||
@ -7,8 +7,9 @@ import (
|
||||
"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/cmd"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd/ping"
|
||||
"github.com/influxdata/influx-cli/v2/internal/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -24,9 +25,12 @@ func Test_PingSuccess(t *testing.T) {
|
||||
stdio := mock.NewMockStdIO(ctrl)
|
||||
bytesWritten := bytes.Buffer{}
|
||||
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())
|
||||
}
|
||||
|
||||
@ -38,9 +42,11 @@ func Test_PingFailedRequest(t *testing.T) {
|
||||
client := mock.NewMockHealthApi(ctrl)
|
||||
client.EXPECT().GetHealth(gomock.Any()).Return(api.ApiGetHealthRequest{ApiService: client})
|
||||
client.EXPECT().GetHealthExecute(gomock.Any()).Return(api.HealthCheck{}, errors.New(e))
|
||||
cli := ping.Client{
|
||||
HealthApi: client,
|
||||
}
|
||||
|
||||
cli := &internal.CLI{}
|
||||
err := cli.Ping(context.Background(), client)
|
||||
err := cli.Ping(context.Background())
|
||||
require.Error(t, err)
|
||||
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().GetHealthExecute(gomock.Any()).
|
||||
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Message: &e})
|
||||
|
||||
cli := &internal.CLI{}
|
||||
err := cli.Ping(context.Background(), client)
|
||||
cli := ping.Client{
|
||||
HealthApi: client,
|
||||
}
|
||||
err := cli.Ping(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), e)
|
||||
}
|
||||
@ -71,8 +78,10 @@ func Test_PingFailedStatusNoMessage(t *testing.T) {
|
||||
client.EXPECT().GetHealthExecute(gomock.Any()).
|
||||
Return(api.HealthCheck{}, &api.HealthCheck{Status: api.HEALTHCHECKSTATUS_FAIL, Name: name})
|
||||
|
||||
cli := &internal.CLI{}
|
||||
err := cli.Ping(context.Background(), client)
|
||||
cli := ping.Client{
|
||||
HealthApi: client,
|
||||
}
|
||||
err := cli.Ping(context.Background())
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), name)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -9,21 +9,12 @@ import (
|
||||
"time"
|
||||
|
||||
"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/duration"
|
||||
)
|
||||
|
||||
type SetupParams struct {
|
||||
Username string
|
||||
Password string
|
||||
AuthToken string
|
||||
Org string
|
||||
Bucket string
|
||||
Retention string
|
||||
Force bool
|
||||
ConfigName string
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPasswordIsTooShort = errors.New("password is too short")
|
||||
ErrAlreadySetUp = errors.New("instance has already been set up")
|
||||
@ -33,9 +24,25 @@ var (
|
||||
|
||||
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.
|
||||
checkResp, err := client.GetSetup(ctx).Execute()
|
||||
checkResp, err := c.GetSetup(ctx).Execute()
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
resp, err := client.PostSetup(ctx).OnboardingRequest(setupBody).Execute()
|
||||
resp, err := c.PostSetup(ctx).OnboardingRequest(setupBody).Execute()
|
||||
if err != nil {
|
||||
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:
|
||||
// - 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
|
||||
func (c *CLI) validateNoNameCollision(configName string) error {
|
||||
func (c Client) validateNoNameCollision(configName string) error {
|
||||
existingConfigs, err := c.ConfigService.ListConfigs()
|
||||
if err != nil {
|
||||
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.
|
||||
// Unless the 'force' parameter is set, the user will be prompted to enter any missing information
|
||||
// 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 {
|
||||
return req, ErrPasswordIsTooShort
|
||||
}
|
||||
@ -131,7 +138,7 @@ func (c *CLI) onboardingRequest(params *SetupParams) (req api.OnboardingRequest,
|
||||
if params.AuthToken != "" {
|
||||
req.Token = ¶ms.AuthToken
|
||||
}
|
||||
rpSecs := int64(InfiniteRetention)
|
||||
rpSecs := int64(bucket.InfiniteRetention)
|
||||
if params.Retention != "" {
|
||||
dur, err := duration.RawDurationToTimeDuration(params.Retention)
|
||||
if err != nil {
|
||||
@ -192,7 +199,7 @@ func (c *CLI) onboardingRequest(params *SetupParams) (req api.OnboardingRequest,
|
||||
}
|
||||
}
|
||||
if params.Retention == "" {
|
||||
infiniteStr := strconv.Itoa(InfiniteRetention)
|
||||
infiniteStr := strconv.Itoa(bucket.InfiniteRetention)
|
||||
for {
|
||||
rpStr, err := c.StdIO.GetStringInput("Please type your retention period in hours, or 0 for infinite", infiniteStr)
|
||||
if err != nil {
|
@ -1,4 +1,4 @@
|
||||
package internal_test
|
||||
package setup_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -10,11 +10,13 @@ import (
|
||||
"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/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/duration"
|
||||
"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"
|
||||
@ -31,9 +33,12 @@ func Test_SetupConfigNameCollision(t *testing.T) {
|
||||
cfg := "foo"
|
||||
configSvc := mock.NewMockConfigService(ctrl)
|
||||
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.Contains(t, err.Error(), cfg)
|
||||
require.Contains(t, err.Error(), "already exists")
|
||||
@ -49,11 +54,14 @@ func Test_SetupConfigNameRequired(t *testing.T) {
|
||||
|
||||
configSvc := mock.NewMockConfigService(ctrl)
|
||||
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.Equal(t, internal.ErrConfigNameRequired, err)
|
||||
require.Equal(t, setup.ErrConfigNameRequired, err)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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.Equal(t, internal.ErrAlreadySetUp, err)
|
||||
require.Equal(t, setup.ErrAlreadySetUp, err)
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
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.Contains(t, err.Error(), e)
|
||||
}
|
||||
@ -92,7 +106,7 @@ func Test_SetupSuccessNoninteractive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retentionSecs := int64(duration.Week.Seconds())
|
||||
params := internal.SetupParams{
|
||||
params := setup.Params{
|
||||
Username: "user",
|
||||
Password: "mysecretpassword",
|
||||
AuthToken: "mytoken",
|
||||
@ -140,14 +154,15 @@ func Test_SetupSuccessNoninteractive(t *testing.T) {
|
||||
stdio := mock.NewMockStdIO(ctrl)
|
||||
bytesWritten := bytes.Buffer{}
|
||||
stdio.EXPECT().Write(gomock.Any()).DoAndReturn(bytesWritten.Write).AnyTimes()
|
||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
||||
require.NoError(t, cli.Setup(context.Background(), client, ¶ms))
|
||||
|
||||
outLines := strings.Split(strings.TrimSpace(bytesWritten.String()), "\n")
|
||||
require.Len(t, outLines, 2)
|
||||
header, data := outLines[0], outLines[1]
|
||||
require.Regexp(t, "User\\s+Organization\\s+Bucket", header)
|
||||
require.Regexp(t, fmt.Sprintf("%s\\s+%s\\s+%s", params.Username, params.Org, params.Bucket), data)
|
||||
cli := setup.Client{
|
||||
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||
SetupApi: client,
|
||||
}
|
||||
require.NoError(t, cli.Setup(context.Background(), ¶ms))
|
||||
testutils.MatchLines(t, []string{
|
||||
`User\s+Organization\s+Bucket`,
|
||||
fmt.Sprintf(`%s\s+%s\s+%s`, params.Username, params.Org, params.Bucket),
|
||||
}, strings.Split(bytesWritten.String(), "\n"))
|
||||
}
|
||||
|
||||
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 retention period in hours, or 0 for infinite", gomock.Any()).Return(strconv.Itoa(retentionHrs), nil)
|
||||
stdio.EXPECT().GetConfirm(gomock.Any()).Return(true)
|
||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
||||
require.NoError(t, cli.Setup(context.Background(), client, &internal.SetupParams{}))
|
||||
|
||||
outLines := strings.Split(strings.TrimSpace(bytesWritten.String()), "\n")
|
||||
require.Len(t, outLines, 2)
|
||||
header, data := outLines[0], outLines[1]
|
||||
require.Regexp(t, "User\\s+Organization\\s+Bucket", header)
|
||||
require.Regexp(t, fmt.Sprintf("%s\\s+%s\\s+%s", username, org, bucket), data)
|
||||
cli := setup.Client{
|
||||
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||
SetupApi: client,
|
||||
}
|
||||
require.NoError(t, cli.Setup(context.Background(), &setup.Params{}))
|
||||
testutils.MatchLines(t, []string{
|
||||
`User\s+Organization\s+Bucket`,
|
||||
fmt.Sprintf(`%s\s+%s\s+%s`, username, org, bucket),
|
||||
}, strings.Split(bytesWritten.String(), "\n"))
|
||||
}
|
||||
|
||||
func Test_SetupPasswordParamToShort(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retentionSecs := int64(duration.Week.Seconds())
|
||||
params := internal.SetupParams{
|
||||
params := setup.Params{
|
||||
Username: "user",
|
||||
Password: "2short",
|
||||
AuthToken: "mytoken",
|
||||
@ -240,16 +256,19 @@ func Test_SetupPasswordParamToShort(t *testing.T) {
|
||||
configSvc.EXPECT().ListConfigs().Return(nil, nil)
|
||||
|
||||
stdio := mock.NewMockStdIO(ctrl)
|
||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
||||
err := cli.Setup(context.Background(), client, ¶ms)
|
||||
require.Equal(t, internal.ErrPasswordIsTooShort, err)
|
||||
cli := setup.Client{
|
||||
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||
SetupApi: client,
|
||||
}
|
||||
err := cli.Setup(context.Background(), ¶ms)
|
||||
require.Equal(t, setup.ErrPasswordIsTooShort, err)
|
||||
}
|
||||
|
||||
func Test_SetupCancelAtConfirmation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retentionSecs := int64(duration.Week.Seconds())
|
||||
params := internal.SetupParams{
|
||||
params := setup.Params{
|
||||
Username: "user",
|
||||
Password: "mysecretpassword",
|
||||
AuthToken: "mytoken",
|
||||
@ -272,7 +291,10 @@ func Test_SetupCancelAtConfirmation(t *testing.T) {
|
||||
stdio.EXPECT().Banner(gomock.Any())
|
||||
stdio.EXPECT().GetConfirm(gomock.Any()).Return(false)
|
||||
|
||||
cli := &internal.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio}
|
||||
err := cli.Setup(context.Background(), client, ¶ms)
|
||||
require.Equal(t, internal.ErrSetupCanceled, err)
|
||||
cli := setup.Client{
|
||||
CLI: cmd.CLI{ConfigService: configSvc, ActiveConfig: config.Config{Host: host}, StdIO: stdio},
|
||||
SetupApi: client,
|
||||
}
|
||||
err := cli.Setup(context.Background(), ¶ms)
|
||||
require.Equal(t, setup.ErrSetupCanceled, err)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package batcher
|
||||
package write
|
||||
|
||||
import (
|
||||
"bufio"
|
@ -1,4 +1,4 @@
|
||||
package batcher
|
||||
package write
|
||||
|
||||
import (
|
||||
"bufio"
|
@ -1,4 +1,4 @@
|
||||
package batcher_test
|
||||
package write_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"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/require"
|
||||
)
|
||||
@ -45,7 +45,7 @@ func TestScanLines(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(tt.input))
|
||||
scanner.Split(batcher.ScanLines)
|
||||
scanner.Split(write.ScanLines)
|
||||
got := []string{}
|
||||
for scanner.Scan() {
|
||||
got = append(got, scanner.Text())
|
||||
@ -129,7 +129,7 @@ func TestBatcher_WriteTo(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b := &batcher.BufferBatcher{
|
||||
b := &write.BufferBatcher{
|
||||
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
||||
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
||||
}
|
||||
@ -151,7 +151,7 @@ func TestBatcher_WriteTo(t *testing.T) {
|
||||
})
|
||||
// test the same data, but now with WriteBatches function
|
||||
t.Run("WriteTo_"+tt.name, func(t *testing.T) {
|
||||
b := &batcher.BufferBatcher{
|
||||
b := &write.BufferBatcher{
|
||||
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
||||
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
||||
}
|
||||
@ -175,7 +175,7 @@ func TestBatcher_WriteTo(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.
|
||||
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 (
|
||||
"compress/gzip"
|
@ -1,4 +1,4 @@
|
||||
package linereader_test
|
||||
package write_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal/linereader"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -117,8 +117,8 @@ func TestLineReader(t *testing.T) {
|
||||
args []string
|
||||
files []string
|
||||
urls []string
|
||||
format linereader.InputFormat
|
||||
compression linereader.InputCompression
|
||||
format write.InputFormat
|
||||
compression write.InputCompression
|
||||
encoding string
|
||||
headers []string
|
||||
skipHeader int
|
||||
@ -148,7 +148,7 @@ func TestLineReader(t *testing.T) {
|
||||
{
|
||||
name: "read compressed LP data from file",
|
||||
files: []string{gzipLpFileNoExt},
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
firstLineCorrection: 0,
|
||||
lines: []string{
|
||||
lpContents,
|
||||
@ -157,7 +157,7 @@ func TestLineReader(t *testing.T) {
|
||||
{
|
||||
name: "read compressed data from LP file using non-UTF encoding",
|
||||
files: []string{gzipLpFileNoExt},
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
encoding: "ISO_8859-1",
|
||||
firstLineCorrection: 0,
|
||||
lines: []string{
|
||||
@ -190,7 +190,7 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read compressed LP data from stdin",
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
stdIn: gzipStdin(stdInLpContents),
|
||||
lines: []string{
|
||||
stdInLpContents,
|
||||
@ -206,7 +206,7 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read compressed LP data from stdin using '-' argument",
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
args: []string{"-"},
|
||||
stdIn: gzipStdin(stdInLpContents),
|
||||
lines: []string{
|
||||
@ -230,7 +230,7 @@ func TestLineReader(t *testing.T) {
|
||||
{
|
||||
name: "read compressed LP data from URL",
|
||||
urls: []string{fmt.Sprintf("/a?data=%s&compress=true", url.QueryEscape(lpContents))},
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
lines: []string{
|
||||
lpContents,
|
||||
},
|
||||
@ -260,7 +260,7 @@ func TestLineReader(t *testing.T) {
|
||||
{
|
||||
name: "read compressed CSV data from file + transform to line protocol",
|
||||
files: []string{gzipCsvFileNoExt},
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
firstLineCorrection: 0,
|
||||
lines: []string{
|
||||
lpContents,
|
||||
@ -296,7 +296,7 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read CSV data from stdin + transform to line protocol",
|
||||
format: linereader.InputFormatCSV,
|
||||
format: write.InputFormatCSV,
|
||||
stdIn: strings.NewReader(stdInCsvContents),
|
||||
lines: []string{
|
||||
stdInLpContents,
|
||||
@ -304,8 +304,8 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read compressed CSV data from stdin + transform to line protocol",
|
||||
format: linereader.InputFormatCSV,
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
format: write.InputFormatCSV,
|
||||
compression: write.InputCompressionGZIP,
|
||||
stdIn: gzipStdin(stdInCsvContents),
|
||||
lines: []string{
|
||||
stdInLpContents,
|
||||
@ -313,7 +313,7 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read CSV data from stdin using '-' argument + transform to line protocol",
|
||||
format: linereader.InputFormatCSV,
|
||||
format: write.InputFormatCSV,
|
||||
args: []string{"-"},
|
||||
stdIn: strings.NewReader(stdInCsvContents),
|
||||
lines: []string{
|
||||
@ -322,8 +322,8 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read compressed CSV data from stdin using '-' argument + transform to line protocol",
|
||||
format: linereader.InputFormatCSV,
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
format: write.InputFormatCSV,
|
||||
compression: write.InputCompressionGZIP,
|
||||
args: []string{"-"},
|
||||
stdIn: gzipStdin(stdInCsvContents),
|
||||
lines: []string{
|
||||
@ -332,7 +332,7 @@ func TestLineReader(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "read CSV data from 1st argument + transform to line protocol",
|
||||
format: linereader.InputFormatCSV,
|
||||
format: write.InputFormatCSV,
|
||||
args: []string{stdInCsvContents},
|
||||
lines: []string{
|
||||
stdInLpContents,
|
||||
@ -348,7 +348,7 @@ func TestLineReader(t *testing.T) {
|
||||
{
|
||||
name: "read compressed CSV data from URL + transform to line protocol",
|
||||
urls: []string{fmt.Sprintf("/a.csv?data=%s&compress=true", url.QueryEscape(csvContents))},
|
||||
compression: linereader.InputCompressionGZIP,
|
||||
compression: write.InputCompressionGZIP,
|
||||
lines: []string{
|
||||
lpContents,
|
||||
},
|
||||
@ -394,7 +394,7 @@ func TestLineReader(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := &linereader.MultiInputLineReader{
|
||||
r := &write.MultiInputLineReader{
|
||||
StdIn: test.stdIn,
|
||||
HttpClient: &mockClient{t: t},
|
||||
Args: test.args,
|
||||
@ -456,7 +456,7 @@ func TestLineReaderErrors(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := linereader.MultiInputLineReader{
|
||||
r := write.MultiInputLineReader{
|
||||
HttpClient: &mockClient{t: t, fail: true},
|
||||
Files: test.files,
|
||||
URLs: test.urls,
|
||||
@ -475,10 +475,10 @@ func TestLineReaderErrorOut(t *testing.T) {
|
||||
stdInContents := "_measurement,a|long:strict\nm,1\nm,1.1"
|
||||
errorOut := bytes.Buffer{}
|
||||
|
||||
r := linereader.MultiInputLineReader{
|
||||
r := write.MultiInputLineReader{
|
||||
StdIn: strings.NewReader(stdInContents),
|
||||
ErrorOut: &errorOut,
|
||||
Format: linereader.InputFormatCSV,
|
||||
Format: write.InputFormatCSV,
|
||||
}
|
||||
reader, closer, err := r.Open(context.Background())
|
||||
require.NoError(t, err)
|
@ -1,4 +1,4 @@
|
||||
package throttler
|
||||
package write
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package throttler_test
|
||||
package write_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal/throttler"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd/write"
|
||||
"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
|
||||
// that no data is lost.
|
||||
in := "Hello world!"
|
||||
bps, err := throttler.ToBytesPerSecond("1B/s")
|
||||
bps, err := write.ToBytesPerSecond("1B/s")
|
||||
require.NoError(t, err)
|
||||
throttler := throttler.NewThrottler(bps)
|
||||
throttler := write.NewThrottler(bps)
|
||||
r := throttler.Throttle(context.Background(), strings.NewReader(in))
|
||||
|
||||
out := bytes.Buffer{}
|
||||
@ -71,7 +71,7 @@ func TestToBytesPerSecond(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
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 {
|
||||
require.Equal(t, test.out, float64(bytesPerSec))
|
||||
require.Nil(t, err)
|
@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package write
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -7,28 +7,30 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/internal/api"
|
||||
"github.com/influxdata/influx-cli/v2/internal/cmd"
|
||||
)
|
||||
|
||||
type LineReader interface {
|
||||
Open(ctx context.Context) (io.Reader, io.Closer, error)
|
||||
}
|
||||
|
||||
type Throttler interface {
|
||||
type RateLimiter interface {
|
||||
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
|
||||
}
|
||||
|
||||
type WriteClients struct {
|
||||
Reader LineReader
|
||||
Throttler Throttler
|
||||
Writer Batcher
|
||||
Client api.WriteApi
|
||||
type Client struct {
|
||||
cmd.CLI
|
||||
api.WriteApi
|
||||
LineReader
|
||||
RateLimiter
|
||||
BatchWriter
|
||||
}
|
||||
|
||||
type WriteParams struct {
|
||||
type Params struct {
|
||||
BucketID string
|
||||
BucketName string
|
||||
OrgID string
|
||||
@ -38,7 +40,7 @@ type WriteParams struct {
|
||||
|
||||
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 == "" {
|
||||
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")
|
||||
}
|
||||
|
||||
r, closer, err := clients.Reader.Open(ctx)
|
||||
r, closer, err := c.LineReader.Open(ctx)
|
||||
if closer != nil {
|
||||
defer closer.Close()
|
||||
}
|
||||
@ -55,7 +57,7 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
req = req.Bucket(params.BucketID)
|
||||
} else {
|
||||
@ -76,7 +78,7 @@ func (c *CLI) Write(ctx context.Context, clients *WriteClients, params *WritePar
|
||||
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
|
||||
} else if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 (
|
||||
"bytes"
|
||||
@ -9,8 +9,9 @@ import (
|
||||
"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/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/assert"
|
||||
@ -64,12 +65,11 @@ func TestWriteByIDs(t *testing.T) {
|
||||
mockThrottler := noopThrottler{}
|
||||
mockBatcher := lineBatcher{}
|
||||
|
||||
params := internal.WriteParams{
|
||||
params := write.Params{
|
||||
OrgID: "12345",
|
||||
BucketID: "98765",
|
||||
Precision: api.WRITEPRECISION_S,
|
||||
}
|
||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
client := mock.NewMockWriteApi(ctrl)
|
||||
@ -85,14 +85,15 @@ func TestWriteByIDs(t *testing.T) {
|
||||
return nil
|
||||
}).Times(len(inLines))
|
||||
|
||||
clients := internal.WriteClients{
|
||||
Reader: &mockReader,
|
||||
Throttler: &mockThrottler,
|
||||
Writer: &mockBatcher,
|
||||
Client: client,
|
||||
cli := write.Client{
|
||||
CLI: cmd.CLI{ActiveConfig: config.Config{Org: "my-default-org"}},
|
||||
LineReader: &mockReader,
|
||||
RateLimiter: &mockThrottler,
|
||||
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.True(t, mockThrottler.used)
|
||||
}
|
||||
@ -109,12 +110,11 @@ func TestWriteByNames(t *testing.T) {
|
||||
mockThrottler := noopThrottler{}
|
||||
mockBatcher := lineBatcher{}
|
||||
|
||||
params := internal.WriteParams{
|
||||
params := write.Params{
|
||||
OrgName: "my-org",
|
||||
BucketName: "my-bucket",
|
||||
Precision: api.WRITEPRECISION_US,
|
||||
}
|
||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
client := mock.NewMockWriteApi(ctrl)
|
||||
@ -130,14 +130,15 @@ func TestWriteByNames(t *testing.T) {
|
||||
return nil
|
||||
}).Times(len(inLines))
|
||||
|
||||
clients := internal.WriteClients{
|
||||
Reader: &mockReader,
|
||||
Throttler: &mockThrottler,
|
||||
Writer: &mockBatcher,
|
||||
Client: client,
|
||||
cli := write.Client{
|
||||
CLI: cmd.CLI{ActiveConfig: config.Config{Org: "my-default-org"}},
|
||||
LineReader: &mockReader,
|
||||
RateLimiter: &mockThrottler,
|
||||
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.True(t, mockThrottler.used)
|
||||
}
|
||||
@ -154,18 +155,18 @@ func TestWriteOrgFromConfig(t *testing.T) {
|
||||
mockThrottler := noopThrottler{}
|
||||
mockBatcher := lineBatcher{}
|
||||
|
||||
params := internal.WriteParams{
|
||||
params := write.Params{
|
||||
BucketName: "my-bucket",
|
||||
Precision: api.WRITEPRECISION_US,
|
||||
}
|
||||
cli := internal.CLI{ActiveConfig: config.Config{Org: "my-default-org"}}
|
||||
|
||||
defaultOrg := "my-default-org"
|
||||
ctrl := gomock.NewController(t)
|
||||
client := mock.NewMockWriteApi(ctrl)
|
||||
var writtenLines []string
|
||||
client.EXPECT().PostWrite(gomock.Any()).Return(api.ApiPostWriteRequest{ApiService: client}).Times(len(inLines))
|
||||
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.Precision, *in.GetPrecision()) &&
|
||||
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
|
||||
}).Times(len(inLines))
|
||||
|
||||
clients := internal.WriteClients{
|
||||
Reader: &mockReader,
|
||||
Throttler: &mockThrottler,
|
||||
Writer: &mockBatcher,
|
||||
Client: client,
|
||||
cli := write.Client{
|
||||
CLI: cmd.CLI{ActiveConfig: config.Config{Org: defaultOrg}},
|
||||
LineReader: &mockReader,
|
||||
RateLimiter: &mockThrottler,
|
||||
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.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])
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user