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