Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interpolate: Interpolate computed list attributes #2157

Merged
merged 2 commits into from
Aug 27, 2015
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
29 changes: 29 additions & 0 deletions config/string_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"reflect"
"testing"
)

func TestStringList_slice(t *testing.T) {
expected := []string{"apple", "banana", "pear"}
l := NewStringList(expected)
actual := l.Slice()

if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Expected %q, got %q", expected, actual)
}
}

func TestStringList_element(t *testing.T) {
list := []string{"apple", "banana", "pear"}
l := NewStringList(list)
actual := l.Element(1)

expected := "banana"

if actual != expected {
t.Fatalf("Expected 2nd element from %q to be %q, got %q",
list, expected, actual)
}
}
47 changes: 45 additions & 2 deletions terraform/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package terraform

import (
"fmt"
"log"
"os"
"regexp"
"sort"
"strings"
"sync"

Expand Down Expand Up @@ -327,6 +330,11 @@ func (i *Interpolater) computeResourceVariable(
return attr, nil
}

// computed list attribute
if _, ok := r.Primary.Attributes[v.Field+".#"]; ok {
return i.interpolateListAttribute(v.Field, r.Primary.Attributes)
}

// At apply time, we can't do the "maybe has it" check below
// that we need for plans since parent elements might be computed.
// Therefore, it is an error and we're missing the key.
Expand Down Expand Up @@ -410,8 +418,8 @@ func (i *Interpolater) computeResourceMultiVariable(
}

var values []string
for i := 0; i < count; i++ {
id := fmt.Sprintf("%s.%d", v.ResourceId(), i)
for j := 0; j < count; j++ {
id := fmt.Sprintf("%s.%d", v.ResourceId(), j)

// If we're dealing with only a single resource, then the
// ID doesn't have a trailing index.
Expand All @@ -430,6 +438,21 @@ func (i *Interpolater) computeResourceMultiVariable(

attr, ok := r.Primary.Attributes[v.Field]
if !ok {
// computed list attribute
_, ok := r.Primary.Attributes[v.Field+".#"]
if !ok {
continue
}
attr, err = i.interpolateListAttribute(v.Field, r.Primary.Attributes)
if err != nil {
return "", err
}
}

if config.IsStringList(attr) {
for _, s := range config.StringList(attr).Slice() {
values = append(values, s)
}
continue
}

Expand Down Expand Up @@ -461,6 +484,26 @@ func (i *Interpolater) computeResourceMultiVariable(
return config.NewStringList(values).String(), nil
}

func (i *Interpolater) interpolateListAttribute(
resourceID string,
attributes map[string]string) (string, error) {

attr := attributes[resourceID+".#"]
log.Printf("[DEBUG] Interpolating computed list attribute %s (%s)",
resourceID, attr)

var members []string
numberedListMember := regexp.MustCompile("^" + resourceID + "\\.[0-9]+$")
for id, value := range attributes {
if numberedListMember.MatchString(id) {
members = append(members, value)
}
}

sort.Strings(members)
return config.NewStringList(members).String(), nil
}

func (i *Interpolater) resourceVariableInfo(
scope *InterpolationScope,
v *config.ResourceVariable) (*ModuleState, *config.Resource, error) {
Expand Down
204 changes: 203 additions & 1 deletion terraform/interpolate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,208 @@ func TestInterpolater_resourceVariableMulti(t *testing.T) {
})
}

func TestInterpolator_resourceMultiAttributes(t *testing.T) {
lock := new(sync.RWMutex)
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_route53_zone.yada": &ResourceState{
Type: "aws_route53_zone",
Dependencies: []string{},
Primary: &InstanceState{
ID: "AAABBBCCCDDDEEE",
Attributes: map[string]string{
"name_servers.#": "4",
"name_servers.0": "ns-1334.awsdns-38.org",
"name_servers.1": "ns-1680.awsdns-18.co.uk",
"name_servers.2": "ns-498.awsdns-62.com",
"name_servers.3": "ns-601.awsdns-11.net",
"listeners.#": "1",
"listeners.0": "red",
"tags.#": "1",
"tags.Name": "reindeer",
"nothing.#": "0",
},
},
},
},
},
},
}

i := &Interpolater{
Module: testModule(t, "interpolate-multi-vars"),
StateLock: lock,
State: state,
}

scope := &InterpolationScope{
Path: rootModulePath,
}

name_servers := []string{
"ns-1334.awsdns-38.org",
"ns-1680.awsdns-18.co.uk",
"ns-498.awsdns-62.com",
"ns-601.awsdns-11.net",
}
expectedNameServers := config.NewStringList(name_servers).String()

// More than 1 element
testInterpolate(t, i, scope, "aws_route53_zone.yada.name_servers", ast.Variable{
Value: expectedNameServers,
Type: ast.TypeString,
})

// Exactly 1 element
testInterpolate(t, i, scope, "aws_route53_zone.yada.listeners", ast.Variable{
Value: config.NewStringList([]string{"red"}).String(),
Type: ast.TypeString,
})

