github.com/hashicorp/vault/sdk@v0.11.0/helper/template/template.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package template
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/hashicorp/go-secure-stdlib/base62"
    13  )
    14  
    15  type Opt func(*StringTemplate) error
    16  
    17  func Template(rawTemplate string) Opt {
    18  	return func(up *StringTemplate) error {
    19  		up.rawTemplate = rawTemplate
    20  		return nil
    21  	}
    22  }
    23  
    24  // Function allows the user to specify functions for use in the template. If the name provided is a function that
    25  // already exists in the function map, this will override the previously specified function.
    26  func Function(name string, f interface{}) Opt {
    27  	return func(up *StringTemplate) error {
    28  		if name == "" {
    29  			return fmt.Errorf("missing function name")
    30  		}
    31  		if f == nil {
    32  			return fmt.Errorf("missing function")
    33  		}
    34  		up.funcMap[name] = f
    35  		return nil
    36  	}
    37  }
    38  
    39  // StringTemplate creates strings based on the provided template.
    40  // This uses the go templating language, so anything that adheres to that language will function in this struct.
    41  // There are several custom functions available for use in the template:
    42  // - random
    43  //   - Randomly generated characters. This uses the charset specified in RandomCharset. Must include a length.
    44  //     Example: {{ rand 20 }}
    45  //
    46  // - truncate
    47  //   - Truncates the previous value to the specified length. Must include a maximum length.
    48  //     Example: {{ .DisplayName | truncate 10 }}
    49  //
    50  // - truncate_sha256
    51  //   - Truncates the previous value to the specified length. If the original length is greater than the length
    52  //     specified, the remaining characters will be sha256 hashed and appended to the end. The hash will be only the first 8 characters The maximum length will
    53  //     be no longer than the length specified.
    54  //     Example: {{ .DisplayName | truncate_sha256 30 }}
    55  //
    56  // - uppercase
    57  //   - Uppercases the previous value.
    58  //     Example: {{ .RoleName | uppercase }}
    59  //
    60  // - lowercase
    61  //   - Lowercases the previous value.
    62  //     Example: {{ .DisplayName | lowercase }}
    63  //
    64  // - replace
    65  //   - Performs a string find & replace
    66  //     Example: {{ .DisplayName | replace - _ }}
    67  //
    68  // - sha256
    69  //   - SHA256 hashes the previous value.
    70  //     Example: {{ .DisplayName | sha256 }}
    71  //
    72  // - base64
    73  //   - base64 encodes the previous value.
    74  //     Example: {{ .DisplayName | base64 }}
    75  //
    76  // - unix_time
    77  //   - Provides the current unix time in seconds.
    78  //     Example: {{ unix_time }}
    79  //
    80  // - unix_time_millis
    81  //   - Provides the current unix time in milliseconds.
    82  //     Example: {{ unix_time_millis }}
    83  //
    84  // - timestamp
    85  //   - Provides the current time. Must include a standard Go format string
    86  //
    87  // - uuid
    88  //   - Generates a UUID
    89  //     Example: {{ uuid }}
    90  type StringTemplate struct {
    91  	rawTemplate string
    92  	tmpl        *template.Template
    93  	funcMap     template.FuncMap
    94  }
    95  
    96  // NewTemplate creates a StringTemplate. No arguments are required
    97  // as this has reasonable defaults for all values.
    98  // The default template is specified in the DefaultTemplate constant.
    99  func NewTemplate(opts ...Opt) (up StringTemplate, err error) {
   100  	up = StringTemplate{
   101  		funcMap: map[string]interface{}{
   102  			"random":          base62.Random,
   103  			"truncate":        truncate,
   104  			"truncate_sha256": truncateSHA256,
   105  			"uppercase":       uppercase,
   106  			"lowercase":       lowercase,
   107  			"replace":         replace,
   108  			"sha256":          hashSHA256,
   109  			"base64":          encodeBase64,
   110  
   111  			"unix_time":        unixTime,
   112  			"unix_time_millis": unixTimeMillis,
   113  			"timestamp":        timestamp,
   114  			"uuid":             uuid,
   115  		},
   116  	}
   117  
   118  	merr := &multierror.Error{}
   119  	for _, opt := range opts {
   120  		merr = multierror.Append(merr, opt(&up))
   121  	}
   122  
   123  	err = merr.ErrorOrNil()
   124  	if err != nil {
   125  		return up, err
   126  	}
   127  
   128  	if up.rawTemplate == "" {
   129  		return StringTemplate{}, fmt.Errorf("missing template")
   130  	}
   131  
   132  	tmpl, err := template.New("template").
   133  		Funcs(up.funcMap).
   134  		Parse(up.rawTemplate)
   135  	if err != nil {
   136  		return StringTemplate{}, fmt.Errorf("unable to parse template: %w", err)
   137  	}
   138  	up.tmpl = tmpl
   139  
   140  	return up, nil
   141  }
   142  
   143  // Generate based on the provided template
   144  func (up StringTemplate) Generate(data interface{}) (string, error) {
   145  	if up.tmpl == nil || up.rawTemplate == "" {
   146  		return "", fmt.Errorf("failed to generate: template not initialized")
   147  	}
   148  	str := &strings.Builder{}
   149  	err := up.tmpl.Execute(str, data)
   150  	if err != nil {
   151  		return "", fmt.Errorf("unable to apply template: %w", err)
   152  	}
   153  
   154  	return str.String(), nil
   155  }