Merge pull request #1094 from hashicorp/f-gsub
config: add "replace" function [GH-1029]
This commit is contained in:
commit
76e3cfcf31
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -15,11 +16,15 @@ var Funcs map[string]ast.Function
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Funcs = map[string]ast.Function{
|
Funcs = map[string]ast.Function{
|
||||||
"concat": interpolationFuncConcat(),
|
|
||||||
"file": interpolationFuncFile(),
|
"file": interpolationFuncFile(),
|
||||||
"join": interpolationFuncJoin(),
|
"join": interpolationFuncJoin(),
|
||||||
"element": interpolationFuncElement(),
|
"element": interpolationFuncElement(),
|
||||||
|
"replace": interpolationFuncReplace(),
|
||||||
"split": interpolationFuncSplit(),
|
"split": interpolationFuncSplit(),
|
||||||
|
|
||||||
|
// Concat is a little useless now since we supported embeddded
|
||||||
|
// interpolations but we keep it around for backwards compat reasons.
|
||||||
|
"concat": interpolationFuncConcat(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +84,33 @@ func interpolationFuncJoin() ast.Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// interpolationFuncReplace implements the "replace" function that does
|
||||||
|
// string replacement.
|
||||||
|
func interpolationFuncReplace() ast.Function {
|
||||||
|
return ast.Function{
|
||||||
|
ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
|
||||||
|
ReturnType: ast.TypeString,
|
||||||
|
Callback: func(args []interface{}) (interface{}, error) {
|
||||||
|
s := args[0].(string)
|
||||||
|
search := args[1].(string)
|
||||||
|
replace := args[2].(string)
|
||||||
|
|
||||||
|
// We search/replace using a regexp if the string is surrounded
|
||||||
|
// in forward slashes.
|
||||||
|
if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
|
||||||
|
re, err := regexp.Compile(search[1 : len(search)-1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return re.ReplaceAllString(s, replace), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(s, search, replace, -1), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// interpolationFuncSplit implements the "split" function that allows
|
// interpolationFuncSplit implements the "split" function that allows
|
||||||
// strings to split into multi-variable values
|
// strings to split into multi-variable values
|
||||||
func interpolationFuncSplit() ast.Function {
|
func interpolationFuncSplit() ast.Function {
|
||||||
|
|
|
@ -107,6 +107,46 @@ func TestInterpolateFuncJoin(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInterpolateFuncReplace(t *testing.T) {
|
||||||
|
testFunction(t, testFunctionConfig{
|
||||||
|
Cases: []testFunctionCase{
|
||||||
|
// Regular search and replace
|
||||||
|
{
|
||||||
|
`${replace("hello", "hel", "bel")}`,
|
||||||
|
"bello",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search string doesn't match
|
||||||
|
{
|
||||||
|
`${replace("hello", "nope", "bel")}`,
|
||||||
|
"hello",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Regular expression
|
||||||
|
{
|
||||||
|
`${replace("hello", "/l/", "L")}`,
|
||||||
|
"heLLo",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`${replace("helo", "/(l)/", "$1$1")}`,
|
||||||
|
"hello",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bad regexp
|
||||||
|
{
|
||||||
|
`${replace("helo", "/(l/", "$1$1")}`,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInterpolateFuncSplit(t *testing.T) {
|
func TestInterpolateFuncSplit(t *testing.T) {
|
||||||
testFunction(t, testFunctionConfig{
|
testFunction(t, testFunctionConfig{
|
||||||
Cases: []testFunctionCase{
|
Cases: []testFunctionCase{
|
||||||
|
@ -225,7 +265,9 @@ func testFunction(t *testing.T, config testFunctionConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(out, tc.Result) {
|
if !reflect.DeepEqual(out, tc.Result) {
|
||||||
t.Fatalf("%d: bad: %#v", i, out)
|
t.Fatalf(
|
||||||
|
"%d: bad output for input: %s\n\nOutput: %#v",
|
||||||
|
i, tc.Input, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,13 @@ The supported built-in functions are:
|
||||||
* `concat(args...)` - Concatenates the values of multiple arguments into
|
* `concat(args...)` - Concatenates the values of multiple arguments into
|
||||||
a single string.
|
a single string.
|
||||||
|
|
||||||
|
* `element(list, index)` - Returns a single element from a list
|
||||||
|
at the given index. If the index is greater than the number of
|
||||||
|
elements, this function will wrap using a standard mod algorithm.
|
||||||
|
A list is only possible with splat variables from resources with
|
||||||
|
a count greater than one.
|
||||||
|
Example: `element(aws_subnet.foo.*.id, count.index)`
|
||||||
|
|
||||||
* `file(path)` - Reads the contents of a file into the string. Variables
|
* `file(path)` - Reads the contents of a file into the string. Variables
|
||||||
in this file are _not_ interpolated. The contents of the file are
|
in this file are _not_ interpolated. The contents of the file are
|
||||||
read as-is.
|
read as-is.
|
||||||
|
@ -77,18 +84,19 @@ The supported built-in functions are:
|
||||||
only possible with splat variables from resources with a count
|
only possible with splat variables from resources with a count
|
||||||
greater than one. Example: `join(",", aws_instance.foo.*.id)`
|
greater than one. Example: `join(",", aws_instance.foo.*.id)`
|
||||||
|
|
||||||
* `split(delim, string)` - Splits the string previously created by `join`
|
|
||||||
back into a list. This is useful for pushing lists through module
|
|
||||||
outputs since they currently only support string values.
|
|
||||||
Example: `split(",", module.amod.server_ids)`
|
|
||||||
|
|
||||||
* `lookup(map, key)` - Performs a dynamic lookup into a mapping
|
* `lookup(map, key)` - Performs a dynamic lookup into a mapping
|
||||||
variable. The `map` parameter should be another variable, such
|
variable. The `map` parameter should be another variable, such
|
||||||
as `var.amis`.
|
as `var.amis`.
|
||||||
|
|
||||||
* `element(list, index)` - Returns a single element from a list
|
* `replace(string, search, replace)` - Does a search and replace on the
|
||||||
at the given index. If the index is greater than the number of
|
given string. All instances of `search` are replaced with the value
|
||||||
elements, this function will wrap using a standard mod algorithm.
|
of `replace`. If `search` is wrapped in forward slashes, it is treated
|
||||||
A list is only possible with splat variables from resources with
|
as a regular expression. If using a regular expression, `replace`
|
||||||
a count greater than one.
|
can reference subcaptures in the regular expression by using `$n` where
|
||||||
Example: `element(aws_subnet.foo.*.id, count.index)`
|
`n` is the index or name of the subcapture. If using a regular expression,
|
||||||
|
the syntax conforms to the [re2 regular expression syntax](https://code.google.com/p/re2/wiki/Syntax).
|
||||||
|
|
||||||
|
* `split(delim, string)` - Splits the string previously created by `join`
|
||||||
|
back into a list. This is useful for pushing lists through module
|
||||||
|
outputs since they currently only support string values.
|
||||||
|
Example: `split(",", module.amod.server_ids)`
|
||||||
|
|
Loading…
Reference in New Issue