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.
This commit is contained in:
Mitchell Hashimoto 2013-08-30 16:05:04 -07:00
parent 6914baa473
commit 662e74d070
3 changed files with 72 additions and 40 deletions

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}