From 928f534cfc69259bdb4a7310e17ce0e1e0cbe725 Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Fri, 13 Nov 2015 11:07:02 -0600 Subject: [PATCH] template_file: source contents instead of path Building on the work of #3846, deprecate `filename` in favor of a `template` attribute that accepts file contents instead of a path. Required a bit of work in the interpolation code to prevent Terraform from assuming that template interpolations were resource variables that needed to be resolved. Leaving them as "Unknown Variables" prevents interpolation from happening early and lets the `template_file` resource do its thing. --- builtin/providers/template/resource.go | 33 ++++++++++------- builtin/providers/template/resource_test.go | 35 ++++++------------- config/interpolate.go | 21 +++++++++++ terraform/interpolate.go | 15 ++++++++ .../docs/providers/template/r/file.html.md | 17 ++++++--- 5 files changed, 80 insertions(+), 41 deletions(-) diff --git a/builtin/providers/template/resource.go b/builtin/providers/template/resource.go index 9019dcfc9..8022c064b 100644 --- a/builtin/providers/template/resource.go +++ b/builtin/providers/template/resource.go @@ -4,7 +4,6 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -12,8 +11,8 @@ import ( "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/lang" "github.com/hashicorp/terraform/config/lang/ast" + "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/schema" - "github.com/mitchellh/go-homedir" ) func resource() *schema.Resource { @@ -24,13 +23,23 @@ func resource() *schema.Resource { Read: Read, Schema: map[string]*schema.Schema{ + "template": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Contents of the template", + ForceNew: true, + ConflictsWith: []string{"filename"}, + }, "filename": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, Description: "file to read template from", ForceNew: true, // Make a "best effort" attempt to relativize the file path. StateFunc: func(v interface{}) string { + if v == nil || v.(string) == "" { + return "" + } pwd, err := os.Getwd() if err != nil { return v.(string) @@ -41,6 +50,8 @@ func resource() *schema.Resource { } return rel }, + Deprecated: "Use the 'template' attribute instead.", + ConflictsWith: []string{"template"}, }, "vars": &schema.Schema{ Type: schema.TypeMap, @@ -96,23 +107,21 @@ func Read(d *schema.ResourceData, meta interface{}) error { type templateRenderError error -var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook - func render(d *schema.ResourceData) (string, error) { + template := d.Get("template").(string) filename := d.Get("filename").(string) vars := d.Get("vars").(map[string]interface{}) - path, err := homedir.Expand(filename) + if template == "" && filename != "" { + template = filename + } + + contents, _, err := pathorcontents.Read(template) if err != nil { return "", err } - buf, err := readfile(path) - if err != nil { - return "", err - } - - rendered, err := execute(string(buf), vars) + rendered, err := execute(contents, vars) if err != nil { return "", templateRenderError( fmt.Errorf("failed to render %v: %v", filename, err), diff --git a/builtin/providers/template/resource_test.go b/builtin/providers/template/resource_test.go index 7f461325a..91882d9d3 100644 --- a/builtin/providers/template/resource_test.go +++ b/builtin/providers/template/resource_test.go @@ -26,15 +26,10 @@ func TestTemplateRendering(t *testing.T) { for _, tt := range cases { r.Test(t, r.TestCase{ - PreCheck: func() { - readfile = func(string) ([]byte, error) { - return []byte(tt.template), nil - } - }, Providers: testProviders, Steps: []r.TestStep{ r.TestStep{ - Config: testTemplateConfig(tt.vars), + Config: testTemplateConfig(tt.template, tt.vars), Check: func(s *terraform.State) error { got := s.RootModule().Outputs["rendered"] if tt.want != got { @@ -62,14 +57,7 @@ func TestTemplateVariableChange(t *testing.T) { 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), + Config: testTemplateConfig(step.template, step.vars), Check: func(i int, want string) r.TestCheckFunc { return func(s *terraform.State) error { got := s.RootModule().Outputs["rendered"] @@ -88,14 +76,13 @@ func TestTemplateVariableChange(t *testing.T) { }) } -func testTemplateConfig(vars string) string { - return ` -resource "template_file" "t0" { - filename = "mock" - vars = ` + vars + ` -} -output "rendered" { - value = "${template_file.t0.rendered}" -} - ` +func testTemplateConfig(template, vars string) string { + return fmt.Sprintf(` + resource "template_file" "t0" { + template = "%s" + vars = %s + } + output "rendered" { + value = "${template_file.t0.rendered}" + }`, template, vars) } diff --git a/config/interpolate.go b/config/interpolate.go index af0a84da4..1ccf4b0eb 100644 --- a/config/interpolate.go +++ b/config/interpolate.go @@ -76,6 +76,13 @@ type SelfVariable struct { key string } +// SimpleVariable is an unprefixed variable, which can show up when users have +// strings they are passing down to resources that use interpolation +// internally. The template_file resource is an example of this. +type SimpleVariable struct { + Key string +} + // A UserVariable is a variable that is referencing a user variable // that is inputted from outside the configuration. This looks like // "${var.foo}" @@ -97,6 +104,8 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) { return NewUserVariable(v) } else if strings.HasPrefix(v, "module.") { return NewModuleVariable(v) + } else if !strings.ContainsRune(v, '.') { + return NewSimpleVariable(v) } else { return NewResourceVariable(v) } @@ -227,6 +236,18 @@ func (v *SelfVariable) GoString() string { return fmt.Sprintf("*%#v", *v) } +func NewSimpleVariable(key string) (*SimpleVariable, error) { + return &SimpleVariable{key}, nil +} + +func (v *SimpleVariable) FullKey() string { + return v.Key +} + +func (v *SimpleVariable) GoString() string { + return fmt.Sprintf("*%#v", *v) +} + func NewUserVariable(key string) (*UserVariable, error) { name := key[len("var."):] elem := "" diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 31c366eab..0ee61901c 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -73,6 +73,8 @@ func (i *Interpolater) Values( err = i.valueResourceVar(scope, n, v, result) case *config.SelfVariable: err = i.valueSelfVar(scope, n, v, result) + case *config.SimpleVariable: + err = i.valueSimpleVar(scope, n, v, result) case *config.UserVariable: err = i.valueUserVar(scope, n, v, result) default: @@ -249,6 +251,19 @@ func (i *Interpolater) valueSelfVar( return i.valueResourceVar(scope, n, rv, result) } +func (i *Interpolater) valueSimpleVar( + scope *InterpolationScope, + n string, + v *config.SimpleVariable, + result map[string]ast.Variable) error { + // SimpleVars are never handled by Terraform's interpolator + result[n] = ast.Variable{ + Value: config.UnknownVariableValue, + Type: ast.TypeString, + } + return nil +} + func (i *Interpolater) valueUserVar( scope *InterpolationScope, n string, diff --git a/website/source/docs/providers/template/r/file.html.md b/website/source/docs/providers/template/r/file.html.md index b46e55a80..7c9e2c59e 100644 --- a/website/source/docs/providers/template/r/file.html.md +++ b/website/source/docs/providers/template/r/file.html.md @@ -14,7 +14,7 @@ Renders a template from a file. ``` resource "template_file" "init" { - filename = "${path.module}/init.tpl" + template = "${file(${path.module}/init.tpl)}" vars { consul_address = "${aws_instance.consul.private_ip}" @@ -27,17 +27,24 @@ resource "template_file" "init" { The following arguments are supported: -* `filename` - (Required) The filename for the template. Use [path - variables](/docs/configuration/interpolation.html#path-variables) to make - this path relative to different path roots. +* `template` - (Required) The contents of the template. These can be loaded + from a file on disk using the [`file()` interpolation + function](/docs/configuration/interpolation.html#file_path_). * `vars` - (Optional) Variables for interpolation within the template. +The following arguments are maintained for backwards compatibility and may be +removed in a future version: + +* `filename` - __Deprecated, please use `template` instead_. The filename for + the template. Use [path variables](/docs/configuration/interpolation.html#path-variables) to make + this path relative to different path roots. + ## Attributes Reference The following attributes are exported: -* `filename` - See Argument Reference above. +* `template` - See Argument Reference above. * `vars` - See Argument Reference above. * `rendered` - The final rendered template.