github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/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  }