2023-03-15 16:00:52 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2020-12-11 20:23:08 +00:00
package template
import (
"fmt"
"strings"
"text/template"
"github.com/hashicorp/go-multierror"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/base62"
2020-12-11 20:23:08 +00:00
)
type Opt func ( * StringTemplate ) error
func Template ( rawTemplate string ) Opt {
return func ( up * StringTemplate ) error {
up . rawTemplate = rawTemplate
return nil
}
}
// Function allows the user to specify functions for use in the template. If the name provided is a function that
// already exists in the function map, this will override the previously specified function.
func Function ( name string , f interface { } ) Opt {
return func ( up * StringTemplate ) error {
if name == "" {
return fmt . Errorf ( "missing function name" )
}
if f == nil {
return fmt . Errorf ( "missing function" )
}
up . funcMap [ name ] = f
return nil
}
}
// StringTemplate creates strings based on the provided template.
// This uses the go templating language, so anything that adheres to that language will function in this struct.
// There are several custom functions available for use in the template:
2021-01-13 17:49:17 +00:00
// - random
2020-12-11 20:23:08 +00:00
// - Randomly generated characters. This uses the charset specified in RandomCharset. Must include a length.
// Example: {{ rand 20 }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - truncate
2020-12-11 20:23:08 +00:00
// - Truncates the previous value to the specified length. Must include a maximum length.
// Example: {{ .DisplayName | truncate 10 }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - truncate_sha256
2020-12-11 20:23:08 +00:00
// - Truncates the previous value to the specified length. If the original length is greater than the length
// 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
// be no longer than the length specified.
// Example: {{ .DisplayName | truncate_sha256 30 }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - uppercase
2020-12-11 20:23:08 +00:00
// - Uppercases the previous value.
// Example: {{ .RoleName | uppercase }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - lowercase
2020-12-11 20:23:08 +00:00
// - Lowercases the previous value.
// Example: {{ .DisplayName | lowercase }}
2022-09-08 00:31:20 +00:00
//
2020-12-11 20:23:08 +00:00
// - replace
// - Performs a string find & replace
// Example: {{ .DisplayName | replace - _ }}
2022-09-08 00:31:20 +00:00
//
2020-12-11 20:23:08 +00:00
// - sha256
// - SHA256 hashes the previous value.
// Example: {{ .DisplayName | sha256 }}
2022-09-08 00:31:20 +00:00
//
2021-01-27 18:59:06 +00:00
// - base64
// - base64 encodes the previous value.
// Example: {{ .DisplayName | base64 }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - unix_time
2020-12-11 20:23:08 +00:00
// - Provides the current unix time in seconds.
2021-01-13 17:49:17 +00:00
// Example: {{ unix_time }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - unix_time_millis
// - Provides the current unix time in milliseconds.
// Example: {{ unix_time_millis }}
2022-09-08 00:31:20 +00:00
//
2021-01-13 17:49:17 +00:00
// - timestamp
// - Provides the current time. Must include a standard Go format string
2022-09-08 00:31:20 +00:00
//
2020-12-11 20:23:08 +00:00
// - uuid
// - Generates a UUID
// Example: {{ uuid }}
type StringTemplate struct {
rawTemplate string
tmpl * template . Template
funcMap template . FuncMap
}
// NewTemplate creates a StringTemplate. No arguments are required
// as this has reasonable defaults for all values.
// The default template is specified in the DefaultTemplate constant.
func NewTemplate ( opts ... Opt ) ( up StringTemplate , err error ) {
up = StringTemplate {
funcMap : map [ string ] interface { } {
"random" : base62 . Random ,
"truncate" : truncate ,
"truncate_sha256" : truncateSHA256 ,
"uppercase" : uppercase ,
"lowercase" : lowercase ,
"replace" : replace ,
"sha256" : hashSHA256 ,
2021-01-27 18:59:06 +00:00
"base64" : encodeBase64 ,
2020-12-11 20:23:08 +00:00
2021-01-13 17:49:17 +00:00
"unix_time" : unixTime ,
"unix_time_millis" : unixTimeMillis ,
"timestamp" : timestamp ,
"uuid" : uuid ,
2020-12-11 20:23:08 +00:00
} ,
}
merr := & multierror . Error { }
for _ , opt := range opts {
merr = multierror . Append ( merr , opt ( & up ) )
}
err = merr . ErrorOrNil ( )
if err != nil {
return up , err
}
if up . rawTemplate == "" {
return StringTemplate { } , fmt . Errorf ( "missing template" )
}
tmpl , err := template . New ( "template" ) .
Funcs ( up . funcMap ) .
Parse ( up . rawTemplate )
if err != nil {
return StringTemplate { } , fmt . Errorf ( "unable to parse template: %w" , err )
}
up . tmpl = tmpl
return up , nil
}
// Generate based on the provided template
func ( up StringTemplate ) Generate ( data interface { } ) ( string , error ) {
if up . tmpl == nil || up . rawTemplate == "" {
return "" , fmt . Errorf ( "failed to generate: template not initialized" )
}
str := & strings . Builder { }
err := up . tmpl . Execute ( str , data )
if err != nil {
return "" , fmt . Errorf ( "unable to apply template: %w" , err )
}
return str . String ( ) , nil
}