diff --git a/internal/lang/funcs/string.go b/internal/lang/funcs/string.go
index ab6da72778..9ef709c7fb 100644
--- a/internal/lang/funcs/string.go
+++ b/internal/lang/funcs/string.go
@@ -8,6 +8,58 @@ import (
"github.com/zclconf/go-cty/cty/function"
)
+// StartsWithFunc constructs a function that checks if a string starts with
+// a specific prefix using strings.HasPrefix
+var StartsWithFunc = function.New(&function.Spec{
+ Params: []function.Parameter{
+ {
+ Name: "str",
+ Type: cty.String,
+ },
+ {
+ Name: "prefix",
+ Type: cty.String,
+ },
+ },
+ Type: function.StaticReturnType(cty.Bool),
+ Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
+ str := args[0].AsString()
+ prefix := args[1].AsString()
+
+ if strings.HasPrefix(str, prefix) {
+ return cty.True, nil
+ }
+
+ return cty.False, nil
+ },
+})
+
+// EndsWithFunc constructs a function that checks if a string ends with
+// a specific suffix using strings.HasSuffix
+var EndsWithFunc = function.New(&function.Spec{
+ Params: []function.Parameter{
+ {
+ Name: "str",
+ Type: cty.String,
+ },
+ {
+ Name: "suffix",
+ Type: cty.String,
+ },
+ },
+ Type: function.StaticReturnType(cty.Bool),
+ Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
+ str := args[0].AsString()
+ suffix := args[1].AsString()
+
+ if strings.HasSuffix(str, suffix) {
+ return cty.True, nil
+ }
+
+ return cty.False, nil
+ },
+})
+
// ReplaceFunc constructs a function that searches a given string for another
// given substring, and replaces each occurence with a given replacement string.
var ReplaceFunc = function.New(&function.Spec{
diff --git a/internal/lang/functions.go b/internal/lang/functions.go
index 1b3f88ce04..ee520965ca 100644
--- a/internal/lang/functions.go
+++ b/internal/lang/functions.go
@@ -59,6 +59,7 @@ func (s *Scope) Functions() map[string]function.Function {
"dirname": funcs.DirnameFunc,
"distinct": stdlib.DistinctFunc,
"element": stdlib.ElementFunc,
+ "endswith": funcs.EndsWithFunc,
"chunklist": stdlib.ChunklistFunc,
"file": funcs.MakeFileFunc(s.BaseDir, false),
"fileexists": funcs.MakeFileExistsFunc(s.BaseDir),
@@ -115,6 +116,7 @@ func (s *Scope) Functions() map[string]function.Function {
"slice": stdlib.SliceFunc,
"sort": stdlib.SortFunc,
"split": stdlib.SplitFunc,
+ "startswith": funcs.StartsWithFunc,
"strrev": stdlib.ReverseFunc,
"substr": stdlib.SubstrFunc,
"sum": funcs.SumFunc,
diff --git a/internal/lang/functions_test.go b/internal/lang/functions_test.go
index ea2091eb97..f2a6f738c4 100644
--- a/internal/lang/functions_test.go
+++ b/internal/lang/functions_test.go
@@ -314,6 +314,47 @@ func TestFunctions(t *testing.T) {
},
},
+ "endswith": {
+ {
+ `endswith("hello world", "world")`,
+ cty.True,
+ },
+ {
+ `endswith("hello world", "hello")`,
+ cty.False,
+ },
+ {
+ `endswith("hello world", "")`,
+ cty.True,
+ // Completely empty suffix value ( "" )
+ // will always evaluate to true for all strings.
+ },
+ {
+ `endswith("hello world", " ")`,
+ cty.False,
+ },
+ {
+ `endswith("", "")`,
+ cty.True,
+ },
+ {
+ `endswith("", " ")`,
+ cty.False,
+ },
+ {
+ `endswith(" ", "")`,
+ cty.True,
+ },
+ {
+ `endswith("", "hello")`,
+ cty.False,
+ },
+ {
+ `endswith(" ", "hello")`,
+ cty.False,
+ },
+ },
+
"file": {
{
`file("hello.txt")`,
@@ -816,6 +857,47 @@ func TestFunctions(t *testing.T) {
},
},
+ "startswith": {
+ {
+ `startswith("hello world", "hello")`,
+ cty.True,
+ },
+ {
+ `startswith("hello world", "world")`,
+ cty.False,
+ },
+ {
+ `startswith("hello world", "")`,
+ cty.True,
+ // Completely empty prefix value ( "" )
+ // will always evaluate to true for all strings.
+ },
+ {
+ `startswith("hello world", " ")`,
+ cty.False,
+ },
+ {
+ `startswith("", "")`,
+ cty.True,
+ },
+ {
+ `startswith("", " ")`,
+ cty.False,
+ },
+ {
+ `startswith(" ", "")`,
+ cty.True,
+ },
+ {
+ `startswith("", "hello")`,
+ cty.False,
+ },
+ {
+ `startswith(" ", "hello")`,
+ cty.False,
+ },
+ },
+
"strrev": {
{
`strrev("hello world")`,
diff --git a/website/data/language-nav-data.json b/website/data/language-nav-data.json
index a0ec64131e..60a649ada4 100644
--- a/website/data/language-nav-data.json
+++ b/website/data/language-nav-data.json
@@ -319,6 +319,10 @@
"title": "chomp",
"href": "/language/functions/chomp"
},
+ {
+ "title": "endswith",
+ "href": "/language/functions/endswith"
+ },
{
"title": "format",
"href": "/language/functions/format"
@@ -352,6 +356,10 @@
"title": "split",
"href": "/language/functions/split"
},
+ {
+ "title": "startswith",
+ "href": "/language/functions/startswith"
+ },
{
"title": "strrev",
"href": "/language/functions/strrev"
@@ -776,6 +784,7 @@
{ "title": "dirname", "path": "functions/dirname", "hidden": true },
{ "title": "distinct", "path": "functions/distinct", "hidden": true },
{ "title": "element", "path": "functions/element", "hidden": true },
+ { "title": "endswith", "path": "functions/endswith", "hidden": true },
{ "title": "file", "path": "functions/file", "hidden": true },
{ "title": "filebase64", "path": "functions/filebase64", "hidden": true },
{
@@ -851,6 +860,7 @@
{ "title": "slice", "path": "functions/slice", "hidden": true },
{ "title": "sort", "path": "functions/sort", "hidden": true },
{ "title": "split", "path": "functions/split", "hidden": true },
+ { "title": "startswith", "path": "functions/startswith", "hidden": true },
{ "title": "strrev", "path": "functions/strrev", "hidden": true },
{ "title": "substr", "path": "functions/substr", "hidden": true },
{ "title": "sum", "path": "functions/sum", "hidden": true },
diff --git a/website/docs/language/functions/endswith.mdx b/website/docs/language/functions/endswith.mdx
new file mode 100644
index 0000000000..d96cc88aad
--- /dev/null
+++ b/website/docs/language/functions/endswith.mdx
@@ -0,0 +1,27 @@
+---
+page_title: endswith - Functions - Configuration Language
+description: |-
+ The endswith function takes two values: a string to check and a suffix string. It returns true if the first string ends with that exact suffix.
+---
+
+# `endswith` Function
+
+`endswith` takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix.
+
+```hcl
+endswith(string, suffix)
+```
+
+## Examples
+
+```
+> endswith("hello world", "world")
+true
+
+> endswith("hello world", "hello")
+false
+```
+
+## Related Functions
+
+- [`startswith`](/language/functions/startswith) takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix.
diff --git a/website/docs/language/functions/startswith.mdx b/website/docs/language/functions/startswith.mdx
new file mode 100644
index 0000000000..d49f7aa0ba
--- /dev/null
+++ b/website/docs/language/functions/startswith.mdx
@@ -0,0 +1,27 @@
+---
+page_title: startsswith - Functions - Configuration Language
+description: |-
+ The startswith function takes two values: a string to check and a prefix string. It returns true if the string begins with that exact prefix.
+---
+
+# `startswith` Function
+
+`startswith` takes two values: a string to check and a prefix string. The function returns true if the string begins with that exact prefix.
+
+```hcl
+startswith(string, prefix)
+```
+
+## Examples
+
+```
+> startswith("hello world", "hello")
+true
+
+> startswith("hello world", "world")
+false
+```
+
+## Related Functions
+
+- [`endswith`](/language/functions/endswith) takes two values: a string to check and a suffix string. The function returns true if the first string ends with that exact suffix.
\ No newline at end of file
diff --git a/website/layouts/language.erb b/website/layouts/language.erb
index 7f7e55e7f3..f2bf83dede 100644
--- a/website/layouts/language.erb
+++ b/website/layouts/language.erb
@@ -370,6 +370,10 @@
chomp
+