diff --git a/terraform/context.go b/terraform/context.go index 66e553738..c7234bd13 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -105,6 +105,18 @@ func (c *Context) Apply() (*State, error) { // Update our state, even if we have an error, for partial updates c.state = s + // If we have no errors, then calculate the outputs if we have any + 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 { + break + } + + s.Outputs[o.Name] = o.RawConfig.Config()["value"].(string) + } + } + return s, err } @@ -232,6 +244,48 @@ func (c *Context) Validate() ([]string, []error) { return warns, errs } +// 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 { + // If there are on variables, then we're done + if len(raw.Variables) == 0 { + return nil + } + + // Go through each variable and find it + vs := make(map[string]string) + 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()) + } + + 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()) + } + + vs[n] = attr + case *config.UserVariable: + vs[n] = c.variables[v.Name] + } + } + + // Interpolate the variables + return raw.Interpolate(vs) +} + func (c *Context) graph() (*depgraph.Graph, error) { return Graph(&GraphOpts{ Config: c.config, diff --git a/terraform/context_test.go b/terraform/context_test.go index c67faec6c..4bd218de2 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -508,6 +508,34 @@ func TestContextApply_hook(t *testing.T) { } } +func TestContextApply_output(t *testing.T) { + c := testConfig(t, "apply-output") + 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(testTerraformApplyOutputStr) + 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/state.go b/terraform/state.go index 104459155..ae50f562a 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -16,6 +16,7 @@ import ( // can use to keep track of what real world resources it is actually // managing. type State struct { + Outputs map[string]string Resources map[string]*ResourceState once sync.Once @@ -96,6 +97,21 @@ func (s *State) String() string { } } + if len(s.Outputs) > 0 { + buf.WriteString("\nOutputs:\n\n") + + ks := make([]string, 0, len(s.Outputs)) + for k, _ := range s.Outputs { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + v := s.Outputs[k] + buf.WriteString(fmt.Sprintf("%s = %s\n", k, v)) + } + } + return buf.String() } diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index dfb0f0e37..5ab41e305 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -106,6 +106,21 @@ aws_instance.foo: num = 2 ` +const testTerraformApplyOutputStr = ` +aws_instance.bar: + ID = foo + foo = bar + type = aws_instance +aws_instance.foo: + ID = foo + num = 2 + type = aws_instance + +Outputs: + +foo_num = 2 +` + const testTerraformApplyUnknownAttrStr = ` aws_instance.foo: ID = foo diff --git a/terraform/test-fixtures/apply-output/main.tf b/terraform/test-fixtures/apply-output/main.tf new file mode 100644 index 000000000..4d8b0c4f0 --- /dev/null +++ b/terraform/test-fixtures/apply-output/main.tf @@ -0,0 +1,11 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "bar" +} + +output "foo_num" { + value = "${aws_instance.foo.num}" +}