Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/stacker_yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ substitution file. An error will be printed that explains how to rewrite it:

error "A=B" was provided as a substitution and unsupported placeholder "${A}" was found. Replace "${A}" with "${{A}}" to use the substitution.

Shell variables whose names only start with a provided key, such as `$FOO_BAR`
when `FOO` is provided, are not placeholders for that key and are left alone.

Substitutions can also be specified in a yaml file given with the argument
`--substitute-file`, with any number of key: value pairs:

Expand Down
30 changes: 22 additions & 8 deletions pkg/types/stackerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,28 @@ func substitute(content string, substitutions []string) (string, error) {
nobracket := fmt.Sprintf("$%s", membs[0])
onebracket := fmt.Sprintf("${%s}", membs[0])
bad_content := content
for _, unsupp_placeholder := range []string{nobracket, onebracket} {
if strings.Contains(content, unsupp_placeholder) {
msg := fmt.Sprintf("%q was provided as a substitution "+
"and unsupported placeholder %q was found. "+
"Replace %q with \"${{%s}}\" to use the substitution.\n", subst,
unsupp_placeholder, unsupp_placeholder, membs[0])
unsupported_messages = append(unsupported_messages, msg)
}

// $FOO is only a placeholder for FOO if it isn't followed by
// another character of a variable name, e.g. $FOO_BAR is not.
nobracket_re, err := regexp.Compile(regexp.QuoteMeta(nobracket) + `([^A-Za-z0-9_]|$)`)
if err != nil {
return "", err
}

unsupp_placeholders := []string{}
if nobracket_re.MatchString(content) {
unsupp_placeholders = append(unsupp_placeholders, nobracket)
}
if strings.Contains(content, onebracket) {
unsupp_placeholders = append(unsupp_placeholders, onebracket)
}

for _, unsupp_placeholder := range unsupp_placeholders {
msg := fmt.Sprintf("%q was provided as a substitution "+
"and unsupported placeholder %q was found. "+
"Replace %q with \"${{%s}}\" to use the substitution.\n", subst,
unsupp_placeholder, unsupp_placeholder, membs[0])
unsupported_messages = append(unsupported_messages, msg)
}

// the ${FOO:bar} syntax never worked! but since we documented it
Expand Down
21 changes: 21 additions & 0 deletions pkg/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,27 @@ func TestSubstitute(t *testing.T) {
t.Fatalf("substitution with unsupported placeholders should have failed but didn't")
}

// variables which merely start with the name of a provided
// substitution, e.g. $VERSION_ID for VERSION, are not placeholders
s = "$VERSION_ID $VERSIONS ${VERSION_ID} ${VERSIONS:-1} ${{VERSION}}"
result, err = substitute(s, []string{"VERSION=1.0"})
if err != nil {
t.Fatalf("failed substitution: %s", err)
}

expected = "$VERSION_ID $VERSIONS ${VERSION_ID} ${VERSIONS:-1} 1.0"
if result != expected {
t.Fatalf("bad substitution result, expected '%s' got '%s'", expected, result)
}

// but $VERSION followed by a non-name character still is
for _, s := range []string{"$VERSION", "$VERSION/bin", "$VERSION-rc1", "\"$VERSION\""} {
_, err = substitute(s, []string{"VERSION=1.0"})
if err == nil {
t.Fatalf("substitution of %q with unsupported placeholder should have failed but didn't", s)
}
}

// a double bracket placeholder with no default value must be provided
s = "${{NOT_SET}}"
_, err = substitute(s, []string{})
Expand Down
Loading