github.com/bendemaree/terraform@v0.5.4-0.20150613200311-f50d97d6eee6/builtin/providers/template/resource.go (about)

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