Skip to content
This repository was archived by the owner on Mar 17, 2026. It is now read-only.
Merged
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
13 changes: 8 additions & 5 deletions engines/terraform/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package terraform

import (
"fmt"
"maps"
"slices"
"strings"

"github.com/aws/jsii-runtime-go"
Expand All @@ -11,18 +13,18 @@ import (
func SpecReferenceFromToken(token string) (*SpecReference, error) {
contents, ok := extractTokenContents(token)
if !ok {
return nil, fmt.Errorf("invalid reference format")
return nil, fmt.Errorf("invalid reference format for token: %s", token)
}

parts := strings.Split(contents, ".")
if len(parts) < 2 {
return nil, fmt.Errorf("invalid reference format")
return nil, fmt.Errorf("invalid reference format for token: %s", token)
}

// Validate that all path components are non-empty
for _, part := range parts[1:] {
if part == "" {
return nil, fmt.Errorf("invalid reference format")
return nil, fmt.Errorf("invalid reference format for token: %s", token)
}
}

Expand Down Expand Up @@ -87,14 +89,15 @@ func (td *TerraformDeployment) resolveToken(intentName string, specRef *SpecRefe

case "self":
if len(specRef.Path) == 0 {
return nil, fmt.Errorf("references 'self' without specifying a variable. All references must include a variable name e.g. ${self.my_var}")
return nil, fmt.Errorf("references 'self' without specifying a variable. All references must include a variable name e.g. self.my_var")
}

varName := specRef.Path[0]

availableVars := slices.Collect(maps.Keys(td.instancedTerraformVariables[intentName]))
tfVariable, ok := td.instancedTerraformVariables[intentName][varName]
if !ok {
return nil, fmt.Errorf("variable %s does not exist for provided blueprint", varName)
return nil, fmt.Errorf("variable %s does not exist for provided blueprint available variables are: %v", varName, availableVars)
}

if len(specRef.Path) > 1 {
Expand Down
73 changes: 51 additions & 22 deletions engines/terraform/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ func TestSpecReferenceFromToken(t *testing.T) {
}{
{
name: "valid infra reference",
token: "infra.resource.property",
expected: &SpecReference{
Source: "infra",
Path: []string{"resource", "property"},
},
expectError: false,
},
{
name: "valid infra reference with ${} wrapping",
token: "${infra.resource.property}",
expected: &SpecReference{
Source: "infra",
Expand All @@ -27,6 +36,15 @@ func TestSpecReferenceFromToken(t *testing.T) {
},
{
name: "valid self reference",
token: "self.my_var",
expected: &SpecReference{
Source: "self",
Path: []string{"my_var"},
},
expectError: false,
},
{
name: "valid self reference with ${} wrapping",
token: "${self.my_var}",
expected: &SpecReference{
Source: "self",
Expand All @@ -36,7 +54,7 @@ func TestSpecReferenceFromToken(t *testing.T) {
},
{
name: "valid self reference with nested path",
token: "${self.my_var.nested.value}",
token: "self.my_var.nested.value",
expected: &SpecReference{
Source: "self",
Path: []string{"my_var", "nested", "value"},
Expand All @@ -45,7 +63,7 @@ func TestSpecReferenceFromToken(t *testing.T) {
},
{
name: "valid var reference",
token: "${var.platform_var}",
token: "var.platform_var",
expected: &SpecReference{
Source: "var",
Path: []string{"platform_var"},
Expand All @@ -54,51 +72,53 @@ func TestSpecReferenceFromToken(t *testing.T) {
},
{
name: "valid var reference with nested path",
token: "${var.platform_var.nested.value}",
token: "var.platform_var.nested.value",
expected: &SpecReference{
Source: "var",
Path: []string{"platform_var", "nested", "value"},
},
expectError: false,
},
{
name: "invalid token format - no closing brace",
token: "${infra.resource",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
name: "valid token with unclosed brace extracts token",
token: "${infra.resource",
expected: &SpecReference{
Source: "infra",
Path: []string{"resource"},
},
expectError: false,
},
{
name: "invalid token format - not enough parts",
token: "${infra}",
token: "infra",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
},
{
name: "invalid token format - only source",
token: "${self}",
token: "self",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
},
{
name: "invalid token format - only var source",
token: "${var}",
token: "var",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
},
{
name: "invalid token format - self with dot but no path",
token: "${self.}",
token: "self.",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
},
{
name: "invalid token format - var with dot but no path",
token: "${var.}",
token: "var.",
expected: nil,
expectError: true,
errorMsg: "invalid reference format",
Expand Down Expand Up @@ -313,14 +333,14 @@ func TestResolveTokenValue(t *testing.T) {
},
{
name: "single token only",
input: "${self.my_var}",
input: "self.my_var",
setupVar: true,
expectToken: true,
expectError: false,
},
{
name: "string with one token in middle",
input: "prefix-${self.my_var}-suffix",
name: "single token only with ${} wrapping",
input: "${self.my_var}",
setupVar: true,
expectToken: true,
expectError: false,
Expand Down Expand Up @@ -385,10 +405,10 @@ func TestResolveStringInterpolation(t *testing.T) {
}{
{
name: "string with two tokens",
input: "prefix-${self.var1}-middle-${self.var2}-suffix",
input: "prefix-${self.var1-middle}-${self.var2-suffix}",
setupVars: map[string]string{
"var1": "value1",
"var2": "value2",
"var1-middle": "value1",
"var2-suffix": "value2",
},
expectError: false,
},
Expand All @@ -403,20 +423,29 @@ func TestResolveStringInterpolation(t *testing.T) {
},
{
name: "string with token at start",
input: "${self.var1}-suffix",
input: "self.var1-suffix",
setupVars: map[string]string{
"var1": "value1",
"var1-suffix": "value1",
},
expectError: false,
},
{
name: "string with token at end",
input: "prefix-${self.var1}",
input: "prefix-self.var1",
setupVars: map[string]string{
"var1": "value1",
},
expectError: false,
},
{
name: "string with ${} wrapped tokens",
input: "prefix-${self.var1}-middle-${self.var2}-suffix",
setupVars: map[string]string{
"var1": "value1",
"var2": "value2",
},
expectError: false,
},
}

for _, tt := range tests {
Expand Down
6 changes: 3 additions & 3 deletions engines/terraform/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type TokenMatch struct {
End int
}

// extractTokenContents extracts the contents between ${} from a token string
// extractTokenContents extracts the contents from a token string (infra/var/self prefix)
func extractTokenContents(token string) (string, bool) {
if matches := tokenPattern.FindStringSubmatch(token); len(matches) == 2 {
return matches[1], true
Expand Down Expand Up @@ -45,5 +45,5 @@ func isOnlyToken(input string) bool {
return strings.TrimSpace(input) == strings.TrimSpace(allTokensPattern.FindString(input))
}

var tokenPattern = regexp.MustCompile(`^\${([^\s}]+)}$`)
var allTokensPattern = regexp.MustCompile(`\$\{([^\s}]+)\}`)
var tokenPattern = regexp.MustCompile(`((?:infra|var|self)\.[\w.\-]+)`)
var allTokensPattern = regexp.MustCompile(`((?:infra|var|self)\.[\w.\-]+)`)