formidable/internal/command/common.go

262 lines
5.6 KiB
Go
Raw Normal View History

2022-03-22 09:21:55 +01:00
package command
import (
2022-05-05 16:22:52 +02:00
"bytes"
2022-03-22 09:21:55 +01:00
"io"
2022-05-05 16:22:52 +02:00
"net/url"
2022-08-01 17:13:56 +02:00
"os"
"reflect"
2022-03-22 09:21:55 +01:00
2022-05-05 16:22:52 +02:00
encjson "encoding/json"
"forge.cadoles.com/wpetit/formidable/internal/data"
"forge.cadoles.com/wpetit/formidable/internal/data/format/hcl"
"forge.cadoles.com/wpetit/formidable/internal/data/format/json"
"forge.cadoles.com/wpetit/formidable/internal/data/format/yaml"
2022-05-05 16:22:52 +02:00
"forge.cadoles.com/wpetit/formidable/internal/data/scheme/file"
"forge.cadoles.com/wpetit/formidable/internal/data/scheme/http"
"forge.cadoles.com/wpetit/formidable/internal/data/scheme/stdin"
2022-05-10 22:31:17 +02:00
"forge.cadoles.com/wpetit/formidable/internal/data/updater/exec"
fileUpdater "forge.cadoles.com/wpetit/formidable/internal/data/updater/file"
2022-08-01 15:33:41 +02:00
"forge.cadoles.com/wpetit/formidable/internal/data/updater/null"
2022-05-10 22:31:17 +02:00
"forge.cadoles.com/wpetit/formidable/internal/data/updater/stdout"
2022-03-22 09:21:55 +01:00
"forge.cadoles.com/wpetit/formidable/internal/def"
"forge.cadoles.com/wpetit/formidable/internal/merge"
2022-03-22 09:21:55 +01:00
"github.com/pkg/errors"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/urfave/cli/v2"
gohttp "net/http"
2022-03-22 09:21:55 +01:00
)
const (
filePathPrefix = "@"
)
func commonFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "defaults",
Aliases: []string{"d"},
2022-08-01 17:14:41 +02:00
Usage: "Use `defaults_url` as defaults",
2022-05-05 16:22:52 +02:00
Value: "",
2022-03-22 09:21:55 +01:00
},
&cli.StringFlag{
Name: "values",
Aliases: []string{"v"},
2022-08-01 17:14:41 +02:00
Usage: "Use `values_url` as values",
2022-05-05 16:22:52 +02:00
Value: "",
2022-03-22 09:21:55 +01:00
},
&cli.StringFlag{
2022-08-01 17:14:41 +02:00
Name: "schema",
Aliases: []string{"s"},
Usage: "Use `schema_url` as schema",
Value: "",
2022-03-22 09:21:55 +01:00
},
&cli.StringFlag{
Name: "output",
Aliases: []string{"o", "out"},
2022-05-10 22:31:17 +02:00
Value: "stdout://local?format=json",
Usage: "Output modified values to specified URL",
2022-03-22 09:21:55 +01:00
},
}
}
2022-05-05 16:22:52 +02:00
func loadURLFlag(ctx *cli.Context, flagName string) (interface{}, error) {
2022-03-22 09:21:55 +01:00
flagValue := ctx.String(flagName)
if flagValue == "" {
return nil, nil
}
2022-05-05 16:22:52 +02:00
loader := newLoader()
2022-03-22 09:21:55 +01:00
2022-05-05 16:22:52 +02:00
url, err := url.Parse(flagValue)
if err != nil {
return nil, errors.WithStack(err)
2022-03-22 09:21:55 +01:00
}
2022-05-05 16:22:52 +02:00
reader, err := loader.Open(url)
2022-03-22 09:21:55 +01:00
if err != nil {
return nil, errors.WithStack(err)
}
defer func() {
2022-05-05 16:22:52 +02:00
if err := reader.Close(); err != nil {
2022-03-22 09:21:55 +01:00
panic(errors.WithStack(err))
}
}()
2022-05-05 16:22:52 +02:00
decoder := newDecoder()
2022-03-22 09:21:55 +01:00
2022-05-05 16:22:52 +02:00
data, err := decoder.Decode(url, reader)
if err != nil {
2022-03-22 09:21:55 +01:00
return nil, errors.WithStack(err)
}
2022-05-05 16:22:52 +02:00
return data, nil
2022-03-22 09:21:55 +01:00
}
func loadValues(ctx *cli.Context) (interface{}, error) {
2022-05-05 16:22:52 +02:00
values, err := loadURLFlag(ctx, "values")
2022-03-22 09:21:55 +01:00
if err != nil {
return nil, errors.WithStack(err)
}
return values, nil
}
func loadDefaults(ctx *cli.Context) (interface{}, error) {
2022-05-05 16:22:52 +02:00
defaults, err := loadURLFlag(ctx, "defaults")
2022-03-22 09:21:55 +01:00
if err != nil {
return nil, errors.WithStack(err)
}
2022-05-05 16:22:52 +02:00
return defaults, nil
2022-03-22 09:21:55 +01:00
}
func loadData(ctx *cli.Context) (defaults interface{}, values interface{}, err error) {
values, err = loadValues(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "could not load values")
}
defaults, err = loadDefaults(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "could not load defaults")
}
merged, err := getMatchingZeroValue(values)
if err != nil {
return nil, nil, errors.WithStack(err)
}
if defaults != nil {
if err := merge.Merge(&merged, defaults, values); err != nil {
return nil, nil, errors.Wrap(err, "could not merge values")
}
values = merged
}
return defaults, values, nil
}
func getMatchingZeroValue(values interface{}) (interface{}, error) {
valuesKind := reflect.TypeOf(values).Kind()
switch valuesKind {
case reflect.Map:
return make(map[string]interface{}, 0), nil
case reflect.Slice:
return make([]interface{}, 0), nil
default:
return nil, errors.Errorf("unexpected type '%T'", values)
}
}
2022-03-22 09:21:55 +01:00
func loadSchema(ctx *cli.Context) (*jsonschema.Schema, error) {
schemaFlag := ctx.String("schema")
2022-05-05 16:22:52 +02:00
if schemaFlag == "" {
return def.Schema, nil
}
schemaTree, err := loadURLFlag(ctx, "schema")
if err != nil {
return nil, errors.WithStack(err)
}
// Reencode schema to JSON format
var buf bytes.Buffer
encoder := encjson.NewEncoder(&buf)
if err := encoder.Encode(schemaTree); err != nil {
return nil, errors.WithStack(err)
}
2022-03-22 09:21:55 +01:00
compiler := jsonschema.NewCompiler()
compiler.ExtractAnnotations = true
compiler.AssertFormat = true
compiler.AssertContent = true
2022-05-05 16:22:52 +02:00
if err := compiler.AddResource(schemaFlag, &buf); err != nil {
return nil, errors.WithStack(err)
2022-03-22 09:21:55 +01:00
}
2022-05-05 16:22:52 +02:00
schema, err := compiler.Compile(schemaFlag)
2022-03-22 09:21:55 +01:00
if err != nil {
return nil, errors.WithStack(err)
}
return schema, nil
}
2022-05-10 22:31:17 +02:00
func outputValues(ctx *cli.Context, values interface{}) error {
outputFlag := ctx.String("output")
2022-03-22 09:21:55 +01:00
2022-05-10 22:31:17 +02:00
url, err := url.Parse(outputFlag)
if err != nil {
return errors.WithStack(err)
}
2022-03-22 09:21:55 +01:00
2022-05-10 22:31:17 +02:00
encoder := newEncoder()
2022-03-22 09:21:55 +01:00
2022-05-10 22:31:17 +02:00
reader, err := encoder.Encode(url, values)
if err != nil {
return errors.WithStack(err)
2022-03-22 09:21:55 +01:00
}
2022-05-10 22:31:17 +02:00
updater := newUpdater()
writer, err := updater.Update(url)
2022-03-22 09:21:55 +01:00
if err != nil {
2022-05-10 22:31:17 +02:00
return errors.WithStack(err)
2022-03-22 09:21:55 +01:00
}
2022-05-10 22:31:17 +02:00
defer func() {
2022-08-01 17:13:56 +02:00
if err := writer.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
2022-05-10 22:31:17 +02:00
panic(errors.WithStack(err))
}
}()
if _, err := io.Copy(writer, reader); err != nil && !errors.Is(err, io.EOF) {
return errors.WithStack(err)
}
return nil
2022-03-22 09:21:55 +01:00
}
2022-05-05 16:22:52 +02:00
func newLoader() *data.Loader {
return data.NewLoader(
file.NewLoaderHandler(),
http.NewLoaderHandler(gohttp.DefaultClient),
stdin.NewLoaderHandler(),
2022-05-05 16:22:52 +02:00
)
}
func newDecoder() *data.Decoder {
return data.NewDecoder(
json.NewDecoderHandler(),
hcl.NewDecoderHandler(nil),
yaml.NewDecoderHandler(),
2022-05-05 16:22:52 +02:00
)
}
2022-05-10 22:31:17 +02:00
func newUpdater() *data.Updater {
return data.NewUpdater(
stdout.NewUpdaterHandler(),
fileUpdater.NewUpdaterHandler(),
exec.NewUpdaterHandler(),
2022-08-01 15:33:41 +02:00
null.NewUpdaterHandler(),
2022-05-10 22:31:17 +02:00
)
}
func newEncoder() *data.Encoder {
return data.NewEncoder(
json.NewEncoderHandler(),
yaml.NewEncoderHandler(),
)
}