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  }