diff --git a/CHANGELOG.md b/CHANGELOG.md index 2927455c8..518820087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ BUG FIXES: Windows [GH-963] * provisioner/ansible: set cwd to staging directory [GH-1016] * provisioners/chef-client: Don't chown directory with Ubuntu. [GH-939] + * provisioners/chef-solo: Deeply nested JSON works properly. [GH-1076] * provisioners/shell: Env var values can have equal signs. [GH-1045] * provisioners/shell: chmod the uploaded script file to 0777. [GH-994] * post-processor/docker-push: Allow repositories with ports. [GH-923] diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 0d95a0ec5..0295546cd 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -203,12 +203,24 @@ 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)) + jsonValid := true + for k, v := range p.config.Json { + p.config.Json[k], err = p.deepJsonFix(k, v) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Error processing JSON: %s", err)) + jsonValid = false + } + } + + if jsonValid { + // 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 { @@ -470,6 +482,57 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error return nil } +func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) { + if current == nil { + return nil, nil + } + + switch c := current.(type) { + case []interface{}: + val := make([]interface{}, len(c)) + for i, v := range c { + var err error + val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v) + if err != nil { + return nil, err + } + } + + return val, nil + case bool: + return c, nil + case int: + return c, nil + case uint: + return c, nil + case float32: + return c, nil + case float64: + return c, nil + case map[interface{}]interface{}: + val := make(map[string]interface{}) + for k, v := range c { + ks, ok := k.(string) + if !ok { + return nil, fmt.Errorf("%s: key is not string", key) + } + + var err error + val[ks], err = p.deepJsonFix( + fmt.Sprintf("%s.%s", key, ks), v) + if err != nil { + return nil, err + } + } + + return val, nil + case string: + return c, nil + default: + return nil, fmt.Errorf("Unknown type for key: '%s'", key) + } +} + func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { jsonBytes, err := json.Marshal(p.config.Json) if err != nil { diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index f7094103e..35fdb915a 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -262,3 +262,33 @@ func TestProvisionerPrepare_json(t *testing.T) { t.Fatalf("bad: %#v", p.config.Json) } } + +func TestProvisionerPrepare_jsonNested(t *testing.T) { + config := testConfig() + config["json"] = map[string]interface{}{ + "foo": map[interface{}]interface{}{ + "bar": "baz", + }, + + "bar": []interface{}{ + "foo", + + map[interface{}]interface{}{ + "bar": "baz", + }, + }, + + "bFalse": false, + "bTrue": true, + "bNil": nil, + + "bInt": 1, + "bFloat": 4.5, + } + + var p Provisioner + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +}