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