Andrew Lee 50de408a0a
fix: add mutual exclusion for OrgId and OrgName params (#377)
Display an error when the OrgName and OrgId flags are both passed in. Only one or the other is allowed.

closes https://github.com/influxdata/influx-cli/issues/371
2022-05-13 18:50:22 -06:00

418 lines
13 KiB
Go

package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/influxdata/influx-cli/v2/clients"
"github.com/influxdata/influx-cli/v2/clients/export"
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
"github.com/influxdata/influx-cli/v2/pkg/template"
"github.com/mattn/go-isatty"
"github.com/urfave/cli"
)
func newExportCmd() cli.Command {
var params struct {
out string
stackId string
resourceType string
bucketIds string
bucketNames string
checkIds string
checkNames string
dashboardIds string
dashboardNames string
endpointIds string
endpointNames string
labelIds string
labelNames string
ruleIds string
ruleNames string
taskIds string
taskNames string
telegrafIds string
telegrafNames string
variableIds string
variableNames string
}
return cli.Command{
Name: "export",
Usage: "Export existing resources as a template",
Description: `The export command provides a mechanism to export existing resources to a
template. Each template resource kind is supported via flags.
Examples:
# export buckets by ID
influx export --buckets=$ID1,$ID2,$ID3
# export buckets, labels, and dashboards by ID
influx export \
--buckets=$BID1,$BID2,$BID3 \
--labels=$LID1,$LID2,$LID3 \
--dashboards=$DID1,$DID2,$DID3
# export all resources for a stack
influx export --stack-id $STACK_ID
# export a stack with resources not associated with the stack
influx export --stack-id $STACK_ID --buckets $BUCKET_ID
All of the resources are supported via the examples provided above. Provide the
resource flag and then provide the IDs.
For information about exporting InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/`,
Subcommands: []cli.Command{
newExportAllCmd(),
newExportStackCmd(),
},
Flags: append(
commonFlagsNoPrint(),
&cli.StringFlag{
Name: "file, f",
Usage: "Output file for created template; defaults to std out if no file provided; the extension of provided file (.yml/.json) will dictate encoding",
Destination: &params.out,
},
&cli.StringFlag{
Name: "stack-id",
Usage: "ID for stack to include in export",
Destination: &params.stackId,
},
&cli.StringFlag{
Name: "resource-type",
Usage: "If specified, strings on stdin/positional args will be treated as IDs of the given type",
Destination: &params.resourceType,
},
&cli.StringFlag{
Name: "buckets",
Usage: "List of bucket ids comma separated",
Destination: &params.bucketIds,
},
&cli.StringFlag{
Name: "checks",
Usage: "List of check ids comma separated",
Destination: &params.checkIds,
},
&cli.StringFlag{
Name: "dashboards",
Usage: "List of dashboard ids comma separated",
Destination: &params.dashboardIds,
},
&cli.StringFlag{
Name: "endpoints",
Usage: "List of notification endpoint ids comma separated",
Destination: &params.endpointIds,
},
&cli.StringFlag{
Name: "labels",
Usage: "List of label ids comma separated",
Destination: &params.labelIds,
},
&cli.StringFlag{
Name: "rules",
Usage: "List of notification rule ids comma separated",
Destination: &params.ruleIds,
},
&cli.StringFlag{
Name: "tasks",
Usage: "List of task ids comma separated",
Destination: &params.taskIds,
},
&cli.StringFlag{
Name: "telegraf-configs",
Usage: "List of telegraf config ids comma separated",
Destination: &params.telegrafIds,
},
&cli.StringFlag{
Name: "variables",
Usage: "List of variable ids comma separated",
Destination: &params.variableIds,
},
&cli.StringFlag{
Name: "bucket-names",
Usage: "List of bucket names comma separated",
Destination: &params.bucketNames,
},
&cli.StringFlag{
Name: "check-names",
Usage: "List of check names comma separated",
Destination: &params.checkNames,
},
&cli.StringFlag{
Name: "dashboard-names",
Usage: "List of dashboard names comma separated",
Destination: &params.dashboardNames,
},
&cli.StringFlag{
Name: "endpoint-names",
Usage: "List of notification endpoint names comma separated",
Destination: &params.endpointNames,
},
&cli.StringFlag{
Name: "label-names",
Usage: "List of label names comma separated",
Destination: &params.labelNames,
},
&cli.StringFlag{
Name: "rule-names",
Usage: "List of notification rule names comma separated",
Destination: &params.ruleNames,
},
&cli.StringFlag{
Name: "task-names",
Usage: "List of task names comma separated",
Destination: &params.taskNames,
},
&cli.StringFlag{
Name: "telegraf-config-names",
Usage: "List of telegraf config names comma separated",
Destination: &params.telegrafNames,
},
&cli.StringFlag{
Name: "variable-names",
Usage: "List of variable names comma separated",
Destination: &params.variableNames,
},
),
ArgsUsage: "[resource-id]...",
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Action: func(ctx *cli.Context) error {
parsedParams := export.Params{
StackId: params.stackId,
IdsPerType: map[string][]string{
"Bucket": splitNonEmpty(params.bucketIds),
"Check": splitNonEmpty(params.checkIds),
"Dashboard": splitNonEmpty(params.dashboardIds),
"NotificationEndpoint": splitNonEmpty(params.endpointIds),
"Label": splitNonEmpty(params.labelIds),
"NotificationRule": splitNonEmpty(params.ruleIds),
"Task": splitNonEmpty(params.taskIds),
"Telegraf": splitNonEmpty(params.telegrafIds),
"Variable": splitNonEmpty(params.variableIds),
},
NamesPerType: map[string][]string{
"Bucket": splitNonEmpty(params.bucketNames),
"Check": splitNonEmpty(params.checkNames),
"Dashboard": splitNonEmpty(params.dashboardNames),
"NotificationEndpoint": splitNonEmpty(params.endpointNames),
"Label": splitNonEmpty(params.labelNames),
"NotificationRule": splitNonEmpty(params.ruleNames),
"Task": splitNonEmpty(params.taskNames),
"Telegraf": splitNonEmpty(params.telegrafNames),
"Variable": splitNonEmpty(params.variableNames),
},
}
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
parsedParams.OutParams = outParams
if params.resourceType != "" {
ids := ctx.Args()
// Read any IDs from stdin.
// !IsTerminal detects when some other process is piping into this command.
if !isatty.IsTerminal(os.Stdin.Fd()) {
inBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("failed to read args from std in: %w", err)
}
ids = append(ids, strings.Fields(string(inBytes))...)
}
if _, ok := parsedParams.IdsPerType[params.resourceType]; !ok {
parsedParams.IdsPerType[params.resourceType] = []string{}
}
parsedParams.IdsPerType[params.resourceType] = append(parsedParams.IdsPerType[params.resourceType], ids...)
if _, ok := parsedParams.NamesPerType[params.resourceType]; !ok {
parsedParams.NamesPerType[params.resourceType] = []string{}
}
parsedParams.NamesPerType[params.resourceType] = append(parsedParams.NamesPerType[params.resourceType], ids...)
} else if ctx.NArg() > 0 {
return fmt.Errorf("must specify --resource-type when passing IDs as args")
}
client := export.Client{
CLI: cli,
TemplatesApi: getAPI(ctx).TemplatesApi,
}
return client.Export(getContext(ctx), &parsedParams)
},
}
}
func newExportAllCmd() cli.Command {
var params struct {
orgParams clients.OrgParams
out string
filters cli.StringSlice
}
return cli.Command{
Name: "all",
Usage: "Export all existing resources for an organization as a template",
Description: `The export all command will export all resources for an organization. The
command also provides a mechanism to filter by label name or resource kind.
Examples:
# Export all resources for an organization
influx export all --org $ORG_NAME
# Export all bucket resources
influx export all --org $ORG_NAME --filter=kind=Bucket
# Export all resources associated with label Foo
influx export all --org $ORG_NAME --filter=labelName=Foo
# Export all bucket resources and filter by label Foo
influx export all --org $ORG_NAME \
--filter kind=Bucket \
--filter labelName=Foo
# Export all bucket or dashboard resources and filter by label Foo.
# note: like filters are unioned and filter types are intersections.
# This example will export a resource if it is a dashboard or
# bucket and has an associated label of Foo.
influx export all --org $ORG_NAME \
--filter kind=Bucket \
--filter kind=Dashboard \
--filter labelName=Foo
For information about exporting InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/
and
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/all/
`,
Flags: append(
append(commonFlagsNoPrint(), getOrgFlags(&params.orgParams)...),
&cli.StringFlag{
Name: "file, f",
Usage: "Output file for created template; defaults to std out if no file provided; the extension of provided file (.yml/.json) will dictate encoding",
Destination: &params.out,
},
&cli.StringSliceFlag{
Name: "filter",
Usage: "Filter exported resources by labelName or resourceKind (format: labelName=example)",
Value: &params.filters,
},
),
Before: middleware.WithBeforeFns(withCli(), withApi(true), middleware.NoArgs),
Action: func(ctx *cli.Context) error {
parsedParams := export.AllParams{
OrgParams: params.orgParams,
}
if err := checkOrgFlags(&params.orgParams); err != nil {
return err
}
for _, filter := range params.filters.Value() {
components := strings.Split(filter, "=")
if len(components) != 2 {
return fmt.Errorf("invalid filter %q, must have format `type=example`", filter)
}
switch key, val := components[0], components[1]; key {
case "labelName":
parsedParams.LabelFilters = append(parsedParams.LabelFilters, val)
case "kind", "resourceKind":
parsedParams.KindFilters = append(parsedParams.KindFilters, val)
default:
return fmt.Errorf("invalid filter provided %q; filter must be 1 in [labelName, resourceKind]", filter)
}
}
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
parsedParams.OutParams = outParams
apiClient := getAPI(ctx)
client := export.Client{
CLI: cli,
TemplatesApi: apiClient.TemplatesApi,
OrganizationsApi: apiClient.OrganizationsApi,
}
return client.ExportAll(getContext(ctx), &parsedParams)
},
}
}
func newExportStackCmd() cli.Command {
var params struct {
out string
}
return cli.Command{
Name: "stack",
Usage: "Export all resources associated with a stack as a template",
Description: `The influx export stack command exports all resources
associated with a stack as a template. All metadata.name fields remain the same.
Example:
# Export a stack as a template
influx export stack $STACK_ID
For information about exporting InfluxDB templates, see
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/
and
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/stack/
`,
Flags: append(
commonFlagsNoPrint(),
&cli.StringFlag{
Name: "file, f",
Usage: "Output file for created template; defaults to std out if no file provided; the extension of provided file (.yml/.json) will dictate encoding",
Destination: &params.out,
},
),
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
Action: func(ctx *cli.Context) error {
if ctx.NArg() != 1 {
return fmt.Errorf("incorrect number of arguments, expected one for <stack-id>")
}
parsedParams := export.StackParams{
StackId: ctx.Args().Get(0),
}
cli := getCLI(ctx)
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
if closer != nil {
defer closer()
}
if err != nil {
return err
}
parsedParams.OutParams = outParams
apiClient := getAPI(ctx)
client := export.Client{
CLI: cli,
TemplatesApi: apiClient.TemplatesApi,
OrganizationsApi: apiClient.OrganizationsApi,
}
return client.ExportStack(getContext(ctx), &parsedParams)
},
}
}
func splitNonEmpty(s string) []string {
if s == "" {
return nil
}
return strings.Split(s, ",")
}