diff --git a/terraform/context.go b/terraform/context.go index c7234bd13..a0c35772e 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -109,7 +109,7 @@ func (c *Context) Apply() (*State, error) { if err == nil && len(c.config.Outputs) > 0 { s.Outputs = make(map[string]string) for _, o := range c.config.Outputs { - if err = c.computeVars(s, o.RawConfig); err != nil { + if err = c.computeVars(o.RawConfig); err != nil { break } @@ -247,7 +247,7 @@ func (c *Context) Validate() ([]string, []error) { // computeVars takes the State and given RawConfig and processes all // the variables. This dynamically discovers the attributes instead of // using a static map[string]string that the genericWalkFn uses. -func (c *Context) computeVars(s *State, raw *config.RawConfig) error { +func (c *Context) computeVars(raw *config.RawConfig) error { // If there are on variables, then we're done if len(raw.Variables) == 0 { return nil @@ -258,22 +258,15 @@ func (c *Context) computeVars(s *State, raw *config.RawConfig) error { for n, rawV := range raw.Variables { switch v := rawV.(type) { case *config.ResourceVariable: - r, ok := s.Resources[v.ResourceId()] - if !ok { - return fmt.Errorf( - "Resource '%s' not found for variable '%s'", - v.ResourceId(), - v.FullKey()) + var attr string + var err error + if v.Multi { + attr, err = c.computeResourceMultiVariable(v) + } else { + attr, err = c.computeResourceVariable(v) } - - attr, ok := r.Attributes[v.Field] - if !ok { - return fmt.Errorf( - "Resource '%s' does not have attribute '%s' "+ - "for variable '%s'", - v.ResourceId(), - v.Field, - v.FullKey()) + if err != nil { + return err } vs[n] = attr @@ -286,6 +279,75 @@ func (c *Context) computeVars(s *State, raw *config.RawConfig) error { return raw.Interpolate(vs) } +func (c *Context) computeResourceVariable( + v *config.ResourceVariable) (string, error) { + r, ok := c.state.Resources[v.ResourceId()] + if !ok { + return "", fmt.Errorf( + "Resource '%s' not found for variable '%s'", + v.ResourceId(), + v.FullKey()) + } + + attr, ok := r.Attributes[v.Field] + if !ok { + return "", fmt.Errorf( + "Resource '%s' does not have attribute '%s' "+ + "for variable '%s'", + v.ResourceId(), + v.Field, + v.FullKey()) + } + + return attr, nil +} + +func (c *Context) computeResourceMultiVariable( + v *config.ResourceVariable) (string, error) { + // Get the resource from the configuration so we can know how + // many of the resource there is. + var cr *config.Resource + for _, r := range c.config.Resources { + if r.Id() == v.ResourceId() { + cr = r + break + } + } + if cr == nil { + return "", fmt.Errorf( + "Resource '%s' not found for variable '%s'", + v.ResourceId(), + v.FullKey()) + } + + var values []string + for i := 0; i < cr.Count; i++ { + id := fmt.Sprintf("%s.%d", v.ResourceId(), i) + r, ok := c.state.Resources[id] + if !ok { + continue + } + + attr, ok := r.Attributes[v.Field] + if !ok { + continue + } + + values = append(values, attr) + } + + if len(values) == 0 { + return "", fmt.Errorf( + "Resource '%s' does not have attribute '%s' "+ + "for variable '%s'", + v.ResourceId(), + v.Field, + v.FullKey()) + } + + return strings.Join(values, ","), nil +} + func (c *Context) graph() (*depgraph.Graph, error) { return Graph(&GraphOpts{ Config: c.config, @@ -701,17 +763,9 @@ func computeAggregateVars( if !ok { continue } - - idx := strings.Index(rv.Field, ".") - if idx == -1 { - // It isn't an aggregated var + if !rv.Multi { continue } - if rv.Field[:idx] != "*" { - // It isn't an aggregated var - continue - } - field := rv.Field[idx+1:] // Get the meta node so that we can determine the count key := fmt.Sprintf("%s.%s", rv.Type, rv.Name) @@ -731,7 +785,7 @@ func computeAggregateVars( rv.Type, rv.Name, i, - field) + rv.Field) if v, ok := vs[key]; ok { values = append(values, v) } diff --git a/terraform/context_test.go b/terraform/context_test.go index 4bd218de2..e0da66d3e 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -536,6 +536,34 @@ func TestContextApply_output(t *testing.T) { } } +func TestContextApply_outputMulti(t *testing.T) { + c := testConfig(t, "apply-output-multi") + p := testProvider("aws") + p.ApplyFn = testApplyFn + p.DiffFn = testDiffFn + ctx := testContext(t, &ContextOpts{ + Config: c, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + }) + + if _, err := ctx.Plan(nil); err != nil { + t.Fatalf("err: %s", err) + } + + state, err := ctx.Apply() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(state.String()) + expected := strings.TrimSpace(testTerraformApplyOutputMultiStr) + if actual != expected { + t.Fatalf("bad: \n%s", actual) + } +} + func TestContextApply_unknownAttribute(t *testing.T) { c := testConfig(t, "apply-unknown") p := testProvider("aws") diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 5ab41e305..ce2b58733 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -121,6 +121,29 @@ Outputs: foo_num = 2 ` +const testTerraformApplyOutputMultiStr = ` +aws_instance.bar.0: + ID = foo + foo = bar + type = aws_instance +aws_instance.bar.1: + ID = foo + foo = bar + type = aws_instance +aws_instance.bar.2: + ID = foo + foo = bar + type = aws_instance +aws_instance.foo: + ID = foo + num = 2 + type = aws_instance + +Outputs: + +foo_num = bar,bar,bar +` + const testTerraformApplyUnknownAttrStr = ` aws_instance.foo: ID = foo diff --git a/terraform/test-fixtures/apply-output-multi/main.tf b/terraform/test-fixtures/apply-output-multi/main.tf new file mode 100644 index 000000000..40003342e --- /dev/null +++ b/terraform/test-fixtures/apply-output-multi/main.tf @@ -0,0 +1,12 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "bar" + count = 3 +} + +output "foo_num" { + value = "${aws_instance.bar.*.foo}" +}