feat: implement template
command (#169)
This commit is contained in:
@ -184,6 +184,17 @@ func (a *TemplatesApiService) ApplyTemplateExecute(r ApiApplyTemplateRequest) (T
|
||||
body: localVarBody,
|
||||
error: _fmt.Sprintf("%s%s", errorPrefix, localVarHTTPResponse.Status),
|
||||
}
|
||||
if localVarHTTPResponse.StatusCode == 422 {
|
||||
var v TemplateSummary
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
newErr.error = _fmt.Sprintf("%s: %s", newErr.Error(), err.Error())
|
||||
return localVarReturnValue, newErr
|
||||
}
|
||||
v.SetMessage(_fmt.Sprintf("%s: %s", newErr.Error(), v.GetMessage()))
|
||||
newErr.model = &v
|
||||
return localVarReturnValue, newErr
|
||||
}
|
||||
var v Error
|
||||
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
|
@ -29,6 +29,12 @@ post:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../schemas/TemplateSummary.yml"
|
||||
"422":
|
||||
description: Template failed validation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../schemas/TemplateSummary.yml"
|
||||
default:
|
||||
description: Unexpected error
|
||||
content:
|
||||
|
@ -12,4 +12,5 @@ properties:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
x-go-field-type: '[]*int'
|
||||
required: [kind, reason, fields, indexes]
|
||||
|
44
api/error.go
44
api/error.go
@ -89,3 +89,47 @@ func (o *LineProtocolLengthError) ErrorCode() ErrorCode {
|
||||
return ERRORCODE_INVALID
|
||||
}
|
||||
}
|
||||
|
||||
func (o *TemplateSummary) Error() string {
|
||||
if len(o.Errors) == 0 {
|
||||
panic("error-less template summary used as an error!")
|
||||
}
|
||||
|
||||
var errMsg []string
|
||||
seenErrs := map[string]struct{}{}
|
||||
for _, e := range o.Errors {
|
||||
fieldPairs := make([]string, 0, len(e.Fields))
|
||||
for i, idx := range e.Indexes {
|
||||
field := e.Fields[i]
|
||||
if idx == nil || *idx == -1 {
|
||||
fieldPairs = append(fieldPairs, field)
|
||||
continue
|
||||
}
|
||||
fieldPairs = append(fieldPairs, fmt.Sprintf("%s[%d]", field, *idx))
|
||||
}
|
||||
msg := fmt.Sprintf("kind=%s field=%s reason=%q", e.Kind, strings.Join(fieldPairs, "."), e.Reason)
|
||||
if _, ok := seenErrs[msg]; ok {
|
||||
continue
|
||||
}
|
||||
seenErrs[msg] = struct{}{}
|
||||
errMsg = append(errMsg, msg)
|
||||
}
|
||||
|
||||
return strings.Join(errMsg, "\n\t")
|
||||
}
|
||||
|
||||
func (o *TemplateSummary) ErrorCode() ErrorCode {
|
||||
if len(o.Errors) == 0 {
|
||||
panic("error-less template summary used as an error!")
|
||||
}
|
||||
|
||||
return ERRORCODE_UNPROCESSABLE_ENTITY
|
||||
}
|
||||
|
||||
func (o *TemplateSummary) SetMessage(string) {
|
||||
// Placeholder to satisfy interface
|
||||
}
|
||||
|
||||
func (o TemplateSummary) GetMessage() string {
|
||||
return o.Error()
|
||||
}
|
||||
|
@ -19,14 +19,14 @@ type TemplateSummaryError struct {
|
||||
Kind string `json:"kind" yaml:"kind"`
|
||||
Reason string `json:"reason" yaml:"reason"`
|
||||
Fields []string `json:"fields" yaml:"fields"`
|
||||
Indexes []int32 `json:"indexes" yaml:"indexes"`
|
||||
Indexes []*int `json:"indexes" yaml:"indexes"`
|
||||
}
|
||||
|
||||
// NewTemplateSummaryError instantiates a new TemplateSummaryError object
|
||||
// This constructor will assign default values to properties that have it defined,
|
||||
// and makes sure properties required by API are set, but the set of arguments
|
||||
// will change when the set of required properties is changed
|
||||
func NewTemplateSummaryError(kind string, reason string, fields []string, indexes []int32) *TemplateSummaryError {
|
||||
func NewTemplateSummaryError(kind string, reason string, fields []string, indexes []*int) *TemplateSummaryError {
|
||||
this := TemplateSummaryError{}
|
||||
this.Kind = kind
|
||||
this.Reason = reason
|
||||
@ -116,9 +116,9 @@ func (o *TemplateSummaryError) SetFields(v []string) {
|
||||
}
|
||||
|
||||
// GetIndexes returns the Indexes field value
|
||||
func (o *TemplateSummaryError) GetIndexes() []int32 {
|
||||
func (o *TemplateSummaryError) GetIndexes() []*int {
|
||||
if o == nil {
|
||||
var ret []int32
|
||||
var ret []*int
|
||||
return ret
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ func (o *TemplateSummaryError) GetIndexes() []int32 {
|
||||
|
||||
// GetIndexesOk returns a tuple with the Indexes field value
|
||||
// and a boolean to check if the value has been set.
|
||||
func (o *TemplateSummaryError) GetIndexesOk() (*[]int32, bool) {
|
||||
func (o *TemplateSummaryError) GetIndexesOk() (*[]*int, bool) {
|
||||
if o == nil {
|
||||
return nil, false
|
||||
}
|
||||
@ -135,7 +135,7 @@ func (o *TemplateSummaryError) GetIndexesOk() (*[]int32, bool) {
|
||||
}
|
||||
|
||||
// SetIndexes sets field value
|
||||
func (o *TemplateSummaryError) SetIndexes(v []int32) {
|
||||
func (o *TemplateSummaryError) SetIndexes(v []*int) {
|
||||
o.Indexes = v
|
||||
}
|
||||
|
||||
|
53
api/model_template_summary_variable_args_render.go
Normal file
53
api/model_template_summary_variable_args_render.go
Normal file
@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (args *TemplateSummaryVariableArgs) Render() string {
|
||||
if args == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
switch args.Type {
|
||||
case "map":
|
||||
b, err := json.Marshal(args.Values)
|
||||
if err != nil {
|
||||
log.Printf("WARN: failed to parse map-variable args: expected JSON, got %v\n", args.Values)
|
||||
return "<parsing err>"
|
||||
}
|
||||
return string(b)
|
||||
case "constant":
|
||||
values, ok := args.Values.([]interface{})
|
||||
if !ok {
|
||||
log.Printf("WARN: failed to parse constant-variable args: expected array, got %v\n", args.Values)
|
||||
return "<parsing err>"
|
||||
}
|
||||
var out []string
|
||||
for _, v := range values {
|
||||
out = append(out, fmt.Sprintf("%q", v))
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(out, " "))
|
||||
case "query":
|
||||
values, ok := args.Values.(map[string]interface{})
|
||||
if !ok {
|
||||
log.Printf("WARN: failed to parse query-variable args: expected JSON object, got %v\n", args.Values)
|
||||
return "<parsing err>"
|
||||
}
|
||||
qVal, ok := values["query"]
|
||||
if !ok {
|
||||
log.Printf("WARN: failed to parse query-variable args: no 'query' key in %v\n", values)
|
||||
return "<parsing err>"
|
||||
}
|
||||
lVal, ok := values["language"]
|
||||
if !ok {
|
||||
log.Printf("WARN: failed to parse query-variable args: no 'language' key in %v\n", values)
|
||||
return "<parsing err>"
|
||||
}
|
||||
return fmt.Sprintf("language=%q query=%q", lVal, qVal)
|
||||
default:
|
||||
}
|
||||
return "<unknown variable type>"
|
||||
}
|
@ -2,12 +2,10 @@ package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
@ -27,7 +25,7 @@ type Params struct {
|
||||
OrgName string
|
||||
|
||||
StackId string
|
||||
Sources []TemplateSource
|
||||
Sources []template.Source
|
||||
Recursive bool
|
||||
|
||||
Secrets map[string]string
|
||||
@ -67,7 +65,7 @@ func (c Client) Apply(ctx context.Context, params *Params) error {
|
||||
orgID = orgs.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
templates, err := readTemplates(ctx, params.Sources)
|
||||
templates, err := template.ReadSources(ctx, params.Sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -158,20 +156,6 @@ func (c Client) Apply(ctx context.Context, params *Params) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readTemplates(ctx context.Context, sources []TemplateSource) ([]api.TemplateApplyTemplate, error) {
|
||||
templates := make([]api.TemplateApplyTemplate, 0, len(sources))
|
||||
for _, source := range sources {
|
||||
tmpl, err := source.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We always send the templates as JSON.
|
||||
tmpl.ContentType = "json"
|
||||
templates = append(templates, tmpl)
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
func (c Client) printDiff(diff api.TemplateSummaryDiff, params *Params) error {
|
||||
if c.PrintAsJSON {
|
||||
return c.PrintJSON(diff)
|
||||
@ -401,7 +385,7 @@ func (c Client) printDiff(diff api.TemplateSummaryDiff, params *Params) error {
|
||||
if vf.Args != nil {
|
||||
argType = vf.Args.Type
|
||||
}
|
||||
return []string{metaName, id, vf.Name, desc, argType, sprintVarArgs(vf.Args)}
|
||||
return []string{metaName, id, vf.Name, desc, argType, vf.Args.Render()}
|
||||
}
|
||||
for _, v := range vars {
|
||||
var oldRow, newRow []string
|
||||
@ -442,46 +426,6 @@ func (c Client) printDiff(diff api.TemplateSummaryDiff, params *Params) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sprintVarArgs(args *api.TemplateSummaryVariableArgs) string {
|
||||
if args == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
switch args.Type {
|
||||
case "map":
|
||||
b, err := json.Marshal(args.Values)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(b)
|
||||
case "constant":
|
||||
values, ok := args.Values.([]interface{})
|
||||
if !ok {
|
||||
return "[]"
|
||||
}
|
||||
var out []string
|
||||
for _, v := range values {
|
||||
out = append(out, fmt.Sprintf("%q", v))
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(out, " "))
|
||||
case "query":
|
||||
values, ok := args.Values.(map[string]interface{})
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
qVal, ok := values["query"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
lVal, ok := values["language"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("language=%q query=%q", lVal, qVal)
|
||||
default:
|
||||
}
|
||||
return "unknown variable argument"
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -542,185 +486,9 @@ func (c Client) printSummary(summary api.TemplateSummaryResources, params *Param
|
||||
})
|
||||
}
|
||||
|
||||
newPrinter := func(title string, headers []string) *template.TablePrinter {
|
||||
return template.NewTablePrinter(c.StdIO, params.RenderTableColors, params.RenderTableBorders).
|
||||
Title(title).
|
||||
SetHeaders(append([]string{"Package Name", "ID", "Resource Name"}, headers...)...)
|
||||
if err := template.PrintSummary(summary, c.StdIO, params.RenderTableColors, params.RenderTableBorders); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if labels := summary.Labels; len(labels) > 0 {
|
||||
printer := newPrinter("LABELS", []string{"Description", "Color"})
|
||||
for _, l := range labels {
|
||||
id := influxid.ID(l.Id).String()
|
||||
var desc string
|
||||
if l.Properties.Description != nil {
|
||||
desc = *l.Properties.Description
|
||||
}
|
||||
printer.Append([]string{l.TemplateMetaName, id, l.Name, desc, l.Properties.Color})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if buckets := summary.Buckets; len(buckets) > 0 {
|
||||
printer := newPrinter("BUCKETS", []string{"Retention", "Description", "Schema Type"})
|
||||
for _, b := range buckets {
|
||||
id := influxid.ID(b.Id).String()
|
||||
var desc string
|
||||
if b.Description != nil {
|
||||
desc = *b.Description
|
||||
}
|
||||
rp := "inf"
|
||||
if b.RetentionPeriod != 0 {
|
||||
rp = time.Duration(b.RetentionPeriod).String()
|
||||
}
|
||||
schemaType := api.SCHEMATYPE_IMPLICIT
|
||||
if b.SchemaType != nil {
|
||||
schemaType = *b.SchemaType
|
||||
}
|
||||
printer.Append([]string{b.TemplateMetaName, id, b.Name, rp, desc, schemaType.String()})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if checks := summary.Checks; len(checks) > 0 {
|
||||
printer := newPrinter("CHECKS", []string{"Description"})
|
||||
for _, c := range checks {
|
||||
id := influxid.ID(c.Id).String()
|
||||
var desc string
|
||||
if c.Description != nil {
|
||||
desc = *c.Description
|
||||
}
|
||||
printer.Append([]string{c.TemplateMetaName, id, c.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if dashboards := summary.Dashboards; len(dashboards) > 0 {
|
||||
printer := newPrinter("DASHBOARDS", []string{"Description"})
|
||||
for _, d := range dashboards {
|
||||
id := influxid.ID(d.Id).String()
|
||||
var desc string
|
||||
if d.Description != nil {
|
||||
desc = *d.Description
|
||||
}
|
||||
printer.Append([]string{d.TemplateMetaName, id, d.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if endpoints := summary.NotificationEndpoints; len(endpoints) > 0 {
|
||||
printer := newPrinter("NOTIFICATION ENDPOINTS", []string{"Description", "Status"})
|
||||
for _, e := range endpoints {
|
||||
id := influxid.ID(e.Id).String()
|
||||
var desc string
|
||||
if e.Description != nil {
|
||||
desc = *e.Description
|
||||
}
|
||||
printer.Append([]string{e.TemplateMetaName, id, e.Name, desc, e.Status})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if rules := summary.NotificationRules; len(rules) > 0 {
|
||||
printer := newPrinter("NOTIFICATION RULES", []string{"Description", "Every", "Offset", "Endpoint Name", "Endpoint ID", "Endpoint Type"})
|
||||
for _, r := range rules {
|
||||
id := influxid.ID(r.Id).String()
|
||||
eid := influxid.ID(r.EndpointID).String()
|
||||
var desc string
|
||||
if r.Description != nil {
|
||||
desc = *r.Description
|
||||
}
|
||||
printer.Append([]string{r.TemplateMetaName, id, r.Name, desc, r.Every, r.Offset, r.EndpointTemplateMetaName, eid, r.EndpointType})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if tasks := summary.Tasks; len(tasks) > 0 {
|
||||
printer := newPrinter("TASKS", []string{"Description", "Cycle"})
|
||||
for _, t := range tasks {
|
||||
id := influxid.ID(t.Id).String()
|
||||
var desc string
|
||||
if t.Description != nil {
|
||||
desc = *t.Description
|
||||
}
|
||||
var timing string
|
||||
if t.Cron != nil {
|
||||
timing = *t.Cron
|
||||
} else {
|
||||
offset := time.Duration(0).String()
|
||||
if t.Offset != nil {
|
||||
offset = *t.Offset
|
||||
}
|
||||
// If `cron` isn't set, `every` must be set
|
||||
timing = fmt.Sprintf("every: %s offset: %s", *t.Every, offset)
|
||||
}
|
||||
printer.Append([]string{t.TemplateMetaName, id, t.Name, desc, timing})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if teles := summary.TelegrafConfigs; len(teles) > 0 {
|
||||
printer := newPrinter("TELEGRAF CONFIGS", []string{"Description"})
|
||||
for _, t := range teles {
|
||||
var desc string
|
||||
if t.TelegrafConfig.Description != nil {
|
||||
desc = *t.TelegrafConfig.Description
|
||||
}
|
||||
printer.Append([]string{t.TemplateMetaName, t.TelegrafConfig.Id, t.TelegrafConfig.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if vars := summary.Variables; len(vars) > 0 {
|
||||
printer := newPrinter("VARIABLES", []string{"Description", "Arg Type", "Arg Values"})
|
||||
for _, v := range vars {
|
||||
id := influxid.ID(v.Id).String()
|
||||
var desc string
|
||||
if v.Description != nil {
|
||||
desc = *v.Description
|
||||
}
|
||||
var argType string
|
||||
if v.Arguments != nil {
|
||||
argType = v.Arguments.Type
|
||||
}
|
||||
printer.Append([]string{v.TemplateMetaName, id, v.Name, desc, argType, sprintVarArgs(v.Arguments)})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if mappings := summary.LabelMappings; len(mappings) > 0 {
|
||||
printer := template.NewTablePrinter(c.StdIO, params.RenderTableColors, params.RenderTableBorders).
|
||||
Title("LABEL ASSOCIATIONS").
|
||||
SetHeaders("Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID")
|
||||
for _, m := range mappings {
|
||||
rid := influxid.ID(m.ResourceID).String()
|
||||
lid := influxid.ID(m.LabelID).String()
|
||||
printer.Append([]string{m.ResourceType, m.ResourceName, rid, m.LabelName, lid})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if secrets := summary.MissingSecrets; len(secrets) > 0 {
|
||||
printer := template.NewTablePrinter(c.StdIO, params.RenderTableColors, params.RenderTableBorders).
|
||||
Title("MISSING SECRETS").
|
||||
SetHeaders("Secret Key")
|
||||
for _, sk := range secrets {
|
||||
printer.Append([]string{sk})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = c.StdIO.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if params.StackId != "" {
|
||||
_, _ = c.StdIO.Write([]byte(fmt.Sprintf("Stack ID: %s\n", params.StackId)))
|
||||
}
|
||||
|
119
clients/template/template.go
Normal file
119
clients/template/template.go
Normal file
@ -0,0 +1,119 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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.TemplatesApi
|
||||
api.OrganizationsApi
|
||||
}
|
||||
|
||||
type SummarizeParams struct {
|
||||
OrgId string
|
||||
OrgName string
|
||||
|
||||
Sources []template.Source
|
||||
|
||||
RenderTableColors bool
|
||||
RenderTableBorders bool
|
||||
}
|
||||
|
||||
func (c Client) Summarize(ctx context.Context, params *SummarizeParams) 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
|
||||
}
|
||||
orgs, err := c.GetOrgs(ctx).Org(orgName).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ID for org %q: %w", orgName, err)
|
||||
}
|
||||
if len(orgs.GetOrgs()) == 0 {
|
||||
return fmt.Errorf("no orgs found with name %q: %w", orgName, err)
|
||||
}
|
||||
orgID = orgs.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
templates, err := template.ReadSources(ctx, params.Sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute a dry-run to make the server summarize the template(s).
|
||||
req := api.TemplateApply{
|
||||
DryRun: true,
|
||||
OrgID: orgID,
|
||||
Templates: templates,
|
||||
}
|
||||
res, err := c.ApplyTemplate(ctx).TemplateApply(req).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to summarize template: %w", err)
|
||||
}
|
||||
|
||||
if c.PrintAsJSON {
|
||||
return c.PrintJSON(res.Summary)
|
||||
}
|
||||
return template.PrintSummary(res.Summary, c.StdIO, params.RenderTableColors, params.RenderTableBorders)
|
||||
}
|
||||
|
||||
type ValidateParams struct {
|
||||
OrgId string
|
||||
OrgName string
|
||||
|
||||
Sources []template.Source
|
||||
}
|
||||
|
||||
func (c Client) Validate(ctx context.Context, params *ValidateParams) 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
|
||||
}
|
||||
orgs, err := c.GetOrgs(ctx).Org(orgName).Execute()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get ID for org %q: %w", orgName, err)
|
||||
}
|
||||
if len(orgs.GetOrgs()) == 0 {
|
||||
return fmt.Errorf("no orgs found with name %q: %w", orgName, err)
|
||||
}
|
||||
orgID = orgs.GetOrgs()[0].GetId()
|
||||
}
|
||||
|
||||
templates, err := template.ReadSources(ctx, params.Sources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Execute a dry-run to make the server summarize the template(s).
|
||||
req := api.TemplateApply{
|
||||
DryRun: true,
|
||||
OrgID: orgID,
|
||||
Templates: templates,
|
||||
}
|
||||
_, err = c.ApplyTemplate(ctx).TemplateApply(req).Execute()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if apiErr, ok := err.(api.GenericOpenAPIError); ok {
|
||||
if summary, ok := apiErr.Model().(*api.TemplateSummary); ok {
|
||||
return fmt.Errorf("template failed validation:\n\t%s", summary)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("failed to validate template: %w", err)
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/clients/apply"
|
||||
"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"
|
||||
)
|
||||
@ -21,7 +22,7 @@ func newApplyCmd() cli.Command {
|
||||
inPaths cli.StringSlice
|
||||
inUrls cli.StringSlice
|
||||
recursive bool
|
||||
encoding apply.TemplateEncoding
|
||||
encoding template.Encoding
|
||||
noColor bool
|
||||
noTableBorders bool
|
||||
quiet bool
|
||||
@ -192,9 +193,9 @@ https://github.com/influxdata/community-templates.
|
||||
log.Println("WARN: Passing URLs via -f/--file is deprecated, please use -u/--template-url instead")
|
||||
deprecationShown = true
|
||||
}
|
||||
parsedParams.Sources = append(parsedParams.Sources, apply.SourceFromURL(u, params.encoding))
|
||||
parsedParams.Sources = append(parsedParams.Sources, template.SourceFromURL(u, params.encoding))
|
||||
} else {
|
||||
fileSources, err := apply.SourcesFromPath(in, params.recursive, params.encoding)
|
||||
fileSources, err := template.SourcesFromPath(in, params.recursive, params.encoding)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -206,10 +207,10 @@ https://github.com/influxdata/community-templates.
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse input URL %q: %w", in, err)
|
||||
}
|
||||
parsedParams.Sources = append(parsedParams.Sources, apply.SourceFromURL(u, params.encoding))
|
||||
parsedParams.Sources = append(parsedParams.Sources, template.SourceFromURL(u, params.encoding))
|
||||
}
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
parsedParams.Sources = append(parsedParams.Sources, apply.SourceFromReader(os.Stdin, params.encoding))
|
||||
parsedParams.Sources = append(parsedParams.Sources, template.SourceFromReader(os.Stdin, params.encoding))
|
||||
}
|
||||
|
||||
// Parse env and secret values.
|
||||
|
@ -52,6 +52,7 @@ var app = cli.App{
|
||||
newAuthCommand(),
|
||||
newApplyCmd(),
|
||||
newStacksCmd(),
|
||||
newTemplateCmd(),
|
||||
},
|
||||
Before: withContext(),
|
||||
}
|
||||
|
173
cmd/influx/template.go
Normal file
173
cmd/influx/template.go
Normal file
@ -0,0 +1,173 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/clients/template"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
|
||||
pkgtmpl "github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type templateParams struct {
|
||||
orgId string
|
||||
orgName string
|
||||
files cli.StringSlice
|
||||
urls cli.StringSlice
|
||||
recurse bool
|
||||
encoding pkgtmpl.Encoding
|
||||
}
|
||||
|
||||
func templateFlags(params *templateParams) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&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: "file, f",
|
||||
Usage: "Path to template file; Supports file paths or (deprecated) HTTP(S) URLs",
|
||||
TakesFile: true,
|
||||
Value: ¶ms.files,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "template-url, u",
|
||||
Usage: "HTTP(S) URL to template file",
|
||||
Value: ¶ms.urls,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "recurse, R",
|
||||
Usage: "Process the directory used in -f, --file recursively. Useful when you want to manage related templates organized within the same directory.",
|
||||
Destination: ¶ms.recurse,
|
||||
},
|
||||
&cli.GenericFlag{
|
||||
Name: "encoding, e",
|
||||
Usage: "Encoding for the input stream. If a file is provided will gather encoding type from file extension. If extension provided will override.",
|
||||
Value: ¶ms.encoding,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (params templateParams) parseSources() ([]pkgtmpl.Source, error) {
|
||||
var deprecationShown bool
|
||||
var sources []pkgtmpl.Source
|
||||
for _, in := range params.files.Value() {
|
||||
// Heuristic to guess what's a URL and what's a local file.
|
||||
// TODO: Remove this once we stop supporting URLs in the --file arg.
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse input path %q: %w", in, err)
|
||||
}
|
||||
if strings.HasPrefix(u.Scheme, "http") {
|
||||
if !deprecationShown {
|
||||
log.Println("WARN: Passing URLs via -f/--file is deprecated, please use -u/--template-url instead")
|
||||
deprecationShown = true
|
||||
}
|
||||
sources = append(sources, pkgtmpl.SourceFromURL(u, params.encoding))
|
||||
} else {
|
||||
fileSources, err := pkgtmpl.SourcesFromPath(in, params.recurse, params.encoding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sources = append(sources, fileSources...)
|
||||
}
|
||||
}
|
||||
for _, in := range params.urls.Value() {
|
||||
u, err := url.Parse(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse input URL: %q: %w", in, err)
|
||||
}
|
||||
sources = append(sources, pkgtmpl.SourceFromURL(u, params.encoding))
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func newTemplateCmd() cli.Command {
|
||||
var params struct {
|
||||
templateParams
|
||||
noColor bool
|
||||
noTableBorders bool
|
||||
}
|
||||
return cli.Command{
|
||||
Name: "template",
|
||||
Usage: "Summarize the provided template",
|
||||
Subcommands: []cli.Command{
|
||||
newTemplateValidateCmd(),
|
||||
},
|
||||
Flags: append(
|
||||
append(commonFlags(), templateFlags(¶ms.templateParams)...),
|
||||
&cli.BoolFlag{
|
||||
Name: "disable-color",
|
||||
Usage: "Disable color in output",
|
||||
Destination: ¶ms.noColor,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "disable-table-borders",
|
||||
Usage: "Disable table borders",
|
||||
Destination: ¶ms.noTableBorders,
|
||||
},
|
||||
),
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
parsedParams := template.SummarizeParams{
|
||||
OrgId: params.orgId,
|
||||
OrgName: params.orgName,
|
||||
RenderTableColors: !params.noColor,
|
||||
RenderTableBorders: !params.noTableBorders,
|
||||
}
|
||||
sources, err := params.parseSources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsedParams.Sources = sources
|
||||
|
||||
api := getAPI(ctx)
|
||||
client := template.Client{
|
||||
CLI: getCLI(ctx),
|
||||
TemplatesApi: api.TemplatesApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.Summarize(getContext(ctx), &parsedParams)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newTemplateValidateCmd() cli.Command {
|
||||
var params templateParams
|
||||
return cli.Command{
|
||||
Name: "validate",
|
||||
Usage: "Validate the provided template",
|
||||
Flags: append(commonFlagsNoPrint(), templateFlags(¶ms)...),
|
||||
Before: middleware.WithBeforeFns(withCli(), withApi(true)),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
parsedParams := template.ValidateParams{
|
||||
OrgId: params.orgId,
|
||||
OrgName: params.orgName,
|
||||
}
|
||||
sources, err := params.parseSources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsedParams.Sources = sources
|
||||
|
||||
api := getAPI(ctx)
|
||||
client := template.Client{
|
||||
CLI: getCLI(ctx),
|
||||
TemplatesApi: api.TemplatesApi,
|
||||
OrganizationsApi: api.OrganizationsApi,
|
||||
}
|
||||
return client.Validate(getContext(ctx), &parsedParams)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package apply
|
||||
package template
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -18,75 +18,75 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TemplateEncoding int
|
||||
type Encoding int
|
||||
|
||||
const (
|
||||
TemplateEncodingUnknown TemplateEncoding = iota
|
||||
TemplateEncodingJson
|
||||
TemplateEncodingJsonnet
|
||||
TemplateEncodingYaml
|
||||
EncodingUnknown Encoding = iota
|
||||
EncodingJson
|
||||
EncodingJsonnet
|
||||
EncodingYaml
|
||||
)
|
||||
|
||||
func (e *TemplateEncoding) Set(v string) error {
|
||||
func (e *Encoding) Set(v string) error {
|
||||
switch v {
|
||||
case "jsonnet":
|
||||
*e = TemplateEncodingJsonnet
|
||||
*e = EncodingJsonnet
|
||||
case "json":
|
||||
*e = TemplateEncodingJson
|
||||
*e = EncodingJson
|
||||
case "yml", "yaml":
|
||||
*e = TemplateEncodingYaml
|
||||
*e = EncodingYaml
|
||||
default:
|
||||
return fmt.Errorf("unknown inEncoding %q", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e TemplateEncoding) String() string {
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case TemplateEncodingJsonnet:
|
||||
case EncodingJsonnet:
|
||||
return "jsonnet"
|
||||
case TemplateEncodingJson:
|
||||
case EncodingJson:
|
||||
return "json"
|
||||
case TemplateEncodingYaml:
|
||||
case EncodingYaml:
|
||||
return "yaml"
|
||||
case TemplateEncodingUnknown:
|
||||
case EncodingUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type TemplateSource struct {
|
||||
type Source struct {
|
||||
Name string
|
||||
Encoding TemplateEncoding
|
||||
Encoding Encoding
|
||||
Open func(context.Context) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
func SourcesFromPath(path string, recur bool, encoding TemplateEncoding) ([]TemplateSource, error) {
|
||||
func SourcesFromPath(path string, recur bool, encoding Encoding) ([]Source, error) {
|
||||
paths, err := findPaths(path, recur)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find inputs at path %q: %w", path, err)
|
||||
}
|
||||
|
||||
sources := make([]TemplateSource, len(paths))
|
||||
sources := make([]Source, len(paths))
|
||||
for i := range paths {
|
||||
path := paths[i] // Local var for the `Open` closure to capture.
|
||||
encoding := encoding
|
||||
if encoding == TemplateEncodingUnknown {
|
||||
if encoding == EncodingUnknown {
|
||||
switch filepath.Ext(path) {
|
||||
case ".jsonnet":
|
||||
encoding = TemplateEncodingJsonnet
|
||||
encoding = EncodingJsonnet
|
||||
case ".json":
|
||||
encoding = TemplateEncodingJson
|
||||
encoding = EncodingJson
|
||||
case ".yml":
|
||||
fallthrough
|
||||
case ".yaml":
|
||||
encoding = TemplateEncodingYaml
|
||||
encoding = EncodingYaml
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
sources[i] = TemplateSource{
|
||||
sources[i] = Source{
|
||||
Name: path,
|
||||
Encoding: encoding,
|
||||
Open: func(context.Context) (io.ReadCloser, error) {
|
||||
@ -127,24 +127,24 @@ func findPaths(path string, recur bool) ([]string, error) {
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func SourceFromURL(u *url.URL, encoding TemplateEncoding) TemplateSource {
|
||||
if encoding == TemplateEncodingUnknown {
|
||||
func SourceFromURL(u *url.URL, encoding Encoding) Source {
|
||||
if encoding == EncodingUnknown {
|
||||
switch path.Ext(u.Path) {
|
||||
case ".jsonnet":
|
||||
encoding = TemplateEncodingJsonnet
|
||||
encoding = EncodingJsonnet
|
||||
case ".json":
|
||||
encoding = TemplateEncodingJson
|
||||
encoding = EncodingJson
|
||||
case ".yml":
|
||||
fallthrough
|
||||
case ".yaml":
|
||||
encoding = TemplateEncodingYaml
|
||||
encoding = EncodingYaml
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
normalized := github.NormalizeURLToContent(u, ".yaml", ".yml", ".jsonnet", ".json").String()
|
||||
|
||||
return TemplateSource{
|
||||
return Source{
|
||||
Name: normalized,
|
||||
Encoding: encoding,
|
||||
Open: func(ctx context.Context) (io.ReadCloser, error) {
|
||||
@ -170,8 +170,8 @@ func SourceFromURL(u *url.URL, encoding TemplateEncoding) TemplateSource {
|
||||
}
|
||||
}
|
||||
|
||||
func SourceFromReader(r io.Reader, encoding TemplateEncoding) TemplateSource {
|
||||
return TemplateSource{
|
||||
func SourceFromReader(r io.Reader, encoding Encoding) Source {
|
||||
return Source{
|
||||
Name: "byte stream",
|
||||
Encoding: encoding,
|
||||
Open: func(context.Context) (io.ReadCloser, error) {
|
||||
@ -180,7 +180,21 @@ func SourceFromReader(r io.Reader, encoding TemplateEncoding) TemplateSource {
|
||||
}
|
||||
}
|
||||
|
||||
func (s TemplateSource) Read(ctx context.Context) (api.TemplateApplyTemplate, error) {
|
||||
func ReadSources(ctx context.Context, sources []Source) ([]api.TemplateApplyTemplate, error) {
|
||||
templates := make([]api.TemplateApplyTemplate, 0, len(sources))
|
||||
for _, source := range sources {
|
||||
tmpl, err := source.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We always send the templates as JSON.
|
||||
tmpl.ContentType = "json"
|
||||
templates = append(templates, tmpl)
|
||||
}
|
||||
return templates, nil
|
||||
}
|
||||
|
||||
func (s Source) Read(ctx context.Context) (api.TemplateApplyTemplate, error) {
|
||||
var entries []api.TemplateEntry
|
||||
if err := func() error {
|
||||
in, err := s.Open(ctx)
|
||||
@ -190,13 +204,13 @@ func (s TemplateSource) Read(ctx context.Context) (api.TemplateApplyTemplate, er
|
||||
defer in.Close()
|
||||
|
||||
switch s.Encoding {
|
||||
case TemplateEncodingJsonnet:
|
||||
case EncodingJsonnet:
|
||||
err = jsonnet.NewDecoder(in).Decode(&entries)
|
||||
case TemplateEncodingJson:
|
||||
case EncodingJson:
|
||||
err = json.NewDecoder(in).Decode(&entries)
|
||||
case TemplateEncodingUnknown:
|
||||
case EncodingUnknown:
|
||||
fallthrough // Assume YAML if we can't make a better guess
|
||||
case TemplateEncodingYaml:
|
||||
case EncodingYaml:
|
||||
dec := yaml.NewDecoder(in)
|
||||
for {
|
||||
var e api.TemplateEntry
|
@ -1,4 +1,4 @@
|
||||
package apply_test
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/clients/apply"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -22,14 +22,14 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
|
||||
type contents struct {
|
||||
name string
|
||||
encoding apply.TemplateEncoding
|
||||
encoding template.Encoding
|
||||
contents string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, rootDir string)
|
||||
inPath func(rootDir string) string
|
||||
inEncoding apply.TemplateEncoding
|
||||
inEncoding template.Encoding
|
||||
recursive bool
|
||||
expected func(rootDir string) []contents
|
||||
}{
|
||||
@ -44,7 +44,7 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.json",
|
||||
encoding: apply.TemplateEncodingJson,
|
||||
encoding: template.EncodingJson,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
@ -60,7 +60,7 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.yaml",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
@ -76,7 +76,7 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.yml",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
@ -92,7 +92,7 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.jsonnet",
|
||||
encoding: apply.TemplateEncodingJsonnet,
|
||||
encoding: template.EncodingJsonnet,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
@ -102,14 +102,14 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inEncoding: apply.TemplateEncodingJson,
|
||||
inEncoding: template.EncodingJson,
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo",
|
||||
encoding: apply.TemplateEncodingJson,
|
||||
encoding: template.EncodingJson,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
@ -131,12 +131,12 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.json"),
|
||||
contents: "foo.json",
|
||||
encoding: apply.TemplateEncodingJson,
|
||||
encoding: template.EncodingJson,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.yml"),
|
||||
contents: "foo.yml",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -159,22 +159,22 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.json"),
|
||||
contents: "foo.json",
|
||||
encoding: apply.TemplateEncodingJson,
|
||||
encoding: template.EncodingJson,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.yml"),
|
||||
contents: "foo.yml",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "bar", "foo.yaml"),
|
||||
contents: "foo.yaml",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "bar", "foo.jsonnet"),
|
||||
contents: "foo.jsonnet",
|
||||
encoding: apply.TemplateEncodingJsonnet,
|
||||
encoding: template.EncodingJsonnet,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -191,7 +191,7 @@ func TestSourcesFromPath(t *testing.T) {
|
||||
defer os.RemoveAll(tmp)
|
||||
tc.setup(t, tmp)
|
||||
|
||||
sources, err := apply.SourcesFromPath(tc.inPath(tmp), tc.recursive, tc.inEncoding)
|
||||
sources, err := template.SourcesFromPath(tc.inPath(tmp), tc.recursive, tc.inEncoding)
|
||||
require.NoError(t, err)
|
||||
expected := tc.expected(tmp)
|
||||
require.Len(t, sources, len(expected))
|
||||
@ -225,8 +225,8 @@ func TestSourceFromURL(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
filename string
|
||||
inEncoding apply.TemplateEncoding
|
||||
expectEncoding apply.TemplateEncoding
|
||||
inEncoding template.Encoding
|
||||
expectEncoding template.Encoding
|
||||
resStatus int
|
||||
resBody string
|
||||
expectErr bool
|
||||
@ -234,43 +234,43 @@ func TestSourceFromURL(t *testing.T) {
|
||||
{
|
||||
name: "JSON file",
|
||||
filename: "foo.json",
|
||||
expectEncoding: apply.TemplateEncodingJson,
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "YAML file",
|
||||
filename: "foo.yaml",
|
||||
expectEncoding: apply.TemplateEncodingYaml,
|
||||
expectEncoding: template.EncodingYaml,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "YML file",
|
||||
filename: "foo.yml",
|
||||
expectEncoding: apply.TemplateEncodingYaml,
|
||||
expectEncoding: template.EncodingYaml,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "JSONNET file",
|
||||
filename: "foo.jsonnet",
|
||||
expectEncoding: apply.TemplateEncodingJsonnet,
|
||||
expectEncoding: template.EncodingJsonnet,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "explicit encoding",
|
||||
filename: "foo",
|
||||
inEncoding: apply.TemplateEncodingJson,
|
||||
expectEncoding: apply.TemplateEncodingJson,
|
||||
inEncoding: template.EncodingJson,
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "err response",
|
||||
filename: "foo.json",
|
||||
expectEncoding: apply.TemplateEncodingJson,
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 403,
|
||||
resBody: "OH NO",
|
||||
expectErr: true,
|
||||
@ -292,7 +292,7 @@ func TestSourceFromURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
u.Path = tc.filename
|
||||
|
||||
source := apply.SourceFromURL(u, tc.inEncoding)
|
||||
source := template.SourceFromURL(u, tc.inEncoding)
|
||||
in, err := source.Open(context.Background())
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
@ -398,22 +398,22 @@ spec:
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
encoding apply.TemplateEncoding
|
||||
encoding template.Encoding
|
||||
data string
|
||||
}{
|
||||
{
|
||||
name: "JSON",
|
||||
encoding: apply.TemplateEncodingJson,
|
||||
encoding: template.EncodingJson,
|
||||
data: jsonTemplate,
|
||||
},
|
||||
{
|
||||
name: "YAML",
|
||||
encoding: apply.TemplateEncodingYaml,
|
||||
encoding: template.EncodingYaml,
|
||||
data: yamlTemplate,
|
||||
},
|
||||
{
|
||||
name: "JSONNET",
|
||||
encoding: apply.TemplateEncodingJsonnet,
|
||||
encoding: template.EncodingJsonnet,
|
||||
data: jsonnetTemplate,
|
||||
},
|
||||
}
|
||||
@ -423,7 +423,7 @@ spec:
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := apply.SourceFromReader(strings.NewReader(tc.data), tc.encoding)
|
||||
source := template.SourceFromReader(strings.NewReader(tc.data), tc.encoding)
|
||||
tmpl, err := source.Read(context.Background())
|
||||
require.NoError(t, err)
|
||||
expected := api.TemplateApplyTemplate{
|
199
pkg/template/summary_printer.go
Normal file
199
pkg/template/summary_printer.go
Normal file
@ -0,0 +1,199 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/influxid"
|
||||
)
|
||||
|
||||
// PrintSummary renders high-level info about a template as a table for display on the console.
|
||||
//
|
||||
// NOTE: The implementation here is very "static" in that it's hard-coded to look for specific
|
||||
// resource-kinds and fields within those kinds. If the API changes to add more kinds / more fields,
|
||||
// this function won't automatically pick them up & print them. It'd be nice to rework this to be
|
||||
// less opinionated / more resilient to extension in the future...
|
||||
func PrintSummary(summary api.TemplateSummaryResources, out io.Writer, useColor bool, useBorders bool) error {
|
||||
newPrinter := func(title string, headers []string) *TablePrinter {
|
||||
return NewTablePrinter(out, useColor, useBorders).
|
||||
Title(title).
|
||||
SetHeaders(append([]string{"Package Name", "ID", "Resource Name"}, headers...)...)
|
||||
}
|
||||
|
||||
if labels := summary.Labels; len(labels) > 0 {
|
||||
printer := newPrinter("LABELS", []string{"Description", "Color"})
|
||||
for _, l := range labels {
|
||||
id := influxid.ID(l.Id).String()
|
||||
var desc string
|
||||
if l.Properties.Description != nil {
|
||||
desc = *l.Properties.Description
|
||||
}
|
||||
printer.Append([]string{l.TemplateMetaName, id, l.Name, desc, l.Properties.Color})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if buckets := summary.Buckets; len(buckets) > 0 {
|
||||
printer := newPrinter("BUCKETS", []string{"Retention", "Description", "Schema Type"})
|
||||
for _, b := range buckets {
|
||||
id := influxid.ID(b.Id).String()
|
||||
var desc string
|
||||
if b.Description != nil {
|
||||
desc = *b.Description
|
||||
}
|
||||
rp := "inf"
|
||||
if b.RetentionPeriod != 0 {
|
||||
rp = time.Duration(b.RetentionPeriod).String()
|
||||
}
|
||||
schemaType := api.SCHEMATYPE_IMPLICIT
|
||||
if b.SchemaType != nil {
|
||||
schemaType = *b.SchemaType
|
||||
}
|
||||
printer.Append([]string{b.TemplateMetaName, id, b.Name, rp, desc, schemaType.String()})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if checks := summary.Checks; len(checks) > 0 {
|
||||
printer := newPrinter("CHECKS", []string{"Description"})
|
||||
for _, c := range checks {
|
||||
id := influxid.ID(c.Id).String()
|
||||
var desc string
|
||||
if c.Description != nil {
|
||||
desc = *c.Description
|
||||
}
|
||||
printer.Append([]string{c.TemplateMetaName, id, c.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if dashboards := summary.Dashboards; len(dashboards) > 0 {
|
||||
printer := newPrinter("DASHBOARDS", []string{"Description"})
|
||||
for _, d := range dashboards {
|
||||
id := influxid.ID(d.Id).String()
|
||||
var desc string
|
||||
if d.Description != nil {
|
||||
desc = *d.Description
|
||||
}
|
||||
printer.Append([]string{d.TemplateMetaName, id, d.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if endpoints := summary.NotificationEndpoints; len(endpoints) > 0 {
|
||||
printer := newPrinter("NOTIFICATION ENDPOINTS", []string{"Description", "Status"})
|
||||
for _, e := range endpoints {
|
||||
id := influxid.ID(e.Id).String()
|
||||
var desc string
|
||||
if e.Description != nil {
|
||||
desc = *e.Description
|
||||
}
|
||||
printer.Append([]string{e.TemplateMetaName, id, e.Name, desc, e.Status})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if rules := summary.NotificationRules; len(rules) > 0 {
|
||||
printer := newPrinter("NOTIFICATION RULES", []string{"Description", "Every", "Offset", "Endpoint Name", "Endpoint ID", "Endpoint Type"})
|
||||
for _, r := range rules {
|
||||
id := influxid.ID(r.Id).String()
|
||||
eid := influxid.ID(r.EndpointID).String()
|
||||
var desc string
|
||||
if r.Description != nil {
|
||||
desc = *r.Description
|
||||
}
|
||||
printer.Append([]string{r.TemplateMetaName, id, r.Name, desc, r.Every, r.Offset, r.EndpointTemplateMetaName, eid, r.EndpointType})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if tasks := summary.Tasks; len(tasks) > 0 {
|
||||
printer := newPrinter("TASKS", []string{"Description", "Cycle"})
|
||||
for _, t := range tasks {
|
||||
id := influxid.ID(t.Id).String()
|
||||
var desc string
|
||||
if t.Description != nil {
|
||||
desc = *t.Description
|
||||
}
|
||||
var timing string
|
||||
if t.Cron != nil {
|
||||
timing = *t.Cron
|
||||
} else {
|
||||
offset := time.Duration(0).String()
|
||||
if t.Offset != nil {
|
||||
offset = *t.Offset
|
||||
}
|
||||
// If `cron` isn't set, `every` must be set
|
||||
timing = fmt.Sprintf("every: %s offset: %s", *t.Every, offset)
|
||||
}
|
||||
printer.Append([]string{t.TemplateMetaName, id, t.Name, desc, timing})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if teles := summary.TelegrafConfigs; len(teles) > 0 {
|
||||
printer := newPrinter("TELEGRAF CONFIGS", []string{"Description"})
|
||||
for _, t := range teles {
|
||||
var desc string
|
||||
if t.TelegrafConfig.Description != nil {
|
||||
desc = *t.TelegrafConfig.Description
|
||||
}
|
||||
printer.Append([]string{t.TemplateMetaName, t.TelegrafConfig.Id, t.TelegrafConfig.Name, desc})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if vars := summary.Variables; len(vars) > 0 {
|
||||
printer := newPrinter("VARIABLES", []string{"Description", "Arg Type", "Arg Values"})
|
||||
for _, v := range vars {
|
||||
id := influxid.ID(v.Id).String()
|
||||
var desc string
|
||||
if v.Description != nil {
|
||||
desc = *v.Description
|
||||
}
|
||||
var argType string
|
||||
if v.Arguments != nil {
|
||||
argType = v.Arguments.Type
|
||||
}
|
||||
printer.Append([]string{v.TemplateMetaName, id, v.Name, desc, argType, v.Arguments.Render()})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if mappings := summary.LabelMappings; len(mappings) > 0 {
|
||||
printer := NewTablePrinter(out, useColor, useBorders).
|
||||
Title("LABEL ASSOCIATIONS").
|
||||
SetHeaders("Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID")
|
||||
for _, m := range mappings {
|
||||
rid := influxid.ID(m.ResourceID).String()
|
||||
lid := influxid.ID(m.LabelID).String()
|
||||
printer.Append([]string{m.ResourceType, m.ResourceName, rid, m.LabelName, lid})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
if secrets := summary.MissingSecrets; len(secrets) > 0 {
|
||||
printer := NewTablePrinter(out, useColor, useBorders).
|
||||
Title("MISSING SECRETS").
|
||||
SetHeaders("Secret Key")
|
||||
for _, sk := range secrets {
|
||||
printer.Append([]string{sk})
|
||||
}
|
||||
printer.Render()
|
||||
_, _ = out.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user