diff --git a/builtin/providers/template/resource.go b/builtin/providers/template/resource.go index 8eb3ce9eb32e..9019dcfc93f4 100644 --- a/builtin/providers/template/resource.go +++ b/builtin/providers/template/resource.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "io/ioutil" + "log" "os" "path/filepath" @@ -75,7 +76,13 @@ func Delete(d *schema.ResourceData, meta interface{}) error { func Exists(d *schema.ResourceData, meta interface{}) (bool, error) { rendered, err := render(d) if err != nil { - return false, err + if _, ok := err.(templateRenderError); ok { + log.Printf("[DEBUG] Got error while rendering in Exists: %s", err) + log.Printf("[DEBUG] Returning false so the template re-renders using latest variables from config.") + return false, nil + } else { + return false, err + } } return hash(rendered) == d.Id(), nil } @@ -87,6 +94,8 @@ func Read(d *schema.ResourceData, meta interface{}) error { return nil } +type templateRenderError error + var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook func render(d *schema.ResourceData) (string, error) { @@ -105,7 +114,9 @@ func render(d *schema.ResourceData) (string, error) { rendered, err := execute(string(buf), vars) if err != nil { - return "", fmt.Errorf("failed to render %v: %v", filename, err) + return "", templateRenderError( + fmt.Errorf("failed to render %v: %v", filename, err), + ) } return rendered, nil diff --git a/builtin/providers/template/resource_test.go b/builtin/providers/template/resource_test.go index 13b6cf0522da..7f461325a2da 100644 --- a/builtin/providers/template/resource_test.go +++ b/builtin/providers/template/resource_test.go @@ -34,15 +34,7 @@ func TestTemplateRendering(t *testing.T) { Providers: testProviders, Steps: []r.TestStep{ r.TestStep{ - Config: ` -resource "template_file" "t0" { - filename = "mock" - vars = ` + tt.vars + ` -} -output "rendered" { - value = "${template_file.t0.rendered}" -} -`, + Config: testTemplateConfig(tt.vars), Check: func(s *terraform.State) error { got := s.RootModule().Outputs["rendered"] if tt.want != got { @@ -55,3 +47,55 @@ output "rendered" { }) } } + +// https://github.com/hashicorp/terraform/issues/2344 +func TestTemplateVariableChange(t *testing.T) { + steps := []struct { + vars string + template string + want string + }{ + {`{a="foo"}`, `${a}`, `foo`}, + {`{b="bar"}`, `${b}`, `bar`}, + } + + var testSteps []r.TestStep + for i, step := range steps { + testSteps = append(testSteps, r.TestStep{ + PreConfig: func(template string) func() { + return func() { + readfile = func(string) ([]byte, error) { + return []byte(template), nil + } + } + }(step.template), + Config: testTemplateConfig(step.vars), + Check: func(i int, want string) r.TestCheckFunc { + return func(s *terraform.State) error { + got := s.RootModule().Outputs["rendered"] + if want != got { + return fmt.Errorf("[%d] got:\n%q\nwant:\n%q\n", i, got, want) + } + return nil + } + }(i, step.want), + }) + } + + r.Test(t, r.TestCase{ + Providers: testProviders, + Steps: testSteps, + }) +} + +func testTemplateConfig(vars string) string { + return ` +resource "template_file" "t0" { + filename = "mock" + vars = ` + vars + ` +} +output "rendered" { + value = "${template_file.t0.rendered}" +} + ` +} diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 6832146b2243..3835e8639cb9 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -60,6 +60,10 @@ type TestCase struct { // potentially complex update logic. In general, simply create/destroy // tests will only need one step. type TestStep struct { + // PreConfig is called before the Config is applied to perform any per-step + // setup that needs to happen + PreConfig func() + // Config a string of the configuration to give to Terraform. Config string @@ -160,6 +164,10 @@ func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { + if step.PreConfig != nil { + step.PreConfig() + } + cfgPath, err := ioutil.TempDir("", "tf-test") if err != nil { return state, fmt.Errorf(