provider/template: don't error when rendering fails in Exists
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
This commit is contained in:
parent
f0d8682df6
commit
385b17d679
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
@ -75,8 +76,14 @@ func Delete(d *schema.ResourceData, meta interface{}) error {
|
||||||
func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
||||||
rendered, err := render(d)
|
rendered, err := render(d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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 false, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return hash(rendered) == d.Id(), nil
|
return hash(rendered) == d.Id(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +94,8 @@ func Read(d *schema.ResourceData, meta interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type templateRenderError error
|
||||||
|
|
||||||
var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
|
var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
|
||||||
|
|
||||||
func render(d *schema.ResourceData) (string, error) {
|
func render(d *schema.ResourceData) (string, error) {
|
||||||
|
@ -105,7 +114,9 @@ func render(d *schema.ResourceData) (string, error) {
|
||||||
|
|
||||||
rendered, err := execute(string(buf), vars)
|
rendered, err := execute(string(buf), vars)
|
||||||
if err != nil {
|
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
|
return rendered, nil
|
||||||
|
|
|
@ -34,15 +34,7 @@ func TestTemplateRendering(t *testing.T) {
|
||||||
Providers: testProviders,
|
Providers: testProviders,
|
||||||
Steps: []r.TestStep{
|
Steps: []r.TestStep{
|
||||||
r.TestStep{
|
r.TestStep{
|
||||||
Config: `
|
Config: testTemplateConfig(tt.vars),
|
||||||
resource "template_file" "t0" {
|
|
||||||
filename = "mock"
|
|
||||||
vars = ` + tt.vars + `
|
|
||||||
}
|
|
||||||
output "rendered" {
|
|
||||||
value = "${template_file.t0.rendered}"
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
got := s.RootModule().Outputs["rendered"]
|
got := s.RootModule().Outputs["rendered"]
|
||||||
if tt.want != got {
|
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}"
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,10 @@ type TestCase struct {
|
||||||
// potentially complex update logic. In general, simply create/destroy
|
// potentially complex update logic. In general, simply create/destroy
|
||||||
// tests will only need one step.
|
// tests will only need one step.
|
||||||
type TestStep struct {
|
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 a string of the configuration to give to Terraform.
|
||||||
Config string
|
Config string
|
||||||
|
|
||||||
|
@ -160,6 +164,10 @@ func testStep(
|
||||||
opts terraform.ContextOpts,
|
opts terraform.ContextOpts,
|
||||||
state *terraform.State,
|
state *terraform.State,
|
||||||
step TestStep) (*terraform.State, error) {
|
step TestStep) (*terraform.State, error) {
|
||||||
|
if step.PreConfig != nil {
|
||||||
|
step.PreConfig()
|
||||||
|
}
|
||||||
|
|
||||||
cfgPath, err := ioutil.TempDir("", "tf-test")
|
cfgPath, err := ioutil.TempDir("", "tf-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return state, fmt.Errorf(
|
return state, fmt.Errorf(
|
||||||
|
|
Loading…
Reference in New Issue