diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index 19595e735..57c811d38 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" "github.com/mitchellh/go-homedir" + "time" ) // stringSliceToVariableValue converts a string slice into the value @@ -85,6 +86,7 @@ func Funcs() map[string]ast.Function { "signum": interpolationFuncSignum(), "sort": interpolationFuncSort(), "split": interpolationFuncSplit(), + "timestamp": interpolationFuncTimestamp(), "title": interpolationFuncTitle(), "trimspace": interpolationFuncTrimSpace(), "upper": interpolationFuncUpper(), @@ -1109,6 +1111,17 @@ func interpolationFuncUUID() ast.Function { } } +// interpolationFuncTimestamp +func interpolationFuncTimestamp() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + return time.Now().UTC().Format(time.RFC3339), nil + }, + } +} + // interpolationFuncTitle implements the "title" function that returns a copy of the // string in which first characters of all the words are capitalized. func interpolationFuncTitle() ast.Function { diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index ccdb22363..548993b73 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" + "time" ) func TestInterpolateFuncZipMap(t *testing.T) { @@ -1921,6 +1922,27 @@ func TestInterpolateFuncUUID(t *testing.T) { } } +func TestInterpolateFuncTimestamp(t *testing.T) { + currentTime := time.Now().UTC() + ast, err := hil.Parse("${timestamp()}") + if err != nil { + t.Fatalf("err: %s", err) + } + + result, err := hil.Eval(ast, langEvalConfig(nil)) + if err != nil { + t.Fatalf("err: %s", err) + } + resultTime, err := time.Parse(time.RFC3339, result.Value.(string)) + if err != nil { + t.Fatalf("Error parsing timestamp: %s", err) + } + + if resultTime.Sub(currentTime).Seconds() > 10.0 { + t.Fatalf("Timestamp Diff too large. Expected: %s\nRecieved: %s", currentTime.Format(time.RFC3339), result.Value.(string)) + } +} + type testFunctionConfig struct { Cases []testFunctionCase Vars map[string]ast.Variable diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index fa8cdbf59..bca61c835 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -266,6 +266,10 @@ The supported built-in functions are: in brackets to indicate that the output is actually a list, e.g. `a_resource_param = ["${split(",", var.CSV_STRING)}"]`. Example: `split(",", module.amod.server_ids)` + + * `timestamp()` - Returns a UTC timestamp string in RFC 3339 format. This string will change with every + invocation of the function, so in order to prevent diffs on every plan & apply, it must be used with the + [`ignore_changes`](/docs/configuration/resources.html#ignore-changes) lifecycle attribute. * `title(string)` - Returns a copy of the string with the first characters of all the words capitalized.