Skip to content

Commit

Permalink
Merge pull request #4 from harsimranmaan/add-sprig
Browse files Browse the repository at this point in the history
Refine Sprig support
  • Loading branch information
harsimranmaan authored Feb 17, 2023
2 parents 481f868 + 70c70be commit ee1d792
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 29 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ test:
.PHONY: get
get:
go get ./...

.PHONY: gen
gen:
go generate ./...
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
65 changes: 48 additions & 17 deletions generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"go/format"
"os"
"reflect"
"strings"
Expand All @@ -11,34 +12,44 @@ 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("<not a function>")
panic(fmt.Sprintf("not a function: %s", funcName))
}
var params []string
for i := 0; i < t.NumIn(); i++ {
params = append(params, t.In(i).String())
}
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
Expand All @@ -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 {
Expand All @@ -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)
}

Expand All @@ -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
12 changes: 10 additions & 2 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
// ]
}
108 changes: 98 additions & 10 deletions sprig.gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
},
},
}
Expand Down

0 comments on commit ee1d792

Please sign in to comment.