github.com/paulmey/terraform@v0.5.2-0.20150519145237-046e9b4c884d/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  		Delete: Delete,
    20  		Exists: Exists,
    21  		Read:   Read,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"filename": &schema.Schema{
    25  				Type:        schema.TypeString,
    26  				Required:    true,
    27  				Description: "file to read template from",
    28  				ForceNew:    true,
    29  			},
    30  			"vars": &schema.Schema{
    31  				Type:        schema.TypeMap,
    32  				Optional:    true,
    33  				Default:     make(map[string]interface{}),
    34  				Description: "variables to substitute",
    35  				ForceNew:    true,
    36  			},
    37  			"rendered": &schema.Schema{
    38  				Type:        schema.TypeString,
    39  				Computed:    true,
    40  				Description: "rendered template",
    41  			},
    42  		},
    43  	}
    44  }
    45  
    46  func Create(d *schema.ResourceData, meta interface{}) error {
    47  	rendered, err := render(d)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	d.Set("rendered", rendered)
    52  	d.SetId(hash(rendered))
    53  	return nil
    54  }
    55  
    56  func Delete(d *schema.ResourceData, meta interface{}) error {
    57  	d.SetId("")
    58  	return nil
    59  }
    60  
    61  func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
    62  	rendered, err := render(d)
    63  	if err != nil {
    64  		return false, err
    65  	}
    66  	return hash(rendered) == d.Id(), nil
    67  }
    68  
    69  func Read(d *schema.ResourceData, meta interface{}) error {
    70  	// Logic is handled in Exists, which only returns true if the rendered
    71  	// contents haven't changed. That means if we get here there's nothing to
    72  	// do.
    73  	return nil
    74  }
    75  
    76  var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
    77  
    78  func render(d *schema.ResourceData) (string, error) {
    79  	filename := d.Get("filename").(string)
    80  	vars := d.Get("vars").(map[string]interface{})
    81  
    82  	path, err := homedir.Expand(filename)
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  
    87  	buf, err := readfile(path)
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  
    92  	rendered, err := execute(string(buf), vars)
    93  	if err != nil {
    94  		return "", fmt.Errorf("failed to render %v: %v", filename, err)
    95  	}
    96  
    97  	return rendered, nil
    98  }
    99  
   100  // execute parses and executes a template using vars.
   101  func execute(s string, vars map[string]interface{}) (string, error) {
   102  	root, err := lang.Parse(s)
   103  	if err != nil {
   104  		return "", err
   105  	}
   106  
   107  	varmap := make(map[string]ast.Variable)
   108  	for k, v := range vars {
   109  		// As far as I can tell, v is always a string.
   110  		// If it's not, tell the user gracefully.
   111  		s, ok := v.(string)
   112  		if !ok {
   113  			return "", fmt.Errorf("unexpected type for variable %q: %T", k, v)
   114  		}
   115  		varmap[k] = ast.Variable{
   116  			Value: s,
   117  			Type:  ast.TypeString,
   118  		}
   119  	}
   120  
   121  	cfg := lang.EvalConfig{
   122  		GlobalScope: &ast.BasicScope{
   123  			VarMap:  varmap,
   124  			FuncMap: config.Funcs,
   125  		},
   126  	}
   127  
   128  	out, typ, err := lang.Eval(root, &cfg)
   129  	if err != nil {
   130  		return "", err
   131  	}
   132  	if typ != ast.TypeString {
   133  		return "", fmt.Errorf("unexpected output ast.Type: %v", typ)
   134  	}
   135  
   136  	return out.(string), nil
   137  }
   138  
   139  func hash(s string) string {
   140  	sha := sha256.Sum256([]byte(s))
   141  	return hex.EncodeToString(sha[:])
   142  }