diff --git a/builtin/bins/provider-template/main.go b/builtin/bins/provider-template/main.go new file mode 100644 index 000000000..3d6979e7b --- /dev/null +++ b/builtin/bins/provider-template/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/template" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: template.Provider, + }) +} diff --git a/builtin/providers/template/provider.go b/builtin/providers/template/provider.go new file mode 100644 index 000000000..7513341bc --- /dev/null +++ b/builtin/providers/template/provider.go @@ -0,0 +1,14 @@ +package template + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "template_file": resource(), + }, + } +} diff --git a/builtin/providers/template/provider_test.go b/builtin/providers/template/provider_test.go new file mode 100644 index 000000000..37c02bb4a --- /dev/null +++ b/builtin/providers/template/provider_test.go @@ -0,0 +1,13 @@ +package template + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" +) + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/builtin/providers/template/resource.go b/builtin/providers/template/resource.go new file mode 100644 index 000000000..bbdb89c67 --- /dev/null +++ b/builtin/providers/template/resource.go @@ -0,0 +1,119 @@ +package template + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/lang" + "github.com/hashicorp/terraform/config/lang/ast" + "github.com/hashicorp/terraform/helper/schema" +) + +func resource() *schema.Resource { + return &schema.Resource{ + Create: Create, + Read: Read, + Update: Update, + Delete: Delete, + Exists: Exists, + + Schema: map[string]*schema.Schema{ + "filename": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "file to read template from", + }, + "vars": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Default: make(map[string]interface{}), + Description: "variables to substitute", + }, + "rendered": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Default: nil, + Description: "rendered template", + }, + }, + } +} + +func Create(d *schema.ResourceData, meta interface{}) error { return eval(d) } +func Update(d *schema.ResourceData, meta interface{}) error { return eval(d) } +func Read(d *schema.ResourceData, meta interface{}) error { return nil } +func Delete(d *schema.ResourceData, meta interface{}) error { + d.SetId("") + return nil +} +func Exists(d *schema.ResourceData, meta interface{}) (bool, error) { + // Reload every time in case something has changed. + // This should be cheap, and cache invalidation is hard. + return false, nil +} + +func eval(d *schema.ResourceData) error { + filename := d.Get("filename").(string) + vars := d.Get("vars").(map[string]interface{}) + + buf, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + + rendered, err := execute(string(buf), vars) + if err != nil { + return fmt.Errorf("failed to render %v: %v", filename, err) + } + + d.Set("rendered", rendered) + d.SetId(hash(rendered)) + return nil +} + +// execute parses and executes a template using vars. +func execute(s string, vars map[string]interface{}) (string, error) { + root, err := lang.Parse(s) + if err != nil { + return "", err + } + + varmap := make(map[string]ast.Variable) + for k, v := range vars { + // As far as I can tell, v is always a string. + // If it's not, tell the user gracefully. + s, ok := v.(string) + if !ok { + return "", fmt.Errorf("unexpected type for variable %q: %T", k, v) + } + varmap[k] = ast.Variable{ + Value: s, + Type: ast.TypeString, + } + } + + cfg := lang.EvalConfig{ + GlobalScope: &ast.BasicScope{ + VarMap: varmap, + FuncMap: config.Funcs, + }, + } + + out, typ, err := lang.Eval(root, &cfg) + if err != nil { + return "", err + } + if typ != ast.TypeString { + return "", fmt.Errorf("unexpected output ast.Type: %v", typ) + } + + return out.(string), nil +} + +func hash(s string) string { + sha := sha256.Sum256([]byte(s)) + return hex.EncodeToString(sha[:])[:20] +}