Files
influx-cli/pkg/template/source.go
2022-03-14 09:15:21 -04:00

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 + ".generated.json"},
Contents: entries,
}, nil
}