2015-05-02 01:59:16 +02:00
|
|
|
package template
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
|
|
"github.com/hashicorp/terraform/config/lang"
|
|
|
|
"github.com/hashicorp/terraform/config/lang/ast"
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2015-05-04 19:26:17 +02:00
|
|
|
"github.com/mitchellh/go-homedir"
|
2015-05-02 01:59:16 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func resource() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: Create,
|
|
|
|
Delete: Delete,
|
|
|
|
Exists: Exists,
|
2015-05-09 00:46:37 +02:00
|
|
|
Read: Read,
|
2015-05-02 01:59:16 +02:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"filename": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
Description: "file to read template from",
|
2015-05-09 00:46:37 +02:00
|
|
|
ForceNew: true,
|
2015-05-02 01:59:16 +02:00
|
|
|
},
|
|
|
|
"vars": &schema.Schema{
|
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Default: make(map[string]interface{}),
|
|
|
|
Description: "variables to substitute",
|
2015-05-09 00:46:37 +02:00
|
|
|
ForceNew: true,
|
2015-05-02 01:59:16 +02:00
|
|
|
},
|
|
|
|
"rendered": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
Description: "rendered template",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-09 00:46:37 +02:00
|
|
|
func Create(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
rendered, err := render(d)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Set("rendered", rendered)
|
|
|
|
d.SetId(hash(rendered))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-05-02 01:59:16 +02:00
|
|
|
func Delete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
d.SetId("")
|
|
|
|
return nil
|
|
|
|
}
|
2015-05-09 00:46:37 +02:00
|
|
|
|
2015-05-02 01:59:16 +02:00
|
|
|
func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
2015-05-09 00:46:37 +02:00
|
|
|
rendered, err := render(d)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return hash(rendered) == d.Id(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Read(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
// Logic is handled in Exists, which only returns true if the rendered
|
|
|
|
// contents haven't changed. That means if we get here there's nothing to
|
|
|
|
// do.
|
|
|
|
return nil
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
2015-05-04 19:26:17 +02:00
|
|
|
var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
|
|
|
|
|
2015-05-09 00:46:37 +02:00
|
|
|
func render(d *schema.ResourceData) (string, error) {
|
2015-05-02 01:59:16 +02:00
|
|
|
filename := d.Get("filename").(string)
|
|
|
|
vars := d.Get("vars").(map[string]interface{})
|
|
|
|
|
2015-05-04 19:26:17 +02:00
|
|
|
path, err := homedir.Expand(filename)
|
|
|
|
if err != nil {
|
2015-05-09 00:46:37 +02:00
|
|
|
return "", err
|
2015-05-04 19:26:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
buf, err := readfile(path)
|
2015-05-02 01:59:16 +02:00
|
|
|
if err != nil {
|
2015-05-09 00:46:37 +02:00
|
|
|
return "", err
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rendered, err := execute(string(buf), vars)
|
|
|
|
if err != nil {
|
2015-05-09 00:46:37 +02:00
|
|
|
return "", fmt.Errorf("failed to render %v: %v", filename, err)
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
2015-05-09 00:46:37 +02:00
|
|
|
return rendered, nil
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// execute parses and executes a template using vars.
|
|
|
|
func execute(s string, vars map[string]interface{}) (string, error) {
|
|
|
|
root, err := lang.Parse(s)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
varmap := make(map[string]ast.Variable)
|
|
|
|
for k, v := range vars {
|
|
|
|
// As far as I can tell, v is always a string.
|
|
|
|
// If it's not, tell the user gracefully.
|
|
|
|
s, ok := v.(string)
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("unexpected type for variable %q: %T", k, v)
|
|
|
|
}
|
|
|
|
varmap[k] = ast.Variable{
|
|
|
|
Value: s,
|
|
|
|
Type: ast.TypeString,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg := lang.EvalConfig{
|
|
|
|
GlobalScope: &ast.BasicScope{
|
|
|
|
VarMap: varmap,
|
|
|
|
FuncMap: config.Funcs,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
out, typ, err := lang.Eval(root, &cfg)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if typ != ast.TypeString {
|
|
|
|
return "", fmt.Errorf("unexpected output ast.Type: %v", typ)
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.(string), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func hash(s string) string {
|
|
|
|
sha := sha256.Sum256([]byte(s))
|
2015-05-09 00:46:37 +02:00
|
|
|
return hex.EncodeToString(sha[:])
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|