internal/template/funcmap.go (146 lines of code) (raw):
// Copyright 2021 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package template
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"github.com/rodaine/hclencoder"
)
var funcMap = map[string]interface{}{
"get": get,
"has": has,
"hcl": hcl,
"hclField": hclField,
"merge": merge,
"replace": replace,
"resourceName": resourceName,
"now": time.Now,
"trimSpace": strings.TrimSpace,
"regexReplaceAll": regexReplaceAll,
"makeSlice": makeSlice,
"schemaDescription": schemaDescription,
"substr": substr,
"getEncodedJSON": getEncodedJSON,
"getEncodedEscapedJSON": getEncodedEscapedJSON,
}
// invalidIDRE defines the invalid characters not allowed in terraform resource names.
var invalidIDRE = regexp.MustCompile("[^a-z0-9_]")
// get allows a template to optionally lookup a value from a dict.
// If a value is not found, it will check for a single default.
// If there is no default, it will return nil.
// There should be at most one default value set.
//
// Keys can reference multiple levels of maps by using "." to indicate a new level
// (e.g. L1.L2 will lookup key L1 in the top level map then L2 within the value.)
func get(m map[string]interface{}, key string, def ...interface{}) interface{} {
split := strings.Split(key, ".")
for i, k := range split {
v, ok := m[k]
switch {
case !ok:
if len(def) == 1 {
return def[0]
}
return nil
case i == len(split)-1:
return v
default:
m = v.(map[string]interface{})
}
}
return nil
}
// has determines whether the key is found in the given map.
// Keys can reference multiple levels of maps by using "." to indicate a new level
// (e.g. L1.L2 will lookup key L1 in the top level map then L2 within the value.)
func has(m map[string]interface{}, key string) bool {
return get(m, key) != nil
}
// hcl marshals the given value to HCL.
func hcl(v interface{}) (string, error) {
b, err := hclencoder.Encode(v)
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}
// hclField returns a hcl marshaled field e.g. `name = "foo"`, if present.
// For required fields, use the hcl func.
func hclField(m map[string]interface{}, key string) (string, error) {
v, ok := m[key]
if !ok {
return "", nil
}
b, err := hclencoder.Encode(v)
if err != nil {
return "", err
}
return fmt.Sprintf("%s = %s", key, string(b)), nil
}
func merge(srcs ...map[string]interface{}) (interface{}, error) {
dst := make(map[string]interface{})
for _, src := range srcs {
if err := MergeData(dst, src); err != nil {
return nil, err
}
}
return dst, nil
}
// resourceName builds a Terraform resource name.
// Invalid characters that are not allowed in terraform resource names such as
// "-", ".", and "@" are replaced with "_".
// The resource name is fetched from the given map and key.
// To override the default behaviour, a user can set the key 'resource_name' in
// given map, which will be given precedence.
func resourceName(m map[string]interface{}, key string) (string, error) {
v, ok := m["resource_name"]
if !ok {
v, ok = m[key]
if !ok {
return "", fmt.Errorf("map did not contain key \"resource_name\" nor %q", key)
}
}
name, ok := v.(string)
if !ok {
return "", fmt.Errorf("resource name value %v is not a string", v)
}
return invalidIDRE.ReplaceAllString(strings.ToLower(name), "_"), nil
}
// alias for strings.Replace with the number of characters fixed to -1 (all).
func replace(s, old, new string) string {
return strings.Replace(s, old, new, -1)
}
func regexReplaceAll(regex string, s string, repl string) (string, error) {
r, err := regexp.Compile(regex)
if err != nil {
return "", err
}
return r.ReplaceAllString(s, repl), nil
}
// makeSlice combines the arguments into an array and returns the array.
func makeSlice(args ...interface{}) []interface{} {
return args
}
// schemaDescription returns a heredoc or single line string
// according to the description provided format.
func schemaDescription(s string) string {
if strings.Contains(s, "\n") {
return fmt.Sprintf("<<EOF\n%s\nEOF", s)
}
return fmt.Sprintf(`"%s"`, s)
}
// substr returns a substring that starts at index 'start'
// and spans 'length' characters (or until the end of the string,
// whichever comes first).
func substr(s string, start int, length int) (string, error) {
if start < 0 || start >= len(s) {
return "", fmt.Errorf("start index parameter has a invalid value: %d", start)
}
if length < 0 {
return "", fmt.Errorf("length parameter has a invalid value: %d", length)
}
if start+length > len(s) {
length = len(s) - start
}
return s[start : start+length], nil
}
// getEncodedJSON returns an encoded JSON string from a given map
func getEncodedJSON(m map[string]interface{}) (string, error) {
jsonStr, err := json.Marshal(m)
if err != nil {
return "", fmt.Errorf("JSON marshalling failed due to %w", err)
}
return string(jsonStr), nil
}
// getEncodedEscapedJSON returns an encoded, escaped JSON string from a given map
func getEncodedEscapedJSON(m map[string]interface{}) (string, error) {
jsonStr, err := json.Marshal(m)
if err != nil {
return "", fmt.Errorf("JSON marshalling failed due to %w", err)
}
escapedJSONStr, err := jsonEscape(string(jsonStr))
if err != nil {
return "", fmt.Errorf("JSON escaping failed due to %w", err)
}
return escapedJSONStr, nil
}
// jsonEscape return an escaped JSON string
func jsonEscape(i string) (string, error) {
jsonStr, err := json.Marshal(i)
if err != nil {
return "", fmt.Errorf("JSON marshalling failed due to %w", err)
}
str := string(jsonStr)
return str[1 : len(str)-1], nil
}