2015-05-02 01:59:16 +02:00
|
|
|
package template
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"fmt"
|
2015-05-21 22:01:28 +02:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2016-07-11 22:39:34 +02:00
|
|
|
"strings"
|
2015-05-02 01:59:16 +02:00
|
|
|
|
2016-01-31 08:45:15 +01:00
|
|
|
"github.com/hashicorp/hil"
|
|
|
|
"github.com/hashicorp/hil/ast"
|
2015-05-02 01:59:16 +02:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2015-11-13 18:07:02 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/pathorcontents"
|
2015-05-02 01:59:16 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
)
|
|
|
|
|
2016-05-22 00:09:55 +02:00
|
|
|
func dataSourceFile() *schema.Resource {
|
2015-05-02 01:59:16 +02:00
|
|
|
return &schema.Resource{
|
2016-05-22 00:09:55 +02:00
|
|
|
Read: dataSourceFileRead,
|
2015-05-02 01:59:16 +02:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
2015-11-13 18:07:02 +01:00
|
|
|
"template": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
|
|
|
Description: "Contents of the template",
|
|
|
|
ConflictsWith: []string{"filename"},
|
|
|
|
},
|
2015-05-02 01:59:16 +02:00
|
|
|
"filename": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
2015-11-13 18:07:02 +01:00
|
|
|
Optional: true,
|
2015-05-02 01:59:16 +02:00
|
|
|
Description: "file to read template from",
|
2015-05-21 22:01:28 +02:00
|
|
|
// Make a "best effort" attempt to relativize the file path.
|
|
|
|
StateFunc: func(v interface{}) string {
|
2015-11-13 18:07:02 +01:00
|
|
|
if v == nil || v.(string) == "" {
|
|
|
|
return ""
|
|
|
|
}
|
2015-05-21 22:01:28 +02:00
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return v.(string)
|
|
|
|
}
|
|
|
|
rel, err := filepath.Rel(pwd, v.(string))
|
|
|
|
if err != nil {
|
|
|
|
return v.(string)
|
|
|
|
}
|
|
|
|
return rel
|
|
|
|
},
|
2015-11-13 18:07:02 +01:00
|
|
|
Deprecated: "Use the 'template' attribute instead.",
|
|
|
|
ConflictsWith: []string{"template"},
|
2015-05-02 01:59:16 +02:00
|
|
|
},
|
|
|
|
"vars": &schema.Schema{
|
2016-07-11 22:39:34 +02:00
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Default: make(map[string]interface{}),
|
|
|
|
Description: "variables to substitute",
|
|
|
|
ValidateFunc: validateVarsAttribute,
|
2015-05-02 01:59:16 +02:00
|
|
|
},
|
|
|
|
"rendered": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Computed: true,
|
|
|
|
Description: "rendered template",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-22 00:09:55 +02:00
|
|
|
func dataSourceFileRead(d *schema.ResourceData, meta interface{}) error {
|
2015-10-06 01:08:33 +02:00
|
|
|
rendered, err := renderFile(d)
|
2015-05-09 00:46:37 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
d.Set("rendered", rendered)
|
|
|
|
d.SetId(hash(rendered))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-06-17 20:58:01 +02:00
|
|
|
type templateRenderError error
|
|
|
|
|
2015-10-06 01:08:33 +02:00
|
|
|
func renderFile(d *schema.ResourceData) (string, error) {
|
2015-11-13 18:07:02 +01:00
|
|
|
template := d.Get("template").(string)
|
2015-05-02 01:59:16 +02:00
|
|
|
filename := d.Get("filename").(string)
|
|
|
|
vars := d.Get("vars").(map[string]interface{})
|
|
|
|
|
2016-11-22 19:23:58 +01:00
|
|
|
contents := template
|
2015-11-13 18:07:02 +01:00
|
|
|
if template == "" && filename != "" {
|
2016-11-22 19:23:58 +01:00
|
|
|
data, _, err := pathorcontents.Read(filename)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2015-05-04 19:26:17 +02:00
|
|
|
|
2016-11-22 19:23:58 +01:00
|
|
|
contents = data
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
2015-11-13 18:07:02 +01:00
|
|
|
rendered, err := execute(contents, vars)
|
2015-05-02 01:59:16 +02:00
|
|
|
if err != nil {
|
2015-06-17 20:58:01 +02:00
|
|
|
return "", templateRenderError(
|
|
|
|
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) {
|
2016-01-31 08:45:15 +01:00
|
|
|
root, err := hil.Parse(s)
|
2015-05-02 01:59:16 +02:00
|
|
|
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{
|
2016-09-02 22:20:03 +02:00
|
|
|
Value: s,
|
|
|
|
Type: ast.TypeString,
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-31 08:45:15 +01:00
|
|
|
cfg := hil.EvalConfig{
|
2015-05-02 01:59:16 +02:00
|
|
|
GlobalScope: &ast.BasicScope{
|
|
|
|
VarMap: varmap,
|
2016-01-15 22:28:47 +01:00
|
|
|
FuncMap: config.Funcs(),
|
2015-05-02 01:59:16 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2016-04-09 03:23:36 +02:00
|
|
|
result, err := hil.Eval(root, &cfg)
|
2015-05-02 01:59:16 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2016-04-09 03:23:36 +02:00
|
|
|
if result.Type != hil.TypeString {
|
|
|
|
return "", fmt.Errorf("unexpected output hil.Type: %v", result.Type)
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
2016-04-09 03:23:36 +02:00
|
|
|
return result.Value.(string), nil
|
2015-05-02 01:59:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
}
|
2016-03-10 19:34:56 +01:00
|
|
|
|
2016-07-11 22:39:34 +02:00
|
|
|
func validateVarsAttribute(v interface{}, key string) (ws []string, es []error) {
|
|
|
|
// vars can only be primitives right now
|
|
|
|
var badVars []string
|
|
|
|
for k, v := range v.(map[string]interface{}) {
|
|
|
|
switch v.(type) {
|
|
|
|
case []interface{}:
|
|
|
|
badVars = append(badVars, fmt.Sprintf("%s (list)", k))
|
|
|
|
case map[string]interface{}:
|
|
|
|
badVars = append(badVars, fmt.Sprintf("%s (map)", k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(badVars) > 0 {
|
|
|
|
es = append(es, fmt.Errorf(
|
|
|
|
"%s: cannot contain non-primitives; bad keys: %s",
|
|
|
|
key, strings.Join(badVars, ", ")))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|