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.
This commit is contained in:
Paul Hinze 2015-11-13 11:07:02 -06:00
parent b6aed3fec3
commit 928f534cfc
5 changed files with 80 additions and 41 deletions

View File

@ -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),

View File

@ -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)
}

View File

@ -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 := ""

View File

@ -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,

View File

@ -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.