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:
parent
b6aed3fec3
commit
928f534cfc
|
@ -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),
|
||||
|
|
|
@ -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" {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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 := ""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue