From 91d750d2df5b8cc2a90c99d88ece45bc8e8d2497 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Sun, 5 Jul 2015 16:26:01 +0200 Subject: [PATCH] interpolate: Expand computed TypeList attributes properly --- terraform/interpolate.go | 47 +++- terraform/interpolate_test.go | 204 +++++++++++++++++- .../interpolate-multi-vars/main.tf | 7 + 3 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 terraform/test-fixtures/interpolate-multi-vars/main.tf diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 6d103cd80..15b81cdbd 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -2,7 +2,10 @@ package terraform import ( "fmt" + "log" "os" + "regexp" + "sort" "strings" "sync" @@ -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. @@ -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. @@ -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 } @@ -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) { diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index af06e429c..bbbb1024a 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -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, @@ -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) } } diff --git a/terraform/test-fixtures/interpolate-multi-vars/main.tf b/terraform/test-fixtures/interpolate-multi-vars/main.tf new file mode 100644 index 000000000..b24d02f98 --- /dev/null +++ b/terraform/test-fixtures/interpolate-multi-vars/main.tf @@ -0,0 +1,7 @@ +resource "aws_route53_zone" "yada" { + +} + +resource "aws_route53_zone" "terra" { + count = 2 +}