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