github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/template/datasource_template_file.go (about)

     1  package template
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/hil"
    12  	"github.com/hashicorp/hil/ast"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/helper/pathorcontents"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  )
    17  
    18  func dataSourceFile() *schema.Resource {
    19  	return &schema.Resource{
    20  		Read: dataSourceFileRead,
    21  
    22  		Schema: map[string]*schema.Schema{
    23  			"template": &schema.Schema{
    24  				Type:          schema.TypeString,
    25  				Optional:      true,
    26  				Description:   "Contents of the template",
    27  				ConflictsWith: []string{"filename"},
    28  				ValidateFunc:  validateTemplateAttribute,
    29  			},
    30  			"filename": &schema.Schema{
    31  				Type:        schema.TypeString,
    32  				Optional:    true,
    33  				Description: "file to read template from",
    34  				// Make a "best effort" attempt to relativize the file path.
    35  				StateFunc: func(v interface{}) string {
    36  					if v == nil || v.(string) == "" {
    37  						return ""
    38  					}
    39  					pwd, err := os.Getwd()
    40  					if err != nil {
    41  						return v.(string)
    42  					}
    43  					rel, err := filepath.Rel(pwd, v.(string))
    44  					if err != nil {
    45  						return v.(string)
    46  					}
    47  					return rel
    48  				},
    49  				Deprecated:    "Use the 'template' attribute instead.",
    50  				ConflictsWith: []string{"template"},
    51  			},
    52  			"vars": &schema.Schema{
    53  				Type:         schema.TypeMap,
    54  				Optional:     true,
    55  				Default:      make(map[string]interface{}),
    56  				Description:  "variables to substitute",
    57  				ValidateFunc: validateVarsAttribute,
    58  			},
    59  			"rendered": &schema.Schema{
    60  				Type:        schema.TypeString,
    61  				Computed:    true,
    62  				Description: "rendered template",
    63  			},
    64  		},
    65  	}
    66  }
    67  
    68  func dataSourceFileRead(d *schema.ResourceData, meta interface{}) error {
    69  	rendered, err := renderFile(d)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	d.Set("rendered", rendered)
    74  	d.SetId(hash(rendered))
    75  	return nil
    76  }
    77  
    78  type templateRenderError error
    79  
    80  func renderFile(d *schema.ResourceData) (string, error) {
    81  	template := d.Get("template").(string)
    82  	filename := d.Get("filename").(string)
    83  	vars := d.Get("vars").(map[string]interface{})
    84  
    85  	if template == "" && filename != "" {
    86  		template = filename
    87  	}
    88  
    89  	contents, _, err := pathorcontents.Read(template)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  
    94  	rendered, err := execute(contents, vars)
    95  	if err != nil {
    96  		return "", templateRenderError(
    97  			fmt.Errorf("failed to render %v: %v", filename, err),
    98  		)
    99  	}
   100  
   101  	return rendered, nil
   102  }
   103  
   104  // execute parses and executes a template using vars.
   105  func execute(s string, vars map[string]interface{}) (string, error) {
   106  	root, err := hil.Parse(s)
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  
   111  	varmap := make(map[string]ast.Variable)
   112  	for k, v := range vars {
   113  		// As far as I can tell, v is always a string.
   114  		// If it's not, tell the user gracefully.
   115  		s, ok := v.(string)
   116  		if !ok {
   117  			return "", fmt.Errorf("unexpected type for variable %q: %T", k, v)
   118  		}
   119  		varmap[k] = ast.Variable{
   120  			Value: s,
   121  			Type:  ast.TypeString,
   122  		}
   123  	}
   124  
   125  	cfg := hil.EvalConfig{
   126  		GlobalScope: &ast.BasicScope{
   127  			VarMap:  varmap,
   128  			FuncMap: config.Funcs(),
   129  		},
   130  	}
   131  
   132  	result, err := hil.Eval(root, &cfg)
   133  	if err != nil {
   134  		return "", err
   135  	}
   136  	if result.Type != hil.TypeString {
   137  		return "", fmt.Errorf("unexpected output hil.Type: %v", result.Type)
   138  	}
   139  
   140  	return result.Value.(string), nil
   141  }
   142  
   143  func hash(s string) string {
   144  	sha := sha256.Sum256([]byte(s))
   145  	return hex.EncodeToString(sha[:])
   146  }
   147  
   148  func validateTemplateAttribute(v interface{}, key string) (ws []string, es []error) {
   149  	_, wasPath, err := pathorcontents.Read(v.(string))
   150  	if err != nil {
   151  		es = append(es, err)
   152  		return
   153  	}
   154  
   155  	if wasPath {
   156  		ws = append(ws, fmt.Sprintf("%s: looks like you specified a path instead of file contents. Use `file()` to load this path. Specifying a path directly is deprecated and will be removed in a future version.", key))
   157  	}
   158  
   159  	return
   160  }
   161  
   162  func validateVarsAttribute(v interface{}, key string) (ws []string, es []error) {
   163  	// vars can only be primitives right now
   164  	var badVars []string
   165  	for k, v := range v.(map[string]interface{}) {
   166  		switch v.(type) {
   167  		case []interface{}:
   168  			badVars = append(badVars, fmt.Sprintf("%s (list)", k))
   169  		case map[string]interface{}:
   170  			badVars = append(badVars, fmt.Sprintf("%s (map)", k))
   171  		}
   172  	}
   173  	if len(badVars) > 0 {
   174  		es = append(es, fmt.Errorf(
   175  			"%s: cannot contain non-primitives; bad keys: %s",
   176  			key, strings.Join(badVars, ", ")))
   177  	}
   178  	return
   179  }