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