feat: implement template
command (#169)
This commit is contained in:
234
pkg/template/source.go
Normal file
234
pkg/template/source.go
Normal file
@ -0,0 +1,234 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/github"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/jsonnet"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Encoding int
|
||||
|
||||
const (
|
||||
EncodingUnknown Encoding = iota
|
||||
EncodingJson
|
||||
EncodingJsonnet
|
||||
EncodingYaml
|
||||
)
|
||||
|
||||
func (e *Encoding) Set(v string) error {
|
||||
switch v {
|
||||
case "jsonnet":
|
||||
*e = EncodingJsonnet
|
||||
case "json":
|
||||
*e = EncodingJson
|
||||
case "yml", "yaml":
|
||||
*e = EncodingYaml
|
||||
default:
|
||||
return fmt.Errorf("unknown inEncoding %q", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Encoding) String() string {
|
||||
switch e {
|
||||
case EncodingJsonnet:
|
||||
return "jsonnet"
|
||||
case EncodingJson:
|
||||
return "json"
|
||||
case EncodingYaml:
|
||||
return "yaml"
|
||||
case EncodingUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
Name string
|
||||
Encoding Encoding
|
||||
Open func(context.Context) (io.ReadCloser, 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([]Source, len(paths))
|
||||
for i := range paths {
|
||||
path := paths[i] // Local var for the `Open` closure to capture.
|
||||
encoding := encoding
|
||||
if encoding == EncodingUnknown {
|
||||
switch filepath.Ext(path) {
|
||||
case ".jsonnet":
|
||||
encoding = EncodingJsonnet
|
||||
case ".json":
|
||||
encoding = EncodingJson
|
||||
case ".yml":
|
||||
fallthrough
|
||||
case ".yaml":
|
||||
encoding = EncodingYaml
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
sources[i] = Source{
|
||||
Name: path,
|
||||
Encoding: encoding,
|
||||
Open: func(context.Context) (io.ReadCloser, error) {
|
||||
return os.Open(path)
|
||||
},
|
||||
}
|
||||
}
|
||||
return sources, nil
|
||||
}
|
||||
|
||||
func findPaths(path string, recur bool) ([]string, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return []string{path}, nil
|
||||
}
|
||||
|
||||
dirFiles, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var paths []string
|
||||
for _, df := range dirFiles {
|
||||
fullPath := filepath.Join(path, df.Name())
|
||||
if df.IsDir() && recur {
|
||||
subPaths, err := findPaths(fullPath, recur)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, subPaths...)
|
||||
} else if !df.IsDir() {
|
||||
paths = append(paths, fullPath)
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func SourceFromURL(u *url.URL, encoding Encoding) Source {
|
||||
if encoding == EncodingUnknown {
|
||||
switch path.Ext(u.Path) {
|
||||
case ".jsonnet":
|
||||
encoding = EncodingJsonnet
|
||||
case ".json":
|
||||
encoding = EncodingJson
|
||||
case ".yml":
|
||||
fallthrough
|
||||
case ".yaml":
|
||||
encoding = EncodingYaml
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
normalized := github.NormalizeURLToContent(u, ".yaml", ".yml", ".jsonnet", ".json").String()
|
||||
|
||||
return Source{
|
||||
Name: normalized,
|
||||
Encoding: encoding,
|
||||
Open: func(ctx context.Context) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", normalized, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode/100 != 2 {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("bad response: address=%s status_code=%d body=%q",
|
||||
normalized, res.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
return res.Body, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func SourceFromReader(r io.Reader, encoding Encoding) Source {
|
||||
return Source{
|
||||
Name: "byte stream",
|
||||
Encoding: encoding,
|
||||
Open: func(context.Context) (io.ReadCloser, error) {
|
||||
return io.NopCloser(r), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
switch s.Encoding {
|
||||
case EncodingJsonnet:
|
||||
err = jsonnet.NewDecoder(in).Decode(&entries)
|
||||
case EncodingJson:
|
||||
err = json.NewDecoder(in).Decode(&entries)
|
||||
case EncodingUnknown:
|
||||
fallthrough // Assume YAML if we can't make a better guess
|
||||
case EncodingYaml:
|
||||
dec := yaml.NewDecoder(in)
|
||||
for {
|
||||
var e api.TemplateEntry
|
||||
if err := dec.Decode(&e); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}(); err != nil {
|
||||
return api.TemplateApplyTemplate{}, fmt.Errorf("failed to read template(s) from %q: %w", s.Name, err)
|
||||
}
|
||||
|
||||
return api.TemplateApplyTemplate{
|
||||
Sources: []string{s.Name},
|
||||
Contents: entries,
|
||||
}, nil
|
||||
}
|
436
pkg/template/source_test.go
Normal file
436
pkg/template/source_test.go
Normal file
@ -0,0 +1,436 @@
|
||||
package template_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/influxdata/influx-cli/v2/api"
|
||||
"github.com/influxdata/influx-cli/v2/pkg/template"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSourcesFromPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type contents struct {
|
||||
name string
|
||||
encoding template.Encoding
|
||||
contents string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
setup func(t *testing.T, rootDir string)
|
||||
inPath func(rootDir string) string
|
||||
inEncoding template.Encoding
|
||||
recursive bool
|
||||
expected func(rootDir string) []contents
|
||||
}{
|
||||
{
|
||||
name: "JSON file",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo.json")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.json",
|
||||
encoding: template.EncodingJson,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "YAML file",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yaml"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo.yaml")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.yaml",
|
||||
encoding: template.EncodingYaml,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "YML file",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo.yml")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.yml",
|
||||
encoding: template.EncodingYaml,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JSONNET file",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.jsonnet"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo.jsonnet")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo.jsonnet",
|
||||
encoding: template.EncodingJsonnet,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "explicit inEncoding",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo"), []byte("foo"), os.ModePerm))
|
||||
},
|
||||
inEncoding: template.EncodingJson,
|
||||
inPath: func(rootDir string) string {
|
||||
return filepath.Join(rootDir, "foo")
|
||||
},
|
||||
expected: func(string) []contents {
|
||||
return []contents{{
|
||||
name: "foo",
|
||||
encoding: template.EncodingJson,
|
||||
contents: "foo",
|
||||
}}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "directory - non-recursive",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.Mkdir(filepath.Join(rootDir, "bar"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo.json"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo.yml"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.jsonnet"), []byte("foo.jsonnet"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.yaml"), []byte("foo.yaml"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return rootDir
|
||||
},
|
||||
expected: func(rootDir string) []contents {
|
||||
return []contents{
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.json"),
|
||||
contents: "foo.json",
|
||||
encoding: template.EncodingJson,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.yml"),
|
||||
contents: "foo.yml",
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "directory - recursive",
|
||||
setup: func(t *testing.T, rootDir string) {
|
||||
require.NoError(t, os.Mkdir(filepath.Join(rootDir, "bar"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.json"), []byte("foo.json"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "foo.yml"), []byte("foo.yml"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.jsonnet"), []byte("foo.jsonnet"), os.ModePerm))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "bar", "foo.yaml"), []byte("foo.yaml"), os.ModePerm))
|
||||
},
|
||||
inPath: func(rootDir string) string {
|
||||
return rootDir
|
||||
},
|
||||
recursive: true,
|
||||
expected: func(rootDir string) []contents {
|
||||
return []contents{
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.json"),
|
||||
contents: "foo.json",
|
||||
encoding: template.EncodingJson,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "foo.yml"),
|
||||
contents: "foo.yml",
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "bar", "foo.yaml"),
|
||||
contents: "foo.yaml",
|
||||
encoding: template.EncodingYaml,
|
||||
},
|
||||
{
|
||||
name: filepath.Join(rootDir, "bar", "foo.jsonnet"),
|
||||
contents: "foo.jsonnet",
|
||||
encoding: template.EncodingJsonnet,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmp)
|
||||
tc.setup(t, tmp)
|
||||
|
||||
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))
|
||||
|
||||
sort.Slice(sources, func(i, j int) bool {
|
||||
return sources[i].Name < sources[j].Name
|
||||
})
|
||||
sort.Slice(expected, func(i, j int) bool {
|
||||
return expected[i].name < expected[j].name
|
||||
})
|
||||
|
||||
for i := range expected {
|
||||
source := sources[i]
|
||||
contents := expected[i]
|
||||
|
||||
require.Equal(t, contents.encoding, source.Encoding)
|
||||
sourceIn, err := source.Open(context.Background())
|
||||
require.NoError(t, err)
|
||||
bytes, err := io.ReadAll(sourceIn)
|
||||
sourceIn.Close()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, contents.contents, string(bytes))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSourceFromURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
filename string
|
||||
inEncoding template.Encoding
|
||||
expectEncoding template.Encoding
|
||||
resStatus int
|
||||
resBody string
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "JSON file",
|
||||
filename: "foo.json",
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "YAML file",
|
||||
filename: "foo.yaml",
|
||||
expectEncoding: template.EncodingYaml,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "YML file",
|
||||
filename: "foo.yml",
|
||||
expectEncoding: template.EncodingYaml,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "JSONNET file",
|
||||
filename: "foo.jsonnet",
|
||||
expectEncoding: template.EncodingJsonnet,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "explicit encoding",
|
||||
filename: "foo",
|
||||
inEncoding: template.EncodingJson,
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 200,
|
||||
resBody: "Foo bar",
|
||||
},
|
||||
{
|
||||
name: "err response",
|
||||
filename: "foo.json",
|
||||
expectEncoding: template.EncodingJson,
|
||||
resStatus: 403,
|
||||
resBody: "OH NO",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(tc.resStatus)
|
||||
rw.Write([]byte(tc.resBody))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
u, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
u.Path = tc.filename
|
||||
|
||||
source := template.SourceFromURL(u, tc.inEncoding)
|
||||
in, err := source.Open(context.Background())
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "bad response")
|
||||
require.Contains(t, err.Error(), tc.resBody)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer in.Close()
|
||||
bytes, err := io.ReadAll(in)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.resBody, string(bytes))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateSource_Read(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
yamlTemplate := `---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Bucket
|
||||
meta: null
|
||||
spec:
|
||||
name: test
|
||||
retentionRules:
|
||||
- type: expire
|
||||
---
|
||||
apiVersion: influxdata.com/v2alpha1
|
||||
kind: Bucket
|
||||
meta: null
|
||||
spec:
|
||||
name: test2
|
||||
retentionRules:
|
||||
- type: expire
|
||||
`
|
||||
jsonTemplate := `[
|
||||
{
|
||||
"apiVersion": "influxdata.com/v2alpha1",
|
||||
"kind": "Bucket",
|
||||
"spec": {
|
||||
"name": "test",
|
||||
"retentionRules": [
|
||||
{
|
||||
"type": "expire"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "influxdata.com/v2alpha1",
|
||||
"kind": "Bucket",
|
||||
"spec": {
|
||||
"name": "test2",
|
||||
"retentionRules": [
|
||||
{
|
||||
"type": "expire"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
`
|
||||
jsonnetTemplate := `local Bucket(name) = {
|
||||
apiVersion: "influxdata.com/v2alpha1",
|
||||
kind: "Bucket",
|
||||
spec: {
|
||||
name: name,
|
||||
retentionRules: [{
|
||||
type: "expire",
|
||||
}],
|
||||
},
|
||||
};
|
||||
[Bucket("test"), Bucket("test2")]
|
||||
`
|
||||
|
||||
parsed := []api.TemplateEntry{
|
||||
{
|
||||
ApiVersion: "influxdata.com/v2alpha1",
|
||||
Kind: "Bucket",
|
||||
Spec: map[string]interface{}{
|
||||
"name": "test",
|
||||
"retentionRules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "expire",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ApiVersion: "influxdata.com/v2alpha1",
|
||||
Kind: "Bucket",
|
||||
Spec: map[string]interface{}{
|
||||
"name": "test2",
|
||||
"retentionRules": []interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "expire",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
encoding template.Encoding
|
||||
data string
|
||||
}{
|
||||
{
|
||||
name: "JSON",
|
||||
encoding: template.EncodingJson,
|
||||
data: jsonTemplate,
|
||||
},
|
||||
{
|
||||
name: "YAML",
|
||||
encoding: template.EncodingYaml,
|
||||
data: yamlTemplate,
|
||||
},
|
||||
{
|
||||
name: "JSONNET",
|
||||
encoding: template.EncodingJsonnet,
|
||||
data: jsonnetTemplate,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source := template.SourceFromReader(strings.NewReader(tc.data), tc.encoding)
|
||||
tmpl, err := source.Read(context.Background())
|
||||
require.NoError(t, err)
|
||||
expected := api.TemplateApplyTemplate{
|
||||
Sources: []string{source.Name},
|
||||
Contents: parsed,
|
||||
}
|
||||
require.Equal(t, expected, tmpl)
|
||||
})
|
||||
}
|
||||
}
|
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