github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/template/resource.go (about)

     1  package template
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"io/ioutil"
     8  
     9  	"github.com/hashicorp/terraform/config"
    10  	"github.com/hashicorp/terraform/config/lang"
    11  	"github.com/hashicorp/terraform/config/lang/ast"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/mitchellh/go-homedir"
    14  )
    15  
    16  func resource() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: Create,
    19  		Read:   Read,
    20  		Update: Update,
    21  		Delete: Delete,
    22  		Exists: Exists,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"filename": &schema.Schema{
    26  				Type:        schema.TypeString,
    27  				Required:    true,
    28  				Description: "file to read template from",
    29  			},
    30  			"vars": &schema.Schema{
    31  				Type:        schema.TypeMap,
    32  				Optional:    true,
    33  				Default:     make(map[string]interface{}),
    34  				Description: "variables to substitute",
    35  			},
    36  			"rendered": &schema.Schema{
    37  				Type:        schema.TypeString,
    38  				Computed:    true,
    39  				Description: "rendered template",
    40  			},
    41  		},
    42  	}
    43  }
    44  
    45  func Create(d *schema.ResourceData, meta interface{}) error { return eval(d) }
    46  func Update(d *schema.ResourceData, meta interface{}) error { return eval(d) }
    47  func Read(d *schema.ResourceData, meta interface{}) error   { return nil }
    48  func Delete(d *schema.ResourceData, meta interface{}) error {
    49  	d.SetId("")
    50  	return nil
    51  }
    52  func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
    53  	// Reload every time in case something has changed.
    54  	// This should be cheap, and cache invalidation is hard.
    55  	return false, nil
    56  }
    57  
    58  var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
    59  
    60  func eval(d *schema.ResourceData) error {
    61  	filename := d.Get("filename").(string)
    62  	vars := d.Get("vars").(map[string]interface{})
    63  
    64  	path, err := homedir.Expand(filename)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	buf, err := readfile(path)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	rendered, err := execute(string(buf), vars)
    75  	if err != nil {
    76  		return fmt.Errorf("failed to render %v: %v", filename, err)
    77  	}
    78  
    79  	d.Set("rendered", rendered)
    80  	d.SetId(hash(rendered))
    81  	return nil
    82  }
    83  
    84  // execute parses and executes a template using vars.
    85  func execute(s string, vars map[string]interface{}) (string, error) {
    86  	root, err := lang.Parse(s)
    87  	if err != nil {
    88  		return "", err
    89  	}
    90  
    91  	varmap := make(map[string]ast.Variable)
    92  	for k, v := range vars {
    93  		// As far as I can tell, v is always a string.
    94  		// If it's not, tell the user gracefully.
    95  		s, ok := v.(string)
    96  		if !ok {
    97  			return "", fmt.Errorf("unexpected type for variable %q: %T", k, v)
    98  		}
    99  		varmap[k] = ast.Variable{
   100  			Value: s,
   101  			Type:  ast.TypeString,
   102  		}
   103  	}
   104  
   105  	cfg := lang.EvalConfig{
   106  		GlobalScope: &ast.BasicScope{
   107  			VarMap:  varmap,
   108  			FuncMap: config.Funcs,
   109  		},
   110  	}
   111  
   112  	out, typ, err := lang.Eval(root, &cfg)
   113  	if err != nil {
   114  		return "", err
   115  	}
   116  	if typ != ast.TypeString {
   117  		return "", fmt.Errorf("unexpected output ast.Type: %v", typ)
   118  	}
   119  
   120  	return out.(string), nil
   121  }
   122  
   123  func hash(s string) string {
   124  	sha := sha256.Sum256([]byte(s))
   125  	return hex.EncodeToString(sha[:])[:20]
   126  }