github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/builtin/providers/template/datasource_template_file.go (about) 1 package template 2 3 import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strconv" 10 "strings" 11 12 "github.com/hashicorp/hil" 13 "github.com/hashicorp/hil/ast" 14 "github.com/hashicorp/terraform/config" 15 "github.com/hashicorp/terraform/helper/pathorcontents" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func dataSourceFile() *schema.Resource { 20 return &schema.Resource{ 21 Read: dataSourceFileRead, 22 23 Schema: map[string]*schema.Schema{ 24 "template": &schema.Schema{ 25 Type: schema.TypeString, 26 Optional: true, 27 Description: "Contents of the template", 28 ConflictsWith: []string{"filename"}, 29 ValidateFunc: validateTemplateAttribute, 30 }, 31 "filename": &schema.Schema{ 32 Type: schema.TypeString, 33 Optional: true, 34 Description: "file to read template from", 35 // Make a "best effort" attempt to relativize the file path. 36 StateFunc: func(v interface{}) string { 37 if v == nil || v.(string) == "" { 38 return "" 39 } 40 pwd, err := os.Getwd() 41 if err != nil { 42 return v.(string) 43 } 44 rel, err := filepath.Rel(pwd, v.(string)) 45 if err != nil { 46 return v.(string) 47 } 48 return rel 49 }, 50 Deprecated: "Use the 'template' attribute instead.", 51 ConflictsWith: []string{"template"}, 52 }, 53 "vars": &schema.Schema{ 54 Type: schema.TypeMap, 55 Optional: true, 56 Default: make(map[string]interface{}), 57 Description: "variables to substitute", 58 ValidateFunc: validateVarsAttribute, 59 }, 60 "rendered": &schema.Schema{ 61 Type: schema.TypeString, 62 Computed: true, 63 Description: "rendered template", 64 }, 65 }, 66 } 67 } 68 69 func dataSourceFileRead(d *schema.ResourceData, meta interface{}) error { 70 rendered, err := renderFile(d) 71 if err != nil { 72 return err 73 } 74 d.Set("rendered", rendered) 75 d.SetId(hash(rendered)) 76 return nil 77 } 78 79 type templateRenderError error 80 81 func renderFile(d *schema.ResourceData) (string, error) { 82 template := d.Get("template").(string) 83 filename := d.Get("filename").(string) 84 vars := d.Get("vars").(map[string]interface{}) 85 86 if template == "" && filename != "" { 87 template = filename 88 } 89 90 contents, _, err := pathorcontents.Read(template) 91 if err != nil { 92 return "", err 93 } 94 95 rendered, err := execute(contents, vars) 96 if err != nil { 97 return "", templateRenderError( 98 fmt.Errorf("failed to render %v: %v", filename, err), 99 ) 100 } 101 102 return rendered, nil 103 } 104 105 // execute parses and executes a template using vars. 106 func execute(s string, vars map[string]interface{}) (string, error) { 107 root, err := hil.Parse(s) 108 if err != nil { 109 return "", err 110 } 111 112 varmap := make(map[string]ast.Variable) 113 for k, v := range vars { 114 // As far as I can tell, v is always a string. 115 // If it's not, tell the user gracefully. 116 s, ok := v.(string) 117 if !ok { 118 return "", fmt.Errorf("unexpected type for variable %q: %T", k, v) 119 } 120 121 // Store the defaults (string and value) 122 var val interface{} = s 123 typ := ast.TypeString 124 125 // If we can parse a float, then use that 126 if v, err := strconv.ParseFloat(s, 64); err == nil { 127 val = v 128 typ = ast.TypeFloat 129 } 130 131 varmap[k] = ast.Variable{ 132 Value: val, 133 Type: typ, 134 } 135 } 136 137 cfg := hil.EvalConfig{ 138 GlobalScope: &ast.BasicScope{ 139 VarMap: varmap, 140 FuncMap: config.Funcs(), 141 }, 142 } 143 144 result, err := hil.Eval(root, &cfg) 145 if err != nil { 146 return "", err 147 } 148 if result.Type != hil.TypeString { 149 return "", fmt.Errorf("unexpected output hil.Type: %v", result.Type) 150 } 151 152 return result.Value.(string), nil 153 } 154 155 func hash(s string) string { 156 sha := sha256.Sum256([]byte(s)) 157 return hex.EncodeToString(sha[:]) 158 } 159 160 func validateTemplateAttribute(v interface{}, key string) (ws []string, es []error) { 161 _, wasPath, err := pathorcontents.Read(v.(string)) 162 if err != nil { 163 es = append(es, err) 164 return 165 } 166 167 if wasPath { 168 ws = append(ws, fmt.Sprintf("%s: looks like you specified a path instead of file contents. Use `file()` to load this path. Specifying a path directly is deprecated and will be removed in a future version.", key)) 169 } 170 171 return 172 } 173 174 func validateVarsAttribute(v interface{}, key string) (ws []string, es []error) { 175 // vars can only be primitives right now 176 var badVars []string 177 for k, v := range v.(map[string]interface{}) { 178 switch v.(type) { 179 case []interface{}: 180 badVars = append(badVars, fmt.Sprintf("%s (list)", k)) 181 case map[string]interface{}: 182 badVars = append(badVars, fmt.Sprintf("%s (map)", k)) 183 } 184 } 185 if len(badVars) > 0 { 186 es = append(es, fmt.Errorf( 187 "%s: cannot contain non-primitives; bad keys: %s", 188 key, strings.Join(badVars, ", "))) 189 } 190 return 191 }