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.