235 lines
4.9 KiB
Go
235 lines
4.9 KiB
Go
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
|
|
}
|