From 4c4020f72324cf50ff58b42da87d91f23ea48eaf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 30 Aug 2013 16:05:04 -0700 Subject: [PATCH] provisioner/chef-solo: properly escape user vars for JSON /cc @mwhooker - I moved the processing up into Prepare so that any errors will be shown during a validate pass. Also, I escape some stuff in keys. Tests moved as well. --- packer/config_template_test.go | 35 +------------- provisioner/chef-solo/provisioner.go | 56 ++++++++++++++++++++--- provisioner/chef-solo/provisioner_test.go | 21 +++++++++ 3 files changed, 72 insertions(+), 40 deletions(-) diff --git a/packer/config_template_test.go b/packer/config_template_test.go index 2ef891bba..fc255246d 100644 --- a/packer/config_template_test.go +++ b/packer/config_template_test.go @@ -1,11 +1,11 @@ package packer import ( + "encoding/json" "math" "strconv" "testing" "time" - "encoding/json" ) func TestConfigTemplateProcess_timestamp(t *testing.T) { @@ -48,39 +48,6 @@ func TestConfigTemplateProcess_user(t *testing.T) { } } -func TestJsonTemplateProcess_user(t *testing.T) { - tpl, err := NewConfigTemplate() - if err != nil { - t.Fatalf("err: %s", err) - } - - tpl.UserVars["foo"] = "bar" - jsonData := make(map[string]interface{}) - jsonData["key"] = map[string]string{ - "key1": "{{user `foo`}}", - } - jsonBytes, err := json.MarshalIndent(jsonData, "", " ") - if err != nil { - t.Fatalf("err: %s", err) - } - var jsonString = string(jsonBytes) - - jsonString, err = tpl.Process(jsonString, nil) - if err != nil { - t.Fatalf("err: %s", err) - } - var dat map[string]map[string]interface{} - if err := json.Unmarshal([]byte(jsonString), &dat); err != nil { - t.Fatalf("err: %s", err) - } - - if dat["key"]["key1"] != "bar" { - t.Fatalf("found %s instead", dat["key"]["key1"]) - } - -} - - func TestConfigTemplateValidate(t *testing.T) { tpl, err := NewConfigTemplate() if err != nil { diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 8589070c6..90ee13811 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -95,6 +95,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { sliceTemplates := map[string][]string{ "cookbook_paths": p.config.CookbookPaths, "remote_cookbook_paths": p.config.RemoteCookbookPaths, + "run_list": p.config.RunList, } for n, slice := range sliceTemplates { @@ -129,6 +130,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + // Process the user variables within the JSON and set the JSON. + // Do this early so that we can validate and show errors. + p.config.Json, err = p.processJsonUserVars() + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) + } + if errs != nil && len(errs.Errors) > 0 { return errs } @@ -136,6 +145,46 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return nil } +func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { + jsonBytes, err := json.Marshal(p.config.Json) + if err != nil { + // This really shouldn't happen since we literally just unmarshalled + panic(err) + } + + // Copy the user variables so that we can restore them later, and + // make sure we make the quotes JSON-friendly in the user variables. + originalUserVars := make(map[string]string) + for k, v := range p.config.tpl.UserVars { + originalUserVars[k] = v + } + + // Make sure we reset them no matter what + defer func() { + p.config.tpl.UserVars = originalUserVars + }() + + // Make the current user variables JSON string safe. + for k, v := range p.config.tpl.UserVars { + v = strings.Replace(v, `\`, `\\`, -1) + v = strings.Replace(v, `"`, `\"`, -1) + p.config.tpl.UserVars[k] = v + } + + // Process the bytes with the template processor + jsonBytesProcessed, err := p.config.tpl.Process(string(jsonBytes), nil) + if err != nil { + return nil, err + } + + var result map[string]interface{} + if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil { + return nil, err + } + + return result, nil +} + func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { if !p.config.SkipInstall { if err := p.installChef(ui, comm); err != nil { @@ -236,14 +285,9 @@ func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string return "", err } - jsonBytesProcessed, err := p.config.tpl.Process(string(jsonBytes), nil) - if err != nil { - return "", err - } - // Upload the bytes remotePath := filepath.Join(p.config.StagingDir, "node.json") - if err := comm.Upload(remotePath, bytes.NewReader([]byte(jsonBytesProcessed))); err != nil { + if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes)); err != nil { return "", err } diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index 50a727446..332718d01 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -51,3 +51,24 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) { t.Fatalf("unexpected: %#v", p.config.CookbookPaths) } } + +func TestProvisionerPrepare_json(t *testing.T) { + config := testConfig() + config["json"] = map[string]interface{}{ + "foo": "{{ user `foo` }}", + } + + config[packer.UserVariablesConfigKey] = map[string]string{ + "foo": `"bar\baz"`, + } + + var p Provisioner + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.Json["foo"] != `"bar\baz"` { + t.Fatalf("bad: %#v", p.config.Json) + } +}