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