
* chore: refactor GetOrg calls * fix: less confusing overloads of 'token' in help Closes #20619 * fix: clean up iscommon function definition
484 lines
14 KiB
Go
484 lines
14 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/influxdata/influx-cli/v2/api"
|
|
"github.com/influxdata/influx-cli/v2/clients"
|
|
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
|
"github.com/influxdata/influx-cli/v2/pkg/template"
|
|
)
|
|
|
|
type Client struct {
|
|
clients.CLI
|
|
api.TemplatesApi
|
|
api.OrganizationsApi
|
|
}
|
|
|
|
type Params struct {
|
|
OrgId string
|
|
OrgName string
|
|
|
|
StackId string
|
|
Sources []template.Source
|
|
Recursive bool
|
|
|
|
Secrets map[string]string
|
|
EnvVars map[string]string
|
|
|
|
Filters []ResourceFilter
|
|
|
|
Force bool
|
|
OverwriteConflicts bool
|
|
Quiet bool
|
|
RenderTableColors bool
|
|
RenderTableBorders bool
|
|
}
|
|
|
|
type ResourceFilter struct {
|
|
Kind string
|
|
Name *string
|
|
}
|
|
|
|
func (c Client) Apply(ctx context.Context, params *Params) error {
|
|
orgID, err := c.GetOrgId(ctx, params.OrgId, params.OrgName, c.OrganizationsApi)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
templates, err := template.ReadSources(ctx, params.Sources)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req := api.TemplateApply{
|
|
DryRun: true,
|
|
OrgID: orgID,
|
|
Templates: templates,
|
|
EnvRefs: params.EnvVars,
|
|
Secrets: params.Secrets,
|
|
Actions: make([]api.TemplateApplyAction, len(params.Filters)),
|
|
}
|
|
if params.StackId != "" {
|
|
req.StackID = ¶ms.StackId
|
|
}
|
|
for i, f := range params.Filters {
|
|
req.Actions[i].Action = api.TEMPLATEAPPLYACTIONKIND_SKIP_KIND
|
|
if f.Name != nil {
|
|
req.Actions[i].Action = api.TEMPLATEAPPLYACTIONKIND_SKIP_RESOURCE
|
|
}
|
|
req.Actions[i].Properties = api.TemplateApplyActionProperties{
|
|
Kind: f.Kind,
|
|
ResourceTemplateName: f.Name,
|
|
}
|
|
}
|
|
|
|
// Initial dry-run to make the server summarize the template & report any missing env/secret values.
|
|
res, err := c.ApplyTemplate(ctx).TemplateApply(req).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check template impact: %w", err)
|
|
}
|
|
|
|
if c.StdIO.IsInteractive() && (len(res.Summary.MissingEnvRefs) > 0 || len(res.Summary.MissingSecrets) > 0) {
|
|
for _, e := range res.Summary.MissingEnvRefs {
|
|
val, err := c.StdIO.GetStringInput(fmt.Sprintf("Please provide environment reference value for key %s", e), "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.EnvRefs[e] = val
|
|
}
|
|
for _, s := range res.Summary.MissingSecrets {
|
|
val, err := c.StdIO.GetSecret(fmt.Sprintf("Please provide secret value for key %s (optional, press enter to skip)", s), 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if val != "" {
|
|
req.Secrets[s] = val
|
|
}
|
|
}
|
|
|
|
// 2nd dry-run to see the diff after resolving all env/secret values.
|
|
res, err = c.ApplyTemplate(ctx).TemplateApply(req).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check template impact: %w", err)
|
|
}
|
|
}
|
|
|
|
if !params.Quiet {
|
|
if err := c.printDiff(res.Diff, params); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if c.StdIO.IsInteractive() && !params.Force {
|
|
if confirmed := c.StdIO.GetConfirm("Confirm application of the above resources"); !confirmed {
|
|
return errors.New("aborted application of template")
|
|
}
|
|
}
|
|
|
|
if !params.OverwriteConflicts && hasConflicts(res.Diff) {
|
|
return errors.New("template has conflicts with existing resources and cannot safely apply")
|
|
}
|
|
|
|
// Flip the dry-run flag and apply the template.
|
|
req.DryRun = false
|
|
res, err = c.ApplyTemplate(ctx).TemplateApply(req).Execute()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to apply template: %w", err)
|
|
}
|
|
params.StackId = res.StackID
|
|
|
|
if !params.Quiet {
|
|
if err := c.printSummary(res.Summary, params); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c Client) printDiff(diff api.TemplateSummaryDiff, params *Params) error {
|
|
if c.PrintAsJSON {
|
|
return c.PrintJSON(diff)
|
|
}
|
|
|
|
newDiffPrinter := func(title string, headers []string) *template.DiffPrinter {
|
|
return template.NewDiffPrinter(c.StdIO, params.RenderTableColors, params.RenderTableBorders).
|
|
Title(title).
|
|
SetHeaders(append([]string{"Metadata Name", "ID", "Resource Name"}, headers...)...)
|
|
}
|
|
|
|
if labels := diff.Labels; len(labels) > 0 {
|
|
printer := newDiffPrinter("Labels", []string{"Color", "Description"})
|
|
buildRow := func(metaName string, id string, lf api.TemplateSummaryDiffLabelFields) []string {
|
|
var desc string
|
|
if lf.Description != nil {
|
|
desc = *lf.Description
|
|
}
|
|
return []string{metaName, id, lf.Name, lf.Color, desc}
|
|
}
|
|
for _, l := range labels {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(l.Id).String()
|
|
if l.Old != nil {
|
|
oldRow = buildRow(l.TemplateMetaName, hexId, *l.Old)
|
|
}
|
|
if l.New != nil {
|
|
newRow = buildRow(l.TemplateMetaName, hexId, *l.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if bkts := diff.Buckets; len(bkts) > 0 {
|
|
printer := newDiffPrinter("Buckets", []string{"Retention Period", "Description", "Schema Type", "Num Measurements"})
|
|
buildRow := func(metaName string, id string, bf api.TemplateSummaryDiffBucketFields) []string {
|
|
var desc string
|
|
if bf.Description != nil {
|
|
desc = *bf.Description
|
|
}
|
|
var retention time.Duration
|
|
if len(bf.RetentionRules) > 0 {
|
|
retention = time.Duration(bf.RetentionRules[0].EverySeconds) * time.Second
|
|
}
|
|
schemaType := api.SCHEMATYPE_IMPLICIT
|
|
if bf.SchemaType != nil {
|
|
schemaType = *bf.SchemaType
|
|
}
|
|
return []string{metaName, id, bf.Name, retention.String(), desc, schemaType.String(), strconv.Itoa(len(bf.MeasurementSchemas))}
|
|
}
|
|
for _, b := range bkts {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(b.Id).String()
|
|
if b.Old != nil {
|
|
oldRow = buildRow(b.TemplateMetaName, hexId, *b.Old)
|
|
}
|
|
if b.New != nil {
|
|
newRow = buildRow(b.TemplateMetaName, hexId, *b.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if checks := diff.Checks; len(checks) > 0 {
|
|
printer := newDiffPrinter("Checks", []string{"Description"})
|
|
buildRow := func(metaName string, id string, cf api.TemplateSummaryDiffCheckFields) []string {
|
|
var desc string
|
|
if cf.Description != nil {
|
|
desc = *cf.Description
|
|
}
|
|
return []string{metaName, id, cf.Name, desc}
|
|
}
|
|
for _, c := range checks {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(c.Id).String()
|
|
if c.Old != nil {
|
|
oldRow = buildRow(c.TemplateMetaName, hexId, *c.Old)
|
|
}
|
|
if c.New != nil {
|
|
newRow = buildRow(c.TemplateMetaName, hexId, *c.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if dashboards := diff.Dashboards; len(dashboards) > 0 {
|
|
printer := newDiffPrinter("Dashboards", []string{"Description", "Num Charts"})
|
|
buildRow := func(metaName string, id string, df api.TemplateSummaryDiffDashboardFields) []string {
|
|
var desc string
|
|
if df.Description != nil {
|
|
desc = *df.Description
|
|
}
|
|
return []string{metaName, id, df.Name, desc, strconv.Itoa(len(df.Charts))}
|
|
}
|
|
for _, d := range dashboards {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(d.Id).String()
|
|
if d.Old != nil {
|
|
oldRow = buildRow(d.TemplateMetaName, hexId, *d.Old)
|
|
}
|
|
if d.New != nil {
|
|
newRow = buildRow(d.TemplateMetaName, hexId, *d.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if endpoints := diff.NotificationEndpoints; len(endpoints) > 0 {
|
|
printer := newDiffPrinter("Notification Endpoints", nil)
|
|
buildRow := func(metaName string, id string, nef api.TemplateSummaryDiffNotificationEndpointFields) []string {
|
|
return []string{metaName, id, nef.Name}
|
|
}
|
|
for _, e := range endpoints {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(e.Id).String()
|
|
if e.Old != nil {
|
|
oldRow = buildRow(e.TemplateMetaName, hexId, *e.Old)
|
|
}
|
|
if e.New != nil {
|
|
newRow = buildRow(e.TemplateMetaName, hexId, *e.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if rules := diff.NotificationRules; len(rules) > 0 {
|
|
printer := newDiffPrinter("Notification Rules", []string{"Description", "Every", "Offset", "Endpoint Name", "Endpoint ID", "Endpoint Type"})
|
|
buildRow := func(metaName string, id string, nrf api.TemplateSummaryDiffNotificationRuleFields) []string {
|
|
var desc string
|
|
if nrf.Description != nil {
|
|
desc = *nrf.Description
|
|
}
|
|
eid := influxid.ID(nrf.EndpointID).String()
|
|
return []string{metaName, id, nrf.Name, desc, nrf.Every, nrf.Offset, nrf.EndpointName, eid, nrf.EndpointType}
|
|
}
|
|
for _, r := range rules {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(r.Id).String()
|
|
if r.Old != nil {
|
|
oldRow = buildRow(r.TemplateMetaName, hexId, *r.Old)
|
|
}
|
|
if r.New != nil {
|
|
newRow = buildRow(r.TemplateMetaName, hexId, *r.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if teles := diff.TelegrafConfigs; len(teles) > 0 {
|
|
printer := newDiffPrinter("Telegraf Configurations", []string{"Description"})
|
|
buildRow := func(metaName string, id string, tc api.TemplateSummaryTelegrafConfig) []string {
|
|
var desc string
|
|
if tc.Description != nil {
|
|
desc = *tc.Description
|
|
}
|
|
return []string{metaName, id, tc.Name, desc}
|
|
}
|
|
for _, t := range teles {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(t.Id).String()
|
|
if t.Old != nil {
|
|
oldRow = buildRow(t.TemplateMetaName, hexId, *t.Old)
|
|
}
|
|
if t.New != nil {
|
|
newRow = buildRow(t.TemplateMetaName, hexId, *t.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if tasks := diff.Tasks; len(tasks) > 0 {
|
|
printer := newDiffPrinter("Tasks", []string{"Description", "Cycle"})
|
|
buildRow := func(metaName string, id string, tf api.TemplateSummaryDiffTaskFields) []string {
|
|
var desc string
|
|
if tf.Description != nil {
|
|
desc = *tf.Description
|
|
}
|
|
var timing string
|
|
if tf.Cron != nil {
|
|
timing = *tf.Cron
|
|
} else {
|
|
offset := time.Duration(0).String()
|
|
if tf.Offset != nil {
|
|
offset = *tf.Offset
|
|
}
|
|
// If `cron` isn't set, `every` must be set
|
|
timing = fmt.Sprintf("every: %s offset: %s", *tf.Every, offset)
|
|
}
|
|
return []string{metaName, id, tf.Name, desc, timing}
|
|
}
|
|
for _, t := range tasks {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(t.Id).String()
|
|
if t.Old != nil {
|
|
oldRow = buildRow(t.TemplateMetaName, hexId, *t.Old)
|
|
}
|
|
if t.New != nil {
|
|
newRow = buildRow(t.TemplateMetaName, hexId, *t.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if vars := diff.Variables; len(vars) > 0 {
|
|
printer := newDiffPrinter("Variables", []string{"Description", "Arg Type", "Arg Values"})
|
|
buildRow := func(metaName string, id string, vf api.TemplateSummaryDiffVariableFields) []string {
|
|
var desc, argType string
|
|
if vf.Description != nil {
|
|
desc = *vf.Description
|
|
}
|
|
if vf.Args != nil {
|
|
argType = vf.Args.Type
|
|
}
|
|
return []string{metaName, id, vf.Name, desc, argType, vf.Args.Render()}
|
|
}
|
|
for _, v := range vars {
|
|
var oldRow, newRow []string
|
|
hexId := influxid.ID(v.Id).String()
|
|
if v.Old != nil {
|
|
oldRow = buildRow(v.TemplateMetaName, hexId, *v.Old)
|
|
}
|
|
if v.New != nil {
|
|
newRow = buildRow(v.TemplateMetaName, hexId, *v.New)
|
|
}
|
|
printer.AppendDiff(oldRow, newRow)
|
|
}
|
|
printer.Render()
|
|
_, _ = c.StdIO.Write([]byte("\n"))
|
|
}
|
|
|
|
if mappings := diff.LabelMappings; len(mappings) > 0 {
|
|
printer := template.NewDiffPrinter(c.StdIO, params.RenderTableColors, params.RenderTableBorders).
|
|
Title("Label Associations").
|
|
SetHeaders("Resource Type", "Resource Meta Name", "Resource Name", "Resource ID", "Label Package Name", "Label Name", "Label ID")
|
|
|
|
for _, m := range mappings {
|
|
resId := influxid.ID(m.ResourceID).String()
|
|
labelId := influxid.ID(m.LabelID).String()
|
|
row := []string{m.ResourceType, m.ResourceName, resId, m.LabelTemplateMetaName, m.LabelName, labelId}
|
|
switch m.StateStatus {
|
|
case "new":
|
|
printer.AppendDiff(nil, row)
|
|
case "remove":
|
|
printer.AppendDiff(row, nil)
|
|
default:
|
|
printer.AppendDiff(row, row)
|
|
}
|
|
}
|
|
printer.Render()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func hasConflicts(diff api.TemplateSummaryDiff) bool {
|
|
for _, l := range diff.Labels {
|
|
if l.StateStatus != "new" && l.Old != nil && l.New != nil && !reflect.DeepEqual(l.Old, l.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, b := range diff.Buckets {
|
|
if b.StateStatus != "new" && b.Old != nil && b.New != nil && !reflect.DeepEqual(b.Old, b.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, c := range diff.Checks {
|
|
if c.StateStatus != "new" && c.Old != nil && c.New != nil && !reflect.DeepEqual(c.Old, c.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, d := range diff.Dashboards {
|
|
if d.StateStatus != "new" && d.Old != nil && d.New != nil && !reflect.DeepEqual(d.Old, d.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, e := range diff.NotificationEndpoints {
|
|
if e.StateStatus != "new" && e.Old != nil && e.New != nil && !reflect.DeepEqual(e.Old, e.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, r := range diff.NotificationRules {
|
|
if r.StateStatus != "new" && r.Old != nil && r.New != nil && !reflect.DeepEqual(r.Old, r.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, t := range diff.TelegrafConfigs {
|
|
if t.StateStatus != "new" && t.Old != nil && t.New != nil && !reflect.DeepEqual(t.Old, t.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, t := range diff.Tasks {
|
|
if t.StateStatus != "new" && t.Old != nil && t.New != nil && !reflect.DeepEqual(t.Old, t.New) {
|
|
return true
|
|
}
|
|
}
|
|
for _, v := range diff.Variables {
|
|
if v.StateStatus != "new" && v.Old != nil && v.New != nil && !reflect.DeepEqual(v.Old, v.New) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c Client) printSummary(summary api.TemplateSummaryResources, params *Params) error {
|
|
if c.PrintAsJSON {
|
|
return c.PrintJSON(struct {
|
|
StackID string `json:"stackID"`
|
|
Summary api.TemplateSummaryResources
|
|
}{
|
|
StackID: params.StackId,
|
|
Summary: summary,
|
|
})
|
|
}
|
|
|
|
if err := template.PrintSummary(summary, c.StdIO, params.RenderTableColors, params.RenderTableBorders); err != nil {
|
|
return err
|
|
}
|
|
if params.StackId != "" {
|
|
_, _ = c.StdIO.Write([]byte(fmt.Sprintf("Stack ID: %s\n", params.StackId)))
|
|
}
|
|
|
|
return nil
|
|
}
|