feat: port stacks
command from influxdb
(#168)
This commit is contained in:
parent
8126bd8397
commit
40fc1845e9
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/clients"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
@ -15,7 +16,7 @@ type Client struct {
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
OutParams
|
||||
template.OutParams
|
||||
StackId string
|
||||
|
||||
IdsPerType map[string][]string
|
||||
@ -52,14 +53,14 @@ func (c Client) Export(ctx context.Context, params *Params) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export template: %w", err)
|
||||
}
|
||||
if err := params.OutParams.writeTemplate(tmpl); err != nil {
|
||||
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
|
||||
return fmt.Errorf("failed to write exported template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type AllParams struct {
|
||||
OutParams
|
||||
template.OutParams
|
||||
|
||||
OrgId string
|
||||
OrgName string
|
||||
@ -105,14 +106,14 @@ func (c Client) ExportAll(ctx context.Context, params *AllParams) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export template: %w", err)
|
||||
}
|
||||
if err := params.OutParams.writeTemplate(tmpl); err != nil {
|
||||
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
|
||||
return fmt.Errorf("failed to write exported template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type StackParams struct {
|
||||
OutParams
|
||||
template.OutParams
|
||||
StackId string
|
||||
}
|
||||
|
||||
@ -127,7 +128,7 @@ func (c Client) ExportStack(ctx context.Context, params *StackParams) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export stack %q: %w", params.StackId, err)
|
||||
}
|
||||
if err := params.OutParams.writeTemplate(tmpl); err != nil {
|
||||
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
|
||||
return fmt.Errorf("failed to write exported template: %w", err)
|
||||
}
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
package export
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type OutEncoding int
|
||||
|
||||
const (
|
||||
YamlEncoding OutEncoding = iota
|
||||
JsonEncoding
|
||||
)
|
||||
|
||||
type OutParams struct {
|
||||
Out io.Writer
|
||||
Encoding OutEncoding
|
||||
}
|
||||
|
||||
func (o OutParams) writeTemplate(template []api.TemplateEntry) error {
|
||||
switch o.Encoding {
|
||||
case JsonEncoding:
|
||||
enc := json.NewEncoder(o.Out)
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(template)
|
||||
case YamlEncoding:
|
||||
enc := yaml.NewEncoder(o.Out)
|
||||
for _, entry := range template {
|
||||
if err := enc.Encode(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("encoding %q is not recognized", o.Encoding)
|
||||
}
|
||||
return nil
|
||||
}
|
265
clients/stacks/stacks.go
Normal file
265
clients/stacks/stacks.go
Normal file
@ -0,0 +1,265 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/clients"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
clients.CLI
|
||||
api.StacksApi
|
||||
api.OrganizationsApi
|
||||
api.TemplatesApi
|
||||
}
|
||||
|
||||
type ListParams struct {
|
||||
OrgId string
|
||||
OrgName string
|
||||
|
||||
StackIds []string
|
||||
StackNames []string
|
||||
}
|
||||
|
||||
func (c Client) List(ctx context.Context, params *ListParams) error {
|
||||
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||
return clients.ErrMustSpecifyOrg
|
||||
}
|
||||
|
||||
orgId := params.OrgId
|
||||
if orgId == "" {
|
||||
orgName := params.OrgName
|
||||
if orgName == "" {
|
||||
orgName = c.ActiveConfig.Org
|
||||
}
|
||||
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
|
||||
}
|
||||
if len(res.GetOrgs()) == 0 {
|
||||
return fmt.Errorf("no organization with name %q: %w", orgName, err)
|
||||
}
|
||||
orgId = res.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
res, err := c.ListStacks(ctx).OrgID(orgId).Name(params.StackNames).StackID(params.StackIds).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list stacks: %w", err)
|
||||
}
|
||||
|
||||
return c.printStacks(stackPrintOptions{stacks: &res})
|
||||
}
|
||||
|
||||
type InitParams struct {
|
||||
OrgId string
|
||||
OrgName string
|
||||
|
||||
Name string
|
||||
Description string
|
||||
URLs []string
|
||||
}
|
||||
|
||||
func (c Client) Init(ctx context.Context, params *InitParams) error {
|
||||
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||
return clients.ErrMustSpecifyOrg
|
||||
}
|
||||
|
||||
orgId := params.OrgId
|
||||
if orgId == "" {
|
||||
orgName := params.OrgName
|
||||
if orgName == "" {
|
||||
orgName = c.ActiveConfig.Org
|
||||
}
|
||||
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
|
||||
}
|
||||
if len(res.GetOrgs()) == 0 {
|
||||
return fmt.Errorf("no organization with name %q: %w", orgName, err)
|
||||
}
|
||||
orgId = res.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
req := api.StackPostRequest{
|
||||
OrgID: orgId,
|
||||
Name: params.Name,
|
||||
Urls: params.URLs,
|
||||
}
|
||||
if params.Description != "" {
|
||||
req.Description = ¶ms.Description
|
||||
}
|
||||
|
||||
stack, err := c.CreateStack(ctx).StackPostRequest(req).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create stack %q: %w", params.Name, err)
|
||||
}
|
||||
|
||||
return c.printStacks(stackPrintOptions{stack: &stack})
|
||||
}
|
||||
|
||||
type RemoveParams struct {
|
||||
OrgId string
|
||||
OrgName string
|
||||
|
||||
Ids []string
|
||||
Force bool
|
||||
}
|
||||
|
||||
func (c Client) Remove(ctx context.Context, params *RemoveParams) error {
|
||||
if params.OrgId == "" && params.OrgName == "" && c.ActiveConfig.Org == "" {
|
||||
return clients.ErrMustSpecifyOrg
|
||||
}
|
||||
|
||||
orgId := params.OrgId
|
||||
if orgId == "" {
|
||||
orgName := params.OrgName
|
||||
if orgName == "" {
|
||||
orgName = c.ActiveConfig.Org
|
||||
}
|
||||
res, err := c.GetOrgs(ctx).Org(orgName).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup org with name %q: %w", orgName, err)
|
||||
}
|
||||
if len(res.GetOrgs()) == 0 {
|
||||
return fmt.Errorf("no organization with name %q: %w", orgName, err)
|
||||
}
|
||||
orgId = res.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
stacks, err := c.ListStacks(ctx).OrgID(orgId).StackID(params.Ids).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to look up stacks: %w", err)
|
||||
}
|
||||
|
||||
for _, stack := range stacks.Stacks {
|
||||
if err := c.printStacks(stackPrintOptions{stack: &stack}); err != nil {
|
||||
return err
|
||||
}
|
||||
if !params.Force && !c.StdIO.GetConfirm(fmt.Sprintf("Confirm removal of the stack[%s] and all associated resources", stack.Id)) {
|
||||
continue
|
||||
}
|
||||
if err := c.DeleteStack(ctx, stack.Id).OrgID(orgId).Execute(); err != nil {
|
||||
return fmt.Errorf("failed to delete stack %q: %w", stack.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AddedResource struct {
|
||||
Kind string
|
||||
Id string
|
||||
}
|
||||
|
||||
type UpdateParams struct {
|
||||
Id string
|
||||
|
||||
Name *string
|
||||
Description *string
|
||||
URLs []string
|
||||
AddedResources []AddedResource
|
||||
|
||||
template.OutParams
|
||||
}
|
||||
|
||||
func (c Client) Update(ctx context.Context, params *UpdateParams) error {
|
||||
req := api.StackPatchRequest{
|
||||
Name: params.Name,
|
||||
Description: params.Description,
|
||||
TemplateURLs: params.URLs,
|
||||
AdditionalResources: make([]api.StackPatchRequestResource, len(params.AddedResources)),
|
||||
}
|
||||
for i, r := range params.AddedResources {
|
||||
req.AdditionalResources[i] = api.StackPatchRequestResource{
|
||||
ResourceID: r.Id,
|
||||
Kind: r.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
stack, err := c.UpdateStack(ctx, params.Id).StackPatchRequest(req).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to udpate stack %q: %w", params.Id, err)
|
||||
}
|
||||
if err := c.printStacks(stackPrintOptions{stack: &stack}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Can skip exporting the updated template if no resources were added.
|
||||
if len(params.AddedResources) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !c.StdIO.GetConfirm(`Your stack now differs from your template. Applying an outdated template will revert these updates.
|
||||
Export a new template with these updates to prevent accidental changes?`) {
|
||||
return nil
|
||||
}
|
||||
|
||||
exportReq := api.TemplateExport{StackID: &stack.Id}
|
||||
tmpl, err := c.ExportTemplate(ctx).TemplateExport(exportReq).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to export stack %q: %w", stack.Id, err)
|
||||
}
|
||||
if err := params.OutParams.WriteTemplate(tmpl); err != nil {
|
||||
return fmt.Errorf("failed to write exported template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stackPrintOptions struct {
|
||||
stack *api.Stack
|
||||
stacks *api.Stacks
|
||||
}
|
||||
|
||||
func (c Client) printStacks(options stackPrintOptions) error {
|
||||
if c.PrintAsJSON {
|
||||
var v interface{}
|
||||
if options.stack != nil {
|
||||
v = options.stack
|
||||
} else {
|
||||
v = options.stacks
|
||||
}
|
||||
return c.PrintJSON(v)
|
||||
}
|
||||
|
||||
headers := []string{"ID", "OrgID", "Active", "Name", "Description", "Num Resources", "Sources", "URLs", "Created At", "Updated At"}
|
||||
var stacks []api.Stack
|
||||
if options.stacks != nil {
|
||||
stacks = options.stacks.Stacks
|
||||
}
|
||||
if options.stack != nil {
|
||||
stacks = append(stacks, *options.stack)
|
||||
}
|
||||
|
||||
var rows []map[string]interface{}
|
||||
for _, s := range stacks {
|
||||
var latestEvent api.StackEvent
|
||||
if len(s.Events) > 0 {
|
||||
sort.Slice(s.Events, func(i, j int) bool {
|
||||
return s.Events[i].UpdatedAt.Before(s.Events[j].UpdatedAt)
|
||||
})
|
||||
latestEvent = s.Events[len(s.Events)-1]
|
||||
}
|
||||
var desc string
|
||||
if latestEvent.Description != nil {
|
||||
desc = *latestEvent.Description
|
||||
}
|
||||
row := map[string]interface{}{
|
||||
"ID": s.Id,
|
||||
"OrgID": s.OrgID,
|
||||
"Active": latestEvent.EventType != "uninstall",
|
||||
"Name": latestEvent.Name,
|
||||
"Description": desc,
|
||||
"Num Resources": len(latestEvent.Resources),
|
||||
"Sources": latestEvent.Sources,
|
||||
"URLs": latestEvent.Urls,
|
||||
"Created At": s.CreatedAt,
|
||||
"Updated At": latestEvent.UpdatedAt,
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return c.PrintTable(headers, rows...)
|
||||
}
|
@ -4,11 +4,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
@ -205,7 +205,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/`,
|
||||
},
|
||||
}
|
||||
|
||||
outParams, closer, err := parseOutParams(params.out)
|
||||
cli := getCLI(ctx)
|
||||
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
|
||||
if closer != nil {
|
||||
defer closer()
|
||||
}
|
||||
@ -241,7 +242,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/`,
|
||||
}
|
||||
|
||||
client := export.Client{
|
||||
CLI: getCLI(ctx),
|
||||
CLI: cli,
|
||||
TemplatesApi: getAPI(ctx).TemplatesApi,
|
||||
}
|
||||
return client.Export(getContext(ctx), &parsedParams)
|
||||
@ -338,7 +339,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/all/
|
||||
}
|
||||
}
|
||||
|
||||
outParams, closer, err := parseOutParams(params.out)
|
||||
cli := getCLI(ctx)
|
||||
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
|
||||
if closer != nil {
|
||||
defer closer()
|
||||
}
|
||||
@ -349,7 +351,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/all/
|
||||
|
||||
apiClient := getAPI(ctx)
|
||||
client := export.Client{
|
||||
CLI: getCLI(ctx),
|
||||
CLI: cli,
|
||||
TemplatesApi: apiClient.TemplatesApi,
|
||||
OrganizationsApi: apiClient.OrganizationsApi,
|
||||
}
|
||||
@ -396,7 +398,8 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/stack/
|
||||
StackId: ctx.Args().Get(0),
|
||||
}
|
||||
|
||||
outParams, closer, err := parseOutParams(params.out)
|
||||
cli := getCLI(ctx)
|
||||
outParams, closer, err := template.ParseOutParams(params.out, cli.StdIO)
|
||||
if closer != nil {
|
||||
defer closer()
|
||||
}
|
||||
@ -407,7 +410,7 @@ https://docs.influxdata.com/influxdb/latest/reference/cli/influx/export/stack/
|
||||
|
||||
apiClient := getAPI(ctx)
|
||||
client := export.Client{
|
||||
CLI: getCLI(ctx),
|
||||
CLI: cli,
|
||||
TemplatesApi: apiClient.TemplatesApi,
|
||||
OrganizationsApi: apiClient.OrganizationsApi,
|
||||
}
|
||||
@ -422,23 +425,3 @@ func splitNonEmpty(s string) []string {
|
||||
}
|
||||
return strings.Split(s, ",")
|
||||
}
|
||||
|
||||
func parseOutParams(outPath string) (export.OutParams, func(), error) {
|
||||
if outPath == "" {
|
||||
return export.OutParams{Out: os.Stdout, Encoding: export.YamlEncoding}, nil, nil
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return export.OutParams{}, nil, fmt.Errorf("failed to open output path %q: %w", outPath, err)
|
||||
}
|
||||
params := export.OutParams{Out: f}
|
||||
switch filepath.Ext(outPath) {
|
||||
case ".json":
|
||||
params.Encoding = export.JsonEncoding
|
||||
default:
|
||||
params.Encoding = export.YamlEncoding
|
||||
}
|
||||
|
||||
return params, func() { _ = f.Close() }, nil
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ var app = cli.App{
|
||||
newV1SubCommand(),
|
||||
newAuthCommand(),
|
||||
newApplyCmd(),
|
||||
newStacksCmd(),
|
||||
},
|
||||
Before: withContext(),
|
||||
}
|
||||
|
300
cmd/influx/stacks.go
Normal file
300
cmd/influx/stacks.go
Normal file
@ -0,0 +1,300 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/clients/stacks"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func newStacksCmd() cli.Command {
|
||||
var params stacks.ListParams
|
||||
return cli.Command{
|
||||
Name: "stacks",
|
||||
Usage: "List stack(s) and associated templates. Subcommands manage stacks.",
|
||||
Description: `List stack(s) and associated templates. Subcommands manage stacks.
|
||||
|
||||
Examples:
|
||||
# list all known stacks
|
||||
influx stacks
|
||||
|
||||
# list stacks filtered by stack name
|
||||
# output here are stacks that have match at least 1 name provided
|
||||
influx stacks --stack-name=$STACK_NAME_1 --stack-name=$STACK_NAME_2
|
||||
|
||||
# list stacks filtered by stack id
|
||||
# output here are stacks that have match at least 1 ids provided
|
||||
influx stacks --stack-id=$STACK_ID_1 --stack-id=$STACK_ID_2
|
||||
|
||||
# list stacks filtered by stack id or stack name
|
||||
# output here are stacks that have match the id provided or
|
||||
# matches of the name provided
|
||||
influx stacks --stack-id=$STACK_ID --stack-name=$STACK_NAME
|
||||
|
||||
For information about Stacks and how they integrate with InfluxDB templates, see
|
||||
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/`,
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Flags: append(
|
||||
commonFlags(),
|
||||
&cli.StringFlag{
|
||||
Name: "org-id",
|
||||
Usage: "The ID of the organization",
|
||||
EnvVar: "INFLUX_ORG_ID",
|
||||
Destination: ¶ms.OrgId,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "org, o",
|
||||
Usage: "The name of the organization",
|
||||
EnvVar: "INFLUX_ORG",
|
||||
Destination: ¶ms.OrgName,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "stack-id",
|
||||
Usage: "Stack ID to filter by",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "stack-name",
|
||||
Usage: "Stack name to filter by",
|
||||
},
|
||||
),
|
||||
Subcommands: []cli.Command{
|
||||
newStacksInitCmd(),
|
||||
newStacksRemoveCmd(),
|
||||
newStacksUpdateCmd(),
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
params.StackIds = ctx.StringSlice("stack-id")
|
||||
params.StackNames = ctx.StringSlice("stack-name")
|
||||
api := getAPI(ctx)
|
||||
client := stacks.Client{
|
||||
CLI: getCLI(ctx),
|
||||
StacksApi: api.StacksApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.List(getContext(ctx), ¶ms)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newStacksInitCmd() cli.Command {
|
||||
var params stacks.InitParams
|
||||
return cli.Command{
|
||||
Name: "init",
|
||||
Usage: "Initialize a stack",
|
||||
Description: `The stack init command creates a new stack to associated templates with. A
|
||||
stack is used to make applying templates idempotent. When you apply a template
|
||||
and associate it with a stack, the stack can manage the created/updated resources
|
||||
from the template back to the platform. This enables a multitude of useful features.
|
||||
Any associated template urls will be applied when applying templates via a stack.
|
||||
|
||||
Examples:
|
||||
# Initialize a stack with a name and description
|
||||
influx stacks init -n $STACK_NAME -d $STACK_DESCRIPTION
|
||||
|
||||
# Initialize a stack with a name and urls to associate with stack.
|
||||
influx stacks init -n $STACK_NAME -u $PATH_TO_TEMPLATE
|
||||
|
||||
For information about how stacks work with InfluxDB templates, see
|
||||
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/
|
||||
and
|
||||
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/init/`,
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Flags: append(
|
||||
commonFlags(),
|
||||
&cli.StringFlag{
|
||||
Name: "org-id",
|
||||
Usage: "The ID of the organization",
|
||||
EnvVar: "INFLUX_ORG_ID",
|
||||
Destination: ¶ms.OrgId,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "org, o",
|
||||
Usage: "The name of the organization",
|
||||
EnvVar: "INFLUX_ORG",
|
||||
Destination: ¶ms.OrgName,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "stack-name, n",
|
||||
Usage: "Name given to created stack",
|
||||
Destination: ¶ms.Name,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "stack-description, d",
|
||||
Usage: "Description given to created stack",
|
||||
Destination: ¶ms.Description,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "template-url, u",
|
||||
Usage: "Template urls to associate with new stack",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
params.URLs = ctx.StringSlice("template-url")
|
||||
api := getAPI(ctx)
|
||||
client := stacks.Client{
|
||||
CLI: getCLI(ctx),
|
||||
StacksApi: api.StacksApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.Init(getContext(ctx), ¶ms)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newStacksRemoveCmd() cli.Command {
|
||||
var params stacks.RemoveParams
|
||||
return cli.Command{
|
||||
Name: "rm",
|
||||
Aliases: []string{"remove", "uninstall"},
|
||||
Usage: "Remove a stack(s) and all associated resources",
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Flags: append(
|
||||
commonFlags(),
|
||||
&cli.StringFlag{
|
||||
Name: "org-id",
|
||||
Usage: "The ID of the organization",
|
||||
EnvVar: "INFLUX_ORG_ID",
|
||||
Destination: ¶ms.OrgId,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "org, o",
|
||||
Usage: "The name of the organization",
|
||||
EnvVar: "INFLUX_ORG",
|
||||
Destination: ¶ms.OrgName,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "stack-id",
|
||||
Usage: "Stack IDs to be removed",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "Remove stack without confirmation prompt",
|
||||
Destination: ¶ms.Force,
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
params.Ids = ctx.StringSlice("stack-id")
|
||||
api := getAPI(ctx)
|
||||
client := stacks.Client{
|
||||
CLI: getCLI(ctx),
|
||||
StacksApi: api.StacksApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.Remove(getContext(ctx), ¶ms)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newStacksUpdateCmd() cli.Command {
|
||||
var params stacks.UpdateParams
|
||||
return cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update a stack",
|
||||
Description: `The stack update command updates a stack.
|
||||
|
||||
Examples:
|
||||
# Update a stack with a name and description
|
||||
influx stacks update -i $STACK_ID -n $STACK_NAME -d $STACK_DESCRIPTION
|
||||
|
||||
# Update a stack with a name and urls to associate with stack.
|
||||
influx stacks update --stack-id $STACK_ID --stack-name $STACK_NAME --template-url $PATH_TO_TEMPLATE
|
||||
|
||||
# Update stack with new resources to manage
|
||||
influx stacks update \
|
||||
--stack-id $STACK_ID \
|
||||
--addResource=Bucket=$BUCKET_ID \
|
||||
--addResource=Dashboard=$DASH_ID
|
||||
|
||||
# Update stack with new resources to manage and export stack
|
||||
# as a template
|
||||
influx stacks update \
|
||||
--stack-id $STACK_ID \
|
||||
--addResource=Bucket=$BUCKET_ID \
|
||||
--export-file /path/to/file.yml
|
||||
|
||||
For information about how stacks work with InfluxDB templates, see
|
||||
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/
|
||||
and
|
||||
https://docs.influxdata.com/influxdb/latest/reference/cli/influx/stacks/update/
|
||||
`,
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Flags: append(
|
||||
commonFlags(),
|
||||
&cli.StringFlag{
|
||||
Name: "stack-id, i",
|
||||
Usage: "ID of stack",
|
||||
Destination: ¶ms.Id,
|
||||
Required: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stack-name, n",
|
||||
Usage: "New name for the stack",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "stack-description, d",
|
||||
Usage: "New description for the stack",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "template-url, u",
|
||||
Usage: "New template URLs to associate with the stack",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "addResource",
|
||||
Usage: "Additional resources to associate with the stack",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "export-file, f",
|
||||
Usage: "Destination for exported template",
|
||||
TakesFile: true,
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
if ctx.IsSet("stack-name") {
|
||||
name := ctx.String("stack-name")
|
||||
params.Name = &name
|
||||
}
|
||||
if ctx.IsSet("stack-description") {
|
||||
desc := ctx.String("stack-description")
|
||||
params.Description = &desc
|
||||
}
|
||||
if ctx.IsSet("template-url") {
|
||||
urls := ctx.StringSlice("template-url")
|
||||
params.URLs = urls
|
||||
}
|
||||
|
||||
rawResources := ctx.StringSlice("addResource")
|
||||
for _, res := range rawResources {
|
||||
pieces := strings.Split(res, "=")
|
||||
if len(pieces) != 2 {
|
||||
return fmt.Errorf("invalid resource specification %q, must have format `KIND=ID`", res)
|
||||
}
|
||||
params.AddedResources = append(params.AddedResources, stacks.AddedResource{
|
||||
Kind: pieces[0],
|
||||
Id: pieces[1],
|
||||
})
|
||||
}
|
||||
|
||||
cli := getCLI(ctx)
|
||||
outParams, closer, err := template.ParseOutParams(ctx.String("export-file"), cli.StdIO)
|
||||
if closer != nil {
|
||||
defer closer()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params.OutParams = outParams
|
||||
|
||||
api := getAPI(ctx)
|
||||
client := stacks.Client{
|
||||
CLI: cli,
|
||||
StacksApi: api.StacksApi,
|
||||
TemplatesApi: api.TemplatesApi,
|
||||
}
|
||||
return client.Update(getContext(ctx), ¶ms)
|
||||
},
|
||||
}
|
||||
}
|
63
pkg/template/out.go
Normal file
63
pkg/template/out.go
Normal file
@ -0,0 +1,63 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type OutEncoding int
|
||||
|
||||
const (
|
||||
YamlEncoding OutEncoding = iota
|
||||
JsonEncoding
|
||||
)
|
||||
|
||||
type OutParams struct {
|
||||
Out io.Writer
|
||||
Encoding OutEncoding
|
||||
}
|
||||
|
||||
func ParseOutParams(path string, fallback io.Writer) (OutParams, func(), error) {
|
||||
if path == "" {
|
||||
return OutParams{Out: fallback, Encoding: YamlEncoding}, nil, nil
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return OutParams{}, nil, fmt.Errorf("failed to open output path %q: %w", path, err)
|
||||
}
|
||||
params := OutParams{Out: f}
|
||||
switch filepath.Ext(path) {
|
||||
case ".json":
|
||||
params.Encoding = JsonEncoding
|
||||
default:
|
||||
params.Encoding = YamlEncoding
|
||||
}
|
||||
|
||||
return params, func() { _ = f.Close() }, nil
|
||||
}
|
||||
|
||||
func (o OutParams) WriteTemplate(template []api.TemplateEntry) error {
|
||||
switch o.Encoding {
|
||||
case JsonEncoding:
|
||||
enc := json.NewEncoder(o.Out)
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(template)
|
||||
case YamlEncoding:
|
||||
enc := yaml.NewEncoder(o.Out)
|
||||
for _, entry := range template {
|
||||
if err := enc.Encode(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("encoding %q is not recognized", o.Encoding)
|
||||
}
|
||||
return nil
|
||||
}
|
124
pkg/template/out_test.go
Normal file
124
pkg/template/out_test.go
Normal file
@ -0,0 +1,124 @@
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var tmpls = []api.TemplateEntry{
|
||||
{
|
||||
ApiVersion: "api1",
|
||||
Kind: "Foo",
|
||||
Metadata: api.TemplateEntryMetadata{
|
||||
Name: "foo",
|
||||
},
|
||||
Spec: map[string]interface{}{
|
||||
"hello": "world",
|
||||
"1 + 1 =": "2",
|
||||
},
|
||||
},
|
||||
{
|
||||
ApiVersion: "api1",
|
||||
Kind: "Bar",
|
||||
Metadata: api.TemplateEntryMetadata{
|
||||
Name: "bar",
|
||||
},
|
||||
Spec: map[string]interface{}{
|
||||
"success?": "true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestOutParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("json to file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
out := filepath.Join(tmp, "test.json")
|
||||
params, closer, err := template.ParseOutParams(out, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, closer)
|
||||
defer closer()
|
||||
|
||||
require.NoError(t, params.WriteTemplate(tmpls))
|
||||
contents, err := os.ReadFile(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
var written []api.TemplateEntry
|
||||
dec := json.NewDecoder(bytes.NewReader(contents))
|
||||
require.NoError(t, dec.Decode(&written))
|
||||
|
||||
require.Equal(t, tmpls, written)
|
||||
})
|
||||
|
||||
t.Run("yaml to file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
out := filepath.Join(tmp, "test.yaml")
|
||||
params, closer, err := template.ParseOutParams(out, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, closer)
|
||||
defer closer()
|
||||
|
||||
require.NoError(t, params.WriteTemplate(tmpls))
|
||||
contents, err := os.ReadFile(out)
|
||||
require.NoError(t, err)
|
||||
|
||||
var written []api.TemplateEntry
|
||||
dec := yaml.NewDecoder(bytes.NewReader(contents))
|
||||
for {
|
||||
var e api.TemplateEntry
|
||||
err := dec.Decode(&e)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
written = append(written, e)
|
||||
}
|
||||
|
||||
require.Equal(t, tmpls, written)
|
||||
})
|
||||
|
||||
t.Run("yaml to buffer", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
out := bytes.Buffer{}
|
||||
params, closer, err := template.ParseOutParams("", &out)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, closer)
|
||||
|
||||
require.NoError(t, params.WriteTemplate(tmpls))
|
||||
|
||||
var written []api.TemplateEntry
|
||||
dec := yaml.NewDecoder(&out)
|
||||
for {
|
||||
var e api.TemplateEntry
|
||||
err := dec.Decode(&e)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
require.NoError(t, err)
|
||||
written = append(written, e)
|
||||
}
|
||||
|
||||
require.Equal(t, tmpls, written)
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user