// Zero elements
testInterpolate(t, i, scope, "aws_route53_zone.yada.nothing", ast.Variable{
Value: config.NewStringList([]string{}).String(),
Type: ast.TypeString,
})

// Maps still need to work
testInterpolate(t, i, scope, "aws_route53_zone.yada.tags.Name", ast.Variable{
Value: "reindeer",
Type: ast.TypeString,
})
}

func TestInterpolator_resourceMultiAttributesWithResourceCount(t *testing.T) {
i := getInterpolaterFixture(t)
scope := &InterpolationScope{
Path: rootModulePath,
}

name_servers := []string{
"ns-1334.awsdns-38.org",
"ns-1680.awsdns-18.co.uk",
"ns-498.awsdns-62.com",
"ns-601.awsdns-11.net",
"ns-000.awsdns-38.org",
"ns-444.awsdns-18.co.uk",
"ns-666.awsdns-11.net",
"ns-999.awsdns-62.com",
}

// More than 1 element
expectedNameServers := config.NewStringList(name_servers[0:4]).String()
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.name_servers", ast.Variable{
Value: expectedNameServers,
Type: ast.TypeString,
})
// More than 1 element in both
expectedNameServers = config.NewStringList(name_servers).String()
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.name_servers", ast.Variable{
Value: expectedNameServers,
Type: ast.TypeString,
})

// Exactly 1 element
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.listeners", ast.Variable{
Value: config.NewStringList([]string{"red"}).String(),
Type: ast.TypeString,
})
// Exactly 1 element in both
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.listeners", ast.Variable{
Value: config.NewStringList([]string{"red", "blue"}).String(),
Type: ast.TypeString,
})

// Zero elements
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.nothing", ast.Variable{
Value: config.NewStringList([]string{}).String(),
Type: ast.TypeString,
})
// Zero + zero elements
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.nothing", ast.Variable{
Value: config.NewStringList([]string{"", ""}).String(),
Type: ast.TypeString,
})
// Zero + 1 element
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.special", ast.Variable{
Value: config.NewStringList([]string{"extra"}).String(),
Type: ast.TypeString,
})

// Maps still need to work
testInterpolate(t, i, scope, "aws_route53_zone.terra.0.tags.Name", ast.Variable{
Value: "reindeer",
Type: ast.TypeString,
})
// Maps still need to work in both
testInterpolate(t, i, scope, "aws_route53_zone.terra.*.tags.Name", ast.Variable{
Value: config.NewStringList([]string{"reindeer", "white-hart"}).String(),
Type: ast.TypeString,
})
}

func getInterpolaterFixture(t *testing.T) *Interpolater {
lock := new(sync.RWMutex)
state := &State{
Modules: []*ModuleState{
&ModuleState{
Path: rootModulePath,
Resources: map[string]*ResourceState{
"aws_route53_zone.terra.0": &ResourceState{
Type: "aws_route53_zone",
Dependencies: []string{},
Primary: &InstanceState{
ID: "AAABBBCCCDDDEEE",
Attributes: map[string]string{
"name_servers.#": "4",
"name_servers.0": "ns-1334.awsdns-38.org",
"name_servers.1": "ns-1680.awsdns-18.co.uk",
"name_servers.2": "ns-498.awsdns-62.com",
"name_servers.3": "ns-601.awsdns-11.net",
"listeners.#": "1",
"listeners.0": "red",
"tags.#": "1",
"tags.Name": "reindeer",
"nothing.#": "0",
},
},
},
"aws_route53_zone.terra.1": &ResourceState{
Type: "aws_route53_zone",
Dependencies: []string{},
Primary: &InstanceState{
ID: "EEEFFFGGGHHHIII",
Attributes: map[string]string{
"name_servers.#": "4",
"name_servers.0": "ns-000.awsdns-38.org",
"name_servers.1": "ns-444.awsdns-18.co.uk",
"name_servers.2": "ns-999.awsdns-62.com",
"name_servers.3": "ns-666.awsdns-11.net",
"listeners.#": "1",
"listeners.0": "blue",
"special.#": "1",
"special.0": "extra",
"tags.#": "1",
"tags.Name": "white-hart",
"nothing.#": "0",
},
},
},
},
},
},
}

return &Interpolater{
Module: testModule(t, "interpolate-multi-vars"),
StateLock: lock,
State: state,
}
}

func testInterpolate(
t *testing.T, i *Interpolater,
scope *InterpolationScope,
Expand All @@ -230,6 +432,6 @@ func testInterpolate(
"foo": expectedVar,
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
t.Fatalf("%q: actual: %#v\nexpected: %#v", n, actual, expected)
}
}
7 changes: 7 additions & 0 deletions terraform/test-fixtures/interpolate-multi-vars/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "aws_route53_zone" "yada" {

}

resource "aws_route53_zone" "terra" {
count = 2
}