github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/jsonnet/native/funcs.go (about) 1 package native 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/json" 7 "fmt" 8 "io" 9 "regexp" 10 "strings" 11 12 jsonnet "github.com/google/go-jsonnet" 13 "github.com/google/go-jsonnet/ast" 14 "github.com/grafana/tanka/pkg/helm" 15 "github.com/grafana/tanka/pkg/kustomize" 16 "github.com/pkg/errors" 17 yaml "gopkg.in/yaml.v3" 18 ) 19 20 // Funcs returns a slice of native Go functions that shall be available 21 // from Jsonnet using `std.nativeFunc` 22 func Funcs() []*jsonnet.NativeFunction { 23 return []*jsonnet.NativeFunction{ 24 // Parse serialized data into dicts 25 parseJSON(), 26 parseYAML(), 27 28 // Convert serializations 29 manifestJSONFromJSON(), 30 manifestYAMLFromJSON(), 31 32 // Regular expressions 33 escapeStringRegex(), 34 regexMatch(), 35 regexSubst(), 36 37 // Hash functions 38 hashSha256(), 39 40 helm.NativeFunc(helm.ExecHelm{}), 41 kustomize.NativeFunc(kustomize.ExecKustomize{}), 42 } 43 } 44 45 // parseJSON wraps `json.Unmarshal` to convert a json string into a dict 46 func parseJSON() *jsonnet.NativeFunction { 47 return &jsonnet.NativeFunction{ 48 Name: "parseJson", 49 Params: ast.Identifiers{"json"}, 50 Func: func(dataString []interface{}) (res interface{}, err error) { 51 data := []byte(dataString[0].(string)) 52 err = json.Unmarshal(data, &res) 53 return 54 }, 55 } 56 } 57 58 func hashSha256() *jsonnet.NativeFunction { 59 return &jsonnet.NativeFunction{ 60 Name: "sha256", 61 Params: ast.Identifiers{"str"}, 62 Func: func(dataString []interface{}) (interface{}, error) { 63 h := sha256.New() 64 h.Write([]byte(dataString[0].(string))) 65 return fmt.Sprintf("%x", h.Sum(nil)), nil 66 }, 67 } 68 } 69 70 // parseYAML wraps `yaml.Unmarshal` to convert a string of yaml document(s) into a (set of) dicts 71 func parseYAML() *jsonnet.NativeFunction { 72 return &jsonnet.NativeFunction{ 73 Name: "parseYaml", 74 Params: ast.Identifiers{"yaml"}, 75 Func: func(dataString []interface{}) (interface{}, error) { 76 data := []byte(dataString[0].(string)) 77 ret := []interface{}{} 78 79 d := yaml.NewDecoder(bytes.NewReader(data)) 80 for { 81 var doc, jsonDoc interface{} 82 if err := d.Decode(&doc); err != nil { 83 if err == io.EOF { 84 break 85 } 86 return nil, errors.Wrap(err, "parsing yaml") 87 } 88 89 jsonRaw, err := json.Marshal(doc) 90 if err != nil { 91 return nil, errors.Wrap(err, "converting yaml to json") 92 } 93 94 if err := json.Unmarshal(jsonRaw, &jsonDoc); err != nil { 95 return nil, errors.Wrap(err, "converting yaml to json") 96 } 97 98 ret = append(ret, jsonDoc) 99 } 100 101 return ret, nil 102 }, 103 } 104 } 105 106 // manifestJSONFromJSON reserializes JSON which allows to change the indentation. 107 func manifestJSONFromJSON() *jsonnet.NativeFunction { 108 return &jsonnet.NativeFunction{ 109 Name: "manifestJsonFromJson", 110 Params: ast.Identifiers{"json", "indent"}, 111 Func: func(data []interface{}) (interface{}, error) { 112 indent := int(data[1].(float64)) 113 dataBytes := []byte(data[0].(string)) 114 dataBytes = bytes.TrimSpace(dataBytes) 115 buf := bytes.Buffer{} 116 if err := json.Indent(&buf, dataBytes, "", strings.Repeat(" ", indent)); err != nil { 117 return "", err 118 } 119 buf.WriteString("\n") 120 return buf.String(), nil 121 }, 122 } 123 } 124 125 // manifestYamlFromJSON serializes a JSON string as a YAML document 126 func manifestYAMLFromJSON() *jsonnet.NativeFunction { 127 return &jsonnet.NativeFunction{ 128 Name: "manifestYamlFromJson", 129 Params: ast.Identifiers{"json"}, 130 Func: func(data []interface{}) (interface{}, error) { 131 var input interface{} 132 dataBytes := []byte(data[0].(string)) 133 if err := json.Unmarshal(dataBytes, &input); err != nil { 134 return "", err 135 } 136 output, err := yaml.Marshal(input) 137 return string(output), err 138 }, 139 } 140 } 141 142 // escapeStringRegex escapes all regular expression metacharacters 143 // and returns a regular expression that matches the literal text. 144 func escapeStringRegex() *jsonnet.NativeFunction { 145 return &jsonnet.NativeFunction{ 146 Name: "escapeStringRegex", 147 Params: ast.Identifiers{"str"}, 148 Func: func(s []interface{}) (interface{}, error) { 149 return regexp.QuoteMeta(s[0].(string)), nil 150 }, 151 } 152 } 153 154 // regexMatch returns whether the given string is matched by the given re2 regular expression. 155 func regexMatch() *jsonnet.NativeFunction { 156 return &jsonnet.NativeFunction{ 157 Name: "regexMatch", 158 Params: ast.Identifiers{"regex", "string"}, 159 Func: func(s []interface{}) (interface{}, error) { 160 return regexp.MatchString(s[0].(string), s[1].(string)) 161 }, 162 } 163 } 164 165 // regexSubst replaces all matches of the re2 regular expression with another string. 166 func regexSubst() *jsonnet.NativeFunction { 167 return &jsonnet.NativeFunction{ 168 Name: "regexSubst", 169 Params: ast.Identifiers{"regex", "src", "repl"}, 170 Func: func(data []interface{}) (interface{}, error) { 171 regex, src, repl := data[0].(string), data[1].(string), data[2].(string) 172 173 r, err := regexp.Compile(regex) 174 if err != nil { 175 return "", err 176 } 177 return r.ReplaceAllString(src, repl), nil 178 }, 179 } 180 }