
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
418 lines
13 KiB
Go
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: ¶ms.out,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "stack-id",
|
|
Usage: "ID for stack to include in export",
|
|
Destination: ¶ms.stackId,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "resource-type",
|
|
Usage: "If specified, strings on stdin/positional args will be treated as IDs of the given type",
|
|
Destination: ¶ms.resourceType,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "buckets",
|
|
Usage: "List of bucket ids comma separated",
|
|
Destination: ¶ms.bucketIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "checks",
|
|
Usage: "List of check ids comma separated",
|
|
Destination: ¶ms.checkIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "dashboards",
|
|
Usage: "List of dashboard ids comma separated",
|
|
Destination: ¶ms.dashboardIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "endpoints",
|
|
Usage: "List of notification endpoint ids comma separated",
|
|
Destination: ¶ms.endpointIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "labels",
|
|
Usage: "List of label ids comma separated",
|
|
Destination: ¶ms.labelIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "rules",
|
|
Usage: "List of notification rule ids comma separated",
|
|
Destination: ¶ms.ruleIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "tasks",
|
|
Usage: "List of task ids comma separated",
|
|
Destination: ¶ms.taskIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "telegraf-configs",
|
|
Usage: "List of telegraf config ids comma separated",
|
|
Destination: ¶ms.telegrafIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "variables",
|
|
Usage: "List of variable ids comma separated",
|
|
Destination: ¶ms.variableIds,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "bucket-names",
|
|
Usage: "List of bucket names comma separated",
|
|
Destination: ¶ms.bucketNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "check-names",
|
|
Usage: "List of check names comma separated",
|
|
Destination: ¶ms.checkNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "dashboard-names",
|
|
Usage: "List of dashboard names comma separated",
|
|
Destination: ¶ms.dashboardNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "endpoint-names",
|
|
Usage: "List of notification endpoint names comma separated",
|
|
Destination: ¶ms.endpointNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "label-names",
|
|
Usage: "List of label names comma separated",
|
|
Destination: ¶ms.labelNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "rule-names",
|
|
Usage: "List of notification rule names comma separated",
|
|
Destination: ¶ms.ruleNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "task-names",
|
|
Usage: "List of task names comma separated",
|
|
Destination: ¶ms.taskNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "telegraf-config-names",
|
|
Usage: "List of telegraf config names comma separated",
|
|
Destination: ¶ms.telegrafNames,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "variable-names",
|
|
Usage: "List of variable names comma separated",
|
|
Destination: ¶ms.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(¶ms.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: ¶ms.out,
|
|
},
|
|
&cli.StringSliceFlag{
|
|
Name: "filter",
|
|
Usage: "Filter exported resources by labelName or resourceKind (format: labelName=example)",
|
|
Value: ¶ms.filters,
|
|
},
|
|
),
|
|
Before: middleware.WithBeforeFns(withCli(), withApi(true), middleware.NoArgs),
|
|
Action: func(ctx *cli.Context) error {
|
|
parsedParams := export.AllParams{
|
|
OrgParams: params.orgParams,
|
|
}
|
|
if err := checkOrgFlags(¶ms.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: ¶ms.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, ",")
|
|
}
|