Skip to content

Commit

Permalink
template_file: source contents instead of path
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
phinze committed Nov 13, 2015
1 parent b6aed3f commit 09fc92c
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 39 deletions.
27 changes: 17 additions & 10 deletions builtin/providers/template/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"

"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 {
Expand All @@ -24,13 +23,22 @@ 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,
},
"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)
Expand All @@ -41,6 +49,7 @@ func resource() *schema.Resource {
}
return rel
},
Deprecated: "Use the 'template' attribute instead.",
},
"vars": &schema.Schema{
Type: schema.TypeMap,
Expand Down Expand Up @@ -96,23 +105,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 err != nil {
return "", err
if template == "" && filename != "" {
template = filename
}

buf, err := readfile(path)
contents, _, err := pathorcontents.Read(template)
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),
Expand Down
35 changes: 11 additions & 24 deletions builtin/providers/template/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"]
Expand All @@ -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)
}
21 changes: 21 additions & 0 deletions config/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 := ""
Expand Down
15 changes: 15 additions & 0 deletions terraform/interpolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
17 changes: 12 additions & 5 deletions website/source/docs/providers/template/r/file.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -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.

Expand Down

0 comments on commit 09fc92c

Please sign in to comment.