From 70c70beb4f02dfd1af5573c57690198d336b2e1a Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Fri, 17 Feb 2023 15:25:14 -0800 Subject: [PATCH] Refine Sprig support --- Makefile | 4 ++ README.md | 10 ++++ generate/main.go | 65 +++++++++++++++++++------- integration_test.go | 12 ++++- sprig.gen.go | 108 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 170 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 5ff8d25..bf13dc1 100644 --- a/Makefile +++ b/Makefile @@ -4,3 +4,7 @@ test: .PHONY: get get: go get ./... + +.PHONY: gen +gen: + go generate ./... diff --git a/README.md b/README.md index 9cd752a..ade7bc6 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ Supports the following functions for jsonnet: - `sha256` - `parseUrl` +[Sprig](https://github.com/Masterminds/sprig) functions are supported in experimental stage and can be added to the [code generator](./generate.main.go) to enable those. + +The following Sprig functions are supported at the moment. This list would cover most of the Sprig functions in the future. + +- `upper` +- `snakecase` +- `camelcase` +- `kebabcase` +- `decryptAES` + ## Usage The library can you used to extend jsonnet functionality offered by your application. diff --git a/generate/main.go b/generate/main.go index 3962c42..a28e10e 100644 --- a/generate/main.go +++ b/generate/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "go/format" "os" "reflect" "strings" @@ -11,24 +12,32 @@ import ( ) // only add hermitic functions here -var allowedFunctions = map[string]bool{ - "hello": true, +var allowedFunctions = []string{ + "hello", + "upper", + "snakecase", + "camelcase", + "kebabcase", - "upper": true, + // Crypto: + //"encryptAES", + "decryptAES", } func main() { + var outputType = make(map[string]bool) var filename string flag.StringVar(&filename, "file", "generated.gen.go", "generated file name") flag.Parse() p := "" - for name, signature := range sprig.GenericFuncMap() { - if !allowedFunctions[name] { - continue + for _, funcName := range allowedFunctions { + signature, ok := sprig.GenericFuncMap()[funcName] + if !ok { + panic(fmt.Sprintf("function not found: %s", funcName)) } t := reflect.TypeOf(signature) if t.Kind() != reflect.Func { - panic("") + panic(fmt.Sprintf("not a function: %s", funcName)) } var params []string for i := 0; i < t.NumIn(); i++ { @@ -36,9 +45,11 @@ func main() { } var outputs []string for i := 0; i < t.NumOut(); i++ { - outputs = append(outputs, t.Out(i).String()) + output := t.Out(i).String() + outputs = append(outputs, output) + outputType[output] = true } - p += jsonnetFunc(name, params, outputs) + p += jsonnetFunc(funcName, params, outputs) } content := fmt.Sprintf(`package jsonnet @@ -55,16 +66,26 @@ func SprigFuncs() []*jsonnet.NativeFunction{ %s } }`, p) - err := os.WriteFile(filename, []byte(content), 0644) + formatted, err := format.Source([]byte(content)) + if err != nil { + panic(err) + } + err = os.WriteFile(filename, formatted, 0644) if err != nil { panic(err) } + fmt.Println(outputType) } func jsonnetFunc(name string, params, outputs []string) string { - returnNil := ", nil" - if len(outputs) == 2 { - returnNil = "" + returnErr := "" + for _, o := range outputs { + if o == "error" { + returnErr = ", err" + } + } + if len(outputs) > 2 { + panic(fmt.Sprintf("upto 1 output variable is supported: %s", name)) } var paramNames, paramNamesQuoted []string var typeConversions = []string{fmt.Sprintf(`if len(dataString) != %d { @@ -74,11 +95,17 @@ func jsonnetFunc(name string, params, outputs []string) string { paramName := fmt.Sprintf("p%d", i) paramNamesQuoted = append(paramNamesQuoted, fmt.Sprintf("%q", paramName)) paramNames = append(paramNames, paramName) + // todo: extract method + dataTypeCast := dataType + // if dataTypeCast == "int" { + // dataTypeCast = "float64" + // } typeConversion := fmt.Sprintf(` - %s, ok := dataString[%d].(%s) + %s, ok := dataString[%d].(%s)`, paramName, i, dataTypeCast) + typeConversion += fmt.Sprintf(` if !ok { return nil, fmt.Errorf("%%q failed to read input param %%q", %q, %q) - }`, paramName, i, dataType, name, paramName) + }`, name, paramName) typeConversions = append(typeConversions, typeConversion) } @@ -92,7 +119,11 @@ func jsonnetFunc(name string, params, outputs []string) string { if !ok { return nil, fmt.Errorf("mismatch function definition for %%q", %q) } - return f(%s)%s + o1%s := f(%s) + return o1, err }, - },`, name, strings.Join(paramNamesQuoted, `,`), strings.Join(typeConversions, "\n"), name, strings.Join(params, `,`), strings.Join(outputs, `,`), name, strings.Join(paramNames, `,`), returnNil) + },`, fmt.Sprintf("sprig.%s", name), strings.Join(paramNamesQuoted, `,`), strings.Join(typeConversions, "\n"), name, strings.Join(params, `,`), strings.Join(outputs, `,`), name, returnErr, strings.Join(paramNames, `,`)) } + +// TODO: Handle []string to []interface, handle jsonnet number to int conversions +// would unlock a majority of functions diff --git a/integration_test.go b/integration_test.go index b884409..978b306 100644 --- a/integration_test.go +++ b/integration_test.go @@ -42,10 +42,18 @@ func ExampleSprigFuncs() { for _, f := range f.SprigFuncs() { vm.NativeFunction(f) } - output, _ := vm.EvaluateAnonymousSnippet("main.jsonnet", `local a = std.native("hello")(); local b = std.native("upper")("hSm");[a,b]`) + output, err := vm.EvaluateAnonymousSnippet("main.jsonnet", `local a = std.native("sprig.hello")(); + local b = std.native("sprig.upper")("hSm"); + local c = std.native("sprig.decryptAES")("secretkey", "30tEfhuJSVRhpG97XCuWgz2okj7L8vQ1s6V9zVUPeDQ="); + + [a,b,c]`) + if err != nil { + fmt.Println(err) + } fmt.Println(output) // Output: [ // "Hello!", - // "HSM" + // "HSM", + // "plaintext" // ] } diff --git a/sprig.gen.go b/sprig.gen.go index 1dca2c2..7fb6ce5 100644 --- a/sprig.gen.go +++ b/sprig.gen.go @@ -10,8 +10,24 @@ import ( func SprigFuncs() []*jsonnet.NativeFunction { return []*jsonnet.NativeFunction{ + + { + Name: "sprig.hello", + Params: ast.Identifiers{}, + Func: func(dataString []any) (res any, err error) { + if len(dataString) != 0 { + return nil, fmt.Errorf("bad arguments to %q: needs 0 argument", "hello") + } + f, ok := sprig.GenericFuncMap()["hello"].(func() string) + if !ok { + return nil, fmt.Errorf("mismatch function definition for %q", "hello") + } + o1 := f() + return o1, err + }, + }, { - Name: "upper", + Name: "sprig.upper", Params: ast.Identifiers{"p0"}, Func: func(dataString []any) (res any, err error) { if len(dataString) != 1 { @@ -24,23 +40,95 @@ func SprigFuncs() []*jsonnet.NativeFunction { } f, ok := sprig.GenericFuncMap()["upper"].(func(string) string) if !ok { - return nil, fmt.Errorf("mismatch function defintion for %q", "upper") + return nil, fmt.Errorf("mismatch function definition for %q", "upper") } - return f(p0), nil + o1 := f(p0) + return o1, err }, }, { - Name: "hello", - Params: ast.Identifiers{}, + Name: "sprig.snakecase", + Params: ast.Identifiers{"p0"}, Func: func(dataString []any) (res any, err error) { - if len(dataString) != 0 { - return nil, fmt.Errorf("bad arguments to %q: needs 0 argument", "hello") + if len(dataString) != 1 { + return nil, fmt.Errorf("bad arguments to %q: needs 1 argument", "snakecase") } - f, ok := sprig.GenericFuncMap()["hello"].(func() string) + + p0, ok := dataString[0].(string) + if !ok { + return nil, fmt.Errorf("%q failed to read input param %q", "snakecase", "p0") + } + f, ok := sprig.GenericFuncMap()["snakecase"].(func(string) string) + if !ok { + return nil, fmt.Errorf("mismatch function definition for %q", "snakecase") + } + o1 := f(p0) + return o1, err + }, + }, + { + Name: "sprig.camelcase", + Params: ast.Identifiers{"p0"}, + Func: func(dataString []any) (res any, err error) { + if len(dataString) != 1 { + return nil, fmt.Errorf("bad arguments to %q: needs 1 argument", "camelcase") + } + + p0, ok := dataString[0].(string) + if !ok { + return nil, fmt.Errorf("%q failed to read input param %q", "camelcase", "p0") + } + f, ok := sprig.GenericFuncMap()["camelcase"].(func(string) string) + if !ok { + return nil, fmt.Errorf("mismatch function definition for %q", "camelcase") + } + o1 := f(p0) + return o1, err + }, + }, + { + Name: "sprig.kebabcase", + Params: ast.Identifiers{"p0"}, + Func: func(dataString []any) (res any, err error) { + if len(dataString) != 1 { + return nil, fmt.Errorf("bad arguments to %q: needs 1 argument", "kebabcase") + } + + p0, ok := dataString[0].(string) + if !ok { + return nil, fmt.Errorf("%q failed to read input param %q", "kebabcase", "p0") + } + f, ok := sprig.GenericFuncMap()["kebabcase"].(func(string) string) + if !ok { + return nil, fmt.Errorf("mismatch function definition for %q", "kebabcase") + } + o1 := f(p0) + return o1, err + }, + }, + { + Name: "sprig.decryptAES", + Params: ast.Identifiers{"p0", "p1"}, + Func: func(dataString []any) (res any, err error) { + if len(dataString) != 2 { + return nil, fmt.Errorf("bad arguments to %q: needs 2 argument", "decryptAES") + } + + p0, ok := dataString[0].(string) + if !ok { + return nil, fmt.Errorf("%q failed to read input param %q", "decryptAES", "p0") + } + + p1, ok := dataString[1].(string) + if !ok { + return nil, fmt.Errorf("%q failed to read input param %q", "decryptAES", "p1") + } + f, ok := sprig.GenericFuncMap()["decryptAES"].(func(string, string) (string, error)) if !ok { - return nil, fmt.Errorf("mismatch function defintion for %q", "hello") + return nil, fmt.Errorf("mismatch function definition for %q", "decryptAES") } - return f(), nil + o1, err := f(p0, p1) + return o1, err }, }, }