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-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"
|
2022-05-09 14:23:01 +02:00
|
|
|
"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"
|
2022-05-09 14:23:01 +02:00
|
|
|
"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"
|
|
|
|
"forge.cadoles.com/wpetit/formidable/internal/data/updater/stdout"
|
2022-03-22 09:21:55 +01:00
|
|
|
"forge.cadoles.com/wpetit/formidable/internal/def"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
|
|
|
"github.com/urfave/cli/v2"
|
2022-05-09 14:23:01 +02:00
|
|
|
|
|
|
|
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"},
|
|
|
|
Usage: "Default values as JSON or file path prefixed by '@'",
|
2022-05-05 16:22:52 +02:00
|
|
|
Value: "",
|
2022-03-22 09:21:55 +01:00
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "values",
|
|
|
|
Aliases: []string{"v"},
|
|
|
|
Usage: "Current values as JSON or file path prefixed by '@'",
|
2022-05-05 16:22:52 +02:00
|
|
|
Value: "",
|
2022-03-22 09:21:55 +01:00
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
|
|
Name: "schema",
|
|
|
|
Aliases: []string{"s"},
|
|
|
|
Usage: "Use `schema_file` as schema",
|
|
|
|
TakesFile: true,
|
|
|
|
},
|
|
|
|
&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 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() {
|
|
|
|
if err := writer.Close(); err != nil {
|
|
|
|
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(),
|
2022-05-09 14:23:01 +02:00
|
|
|
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),
|
2022-05-09 14:23:01 +02:00
|
|
|
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(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newEncoder() *data.Encoder {
|
|
|
|
return data.NewEncoder(
|
|
|
|
json.NewEncoderHandler(),
|
|
|
|
yaml.NewEncoderHandler(),
|
|
|
|
)
|
|
|
|
}
|