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 }