Skip to content

Commit

Permalink
provider/template: don't error when rendering fails in Exists
Browse files Browse the repository at this point in the history
The Exists function can run in a context where the contents of the
template have changed, but it uses the old set of variables from the
state. This means that when the set of variables changes, rendering will
fail in Exists. This was returning an error, but really it just needs to
be treated as a scenario where the template needs re-rendering.

fixes #2344 and possibly a few other template issues floating around
  • Loading branch information
phinze committed Jun 17, 2015
1 parent f0d8682 commit 385b17d
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 11 deletions.
15 changes: 13 additions & 2 deletions builtin/providers/template/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"

Expand Down Expand Up @@ -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
}
Expand All @@ -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) {
Expand All @@ -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
Expand Down
62 changes: 53 additions & 9 deletions builtin/providers/template/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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}"
}
`
}
8 changes: 8 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 385b17d

Please sign in to comment.