Merge pull request #3909 from hashicorp/phinze/template-file-contents

template_file: source contents instead of path
This commit is contained in:
Paul Hinze 2015-11-16 14:50:45 -06:00
commit 993ec0a320
5 changed files with 80 additions and 41 deletions

View File

@ -4,7 +4,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
@ -12,8 +11,8 @@ import (
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/lang" "github.com/hashicorp/terraform/config/lang"
"github.com/hashicorp/terraform/config/lang/ast" "github.com/hashicorp/terraform/config/lang/ast"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/go-homedir"
) )
func resource() *schema.Resource { func resource() *schema.Resource {
@ -24,13 +23,23 @@ func resource() *schema.Resource {
Read: Read, Read: Read,
Schema: map[string]*schema.Schema{ 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{ "filename": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
Description: "file to read template from", Description: "file to read template from",
ForceNew: true, ForceNew: true,
// Make a "best effort" attempt to relativize the file path. // Make a "best effort" attempt to relativize the file path.
StateFunc: func(v interface{}) string { StateFunc: func(v interface{}) string {
if v == nil || v.(string) == "" {
return ""
}
pwd, err := os.Getwd() pwd, err := os.Getwd()
if err != nil { if err != nil {
return v.(string) return v.(string)
@ -41,6 +50,8 @@ func resource() *schema.Resource {
} }
return rel return rel
}, },
Deprecated: "Use the 'template' attribute instead.",
ConflictsWith: []string{"template"},
}, },
"vars": &schema.Schema{ "vars": &schema.Schema{
Type: schema.TypeMap, Type: schema.TypeMap,
@ -96,23 +107,21 @@ func Read(d *schema.ResourceData, meta interface{}) error {
type templateRenderError error type templateRenderError error
var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
func render(d *schema.ResourceData) (string, error) { func render(d *schema.ResourceData) (string, error) {
template := d.Get("template").(string)
filename := d.Get("filename").(string) filename := d.Get("filename").(string)
vars := d.Get("vars").(map[string]interface{}) 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 { if err != nil {
return "", err return "", err
} }
buf, err := readfile(path) rendered, err := execute(contents, vars)
if err != nil {
return "", err
}
rendered, err := execute(string(buf), vars)
if err != nil { if err != nil {
return "", templateRenderError( return "", templateRenderError(
fmt.Errorf("failed to render %v: %v", filename, err), fmt.Errorf("failed to render %v: %v", filename, err),

View File

@ -26,15 +26,10 @@ func TestTemplateRendering(t *testing.T) {
for _, tt := range cases { for _, tt := range cases {
r.Test(t, r.TestCase{ r.Test(t, r.TestCase{
PreCheck: func() {
readfile = func(string) ([]byte, error) {
return []byte(tt.template), nil
}
},
Providers: testProviders, Providers: testProviders,
Steps: []r.TestStep{ Steps: []r.TestStep{
r.TestStep{ r.TestStep{
Config: testTemplateConfig(tt.vars), Config: testTemplateConfig(tt.template, tt.vars),
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 {
@ -62,14 +57,7 @@ func TestTemplateVariableChange(t *testing.T) {
var testSteps []r.TestStep var testSteps []r.TestStep
for i, step := range steps { for i, step := range steps {
testSteps = append(testSteps, r.TestStep{ testSteps = append(testSteps, r.TestStep{
PreConfig: func(template string) func() { Config: testTemplateConfig(step.template, step.vars),
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 { Check: func(i int, want string) r.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
got := s.RootModule().Outputs["rendered"] got := s.RootModule().Outputs["rendered"]
@ -88,14 +76,13 @@ func TestTemplateVariableChange(t *testing.T) {
}) })
} }
func testTemplateConfig(vars string) string { func testTemplateConfig(template, vars string) string {
return ` return fmt.Sprintf(`
resource "template_file" "t0" { resource "template_file" "t0" {
filename = "mock" template = "%s"
vars = ` + vars + ` vars = %s
} }
output "rendered" { output "rendered" {
value = "${template_file.t0.rendered}" value = "${template_file.t0.rendered}"
} }`, template, vars)
`
} }

View File

@ -76,6 +76,13 @@ type SelfVariable struct {
key string 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 // A UserVariable is a variable that is referencing a user variable
// that is inputted from outside the configuration. This looks like // that is inputted from outside the configuration. This looks like
// "${var.foo}" // "${var.foo}"
@ -97,6 +104,8 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
return NewUserVariable(v) return NewUserVariable(v)
} else if strings.HasPrefix(v, "module.") { } else if strings.HasPrefix(v, "module.") {
return NewModuleVariable(v) return NewModuleVariable(v)
} else if !strings.ContainsRune(v, '.') {
return NewSimpleVariable(v)
} else { } else {
return NewResourceVariable(v) return NewResourceVariable(v)
} }
@ -227,6 +236,18 @@ func (v *SelfVariable) GoString() string {
return fmt.Sprintf("*%#v", *v) 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) { func NewUserVariable(key string) (*UserVariable, error) {
name := key[len("var."):] name := key[len("var."):]
elem := "" elem := ""

View File

@ -73,6 +73,8 @@ func (i *Interpolater) Values(
err = i.valueResourceVar(scope, n, v, result) err = i.valueResourceVar(scope, n, v, result)
case *config.SelfVariable: case *config.SelfVariable:
err = i.valueSelfVar(scope, n, v, result) err = i.valueSelfVar(scope, n, v, result)
case *config.SimpleVariable:
err = i.valueSimpleVar(scope, n, v, result)
case *config.UserVariable: case *config.UserVariable:
err = i.valueUserVar(scope, n, v, result) err = i.valueUserVar(scope, n, v, result)
default: default:
@ -249,6 +251,19 @@ func (i *Interpolater) valueSelfVar(
return i.valueResourceVar(scope, n, rv, result) 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( func (i *Interpolater) valueUserVar(
scope *InterpolationScope, scope *InterpolationScope,
n string, n string,

View File

@ -14,7 +14,7 @@ Renders a template from a file.
``` ```
resource "template_file" "init" { resource "template_file" "init" {
filename = "${path.module}/init.tpl" template = "${file(${path.module}/init.tpl)}"
vars { vars {
consul_address = "${aws_instance.consul.private_ip}" consul_address = "${aws_instance.consul.private_ip}"
@ -27,17 +27,24 @@ resource "template_file" "init" {
The following arguments are supported: The following arguments are supported:
* `filename` - (Required) The filename for the template. Use [path * `template` - (Required) The contents of the template. These can be loaded
variables](/docs/configuration/interpolation.html#path-variables) to make from a file on disk using the [`file()` interpolation
this path relative to different path roots. function](/docs/configuration/interpolation.html#file_path_).
* `vars` - (Optional) Variables for interpolation within the template. * `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 ## Attributes Reference
The following attributes are exported: The following attributes are exported:
* `filename` - See Argument Reference above. * `template` - See Argument Reference above.
* `vars` - See Argument Reference above. * `vars` - See Argument Reference above.
* `rendered` - The final rendered template. * `rendered` - The final rendered template.