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 { clients.OrgParams StackIds []string StackNames []string } func (c Client) List(ctx context.Context, params *ListParams) error { orgId, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) if err != nil { return err } 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.Stacks}) } type InitParams struct { clients.OrgParams Name string Description string URLs []string } func (c Client) Init(ctx context.Context, params *InitParams) error { orgId, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) if err != nil { return err } 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 { clients.OrgParams Ids []string Force bool } func (c Client) Remove(ctx context.Context, params *RemoveParams) error { orgId, err := params.GetOrgID(ctx, c.ActiveConfig, c.OrganizationsApi) if err != nil { return err } 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 update 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.Stack } 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"} stacks := options.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...) }