From 1e520b161b0fe5688df7d774ac5f59a910a7d1ae Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 15 Aug 2013 19:11:27 -0700 Subject: [PATCH] packer: ConfigTemplate, move from common --- packer/config_template.go | 81 ++++++++++++++++++++++++++++++++++ packer/config_template_test.go | 67 ++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 packer/config_template.go create mode 100644 packer/config_template_test.go diff --git a/packer/config_template.go b/packer/config_template.go new file mode 100644 index 000000000..354208cbc --- /dev/null +++ b/packer/config_template.go @@ -0,0 +1,81 @@ +package packer + +import ( + "bytes" + "fmt" + "strconv" + "text/template" + "time" +) + +// ConfigTemplate processes string data as a text/template with some common +// elements and functions available. Plugin creators should process as +// many fields as possible through this. +type ConfigTemplate struct { + UserVars map[string]string + + root *template.Template + i int +} + +// NewConfigTemplate creates a new configuration template processor. +func NewConfigTemplate() (*ConfigTemplate, error) { + result := &ConfigTemplate{ + UserVars: make(map[string]string), + } + + result.root = template.New("configTemplateRoot") + result.root.Funcs(template.FuncMap{ + "timestamp": templateTimestamp, + "user": result.templateUser, + }) + + return result, nil +} + +// Process processes a single string, compiling and executing the template. +func (t *ConfigTemplate) Process(s string, data interface{}) (string, error) { + tpl, err := t.root.New(t.nextTemplateName()).Parse(s) + if err != nil { + return "", err + } + + buf := new(bytes.Buffer) + if err := tpl.Execute(buf, data); err != nil { + return "", err + } + + return buf.String(), nil +} + +// Validate the template. +func (t *ConfigTemplate) Validate(s string) error { + root, err := t.root.Clone() + if err != nil { + return err + } + + _, err = root.New("template").Parse(s) + return err +} + +func (t *ConfigTemplate) nextTemplateName() string { + name := fmt.Sprintf("tpl%d", t.i) + t.i++ + return name +} + +// User is the function exposed as "user" within the templates and +// looks up user variables. +func (t *ConfigTemplate) templateUser(n string) (string, error) { + result, ok := t.UserVars[n] + if !ok { + return "", fmt.Errorf("uknown user var: %s", n) + } + + return result, nil +} + +func templateTimestamp() string { + return strconv.FormatInt(time.Now().UTC().Unix(), 10) +} diff --git a/packer/config_template_test.go b/packer/config_template_test.go new file mode 100644 index 000000000..4c4864cda --- /dev/null +++ b/packer/config_template_test.go @@ -0,0 +1,67 @@ +package packer + +import ( + "math" + "strconv" + "testing" + "time" +) + +func TestConfigTemplateProcess_timestamp(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + result, err := tpl.Process(`{{timestamp}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + val, err := strconv.ParseInt(result, 10, 64) + if err != nil { + t.Fatalf("err: %s", err) + } + + currentTime := time.Now().UTC().Unix() + if math.Abs(float64(currentTime-val)) > 10 { + t.Fatalf("val: %d (current: %d)", val, currentTime) + } +} + +func TestConfigTemplateProcess_user(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + tpl.UserVars["foo"] = "bar" + + result, err := tpl.Process(`{{user "foo"}}`, nil) + if err != nil { + t.Fatalf("err: %s", err) + } + + if result != "bar" { + t.Fatalf("bad: %s", result) + } +} + +func TestConfigTemplateValidate(t *testing.T) { + tpl, err := NewConfigTemplate() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Valid + err = tpl.Validate(`{{user "foo"}}`) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Invalid + err = tpl.Validate(`{{idontexist}}`) + if err == nil { + t.Fatal("should have error") + } +}