From c307dc95571f210931fda3c6aa1f59816f4ada41 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Sat, 1 Oct 2016 00:35:27 +0200 Subject: [PATCH] Accept both slices and strings in vault_json (#9114) Fixes #9105 by allowing the `vault_json` to contain either slices or strings. And fixes #8932 by changing to way we cleanup the user key. --- .../provisioners/chef/resource_provisioner.go | 56 +++++--- .../chef/resource_provisioner_test.go | 125 ++++++++++++++++++ 2 files changed, 166 insertions(+), 15 deletions(-) diff --git a/builtin/provisioners/chef/resource_provisioner.go b/builtin/provisioners/chef/resource_provisioner.go index cf767c765..036e6af70 100644 --- a/builtin/provisioners/chef/resource_provisioner.go +++ b/builtin/provisioners/chef/resource_provisioner.go @@ -11,6 +11,7 @@ import ( "path" "regexp" "strings" + "sync" "text/template" "time" @@ -110,7 +111,7 @@ type Provisioner struct { Version string `mapstructure:"version"` attributes map[string]interface{} - vaults map[string]string + vaults map[string][]string cleanupUserKeyCmd string createConfigFiles func(terraform.UIOutput, communicator.Communicator) error @@ -192,12 +193,14 @@ func (r *ResourceProvisioner) Apply( defer comm.Disconnect() // Make sure we always delete the user key from the new node! - defer func() { + var once sync.Once + cleanupUserKey := func() { o.Output("Cleanup user key...") if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil { o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error()) } - }() + } + defer once.Do(cleanupUserKey) if !p.SkipInstall { if err := p.installChefClient(o, comm); err != nil { @@ -229,6 +232,10 @@ func (r *ResourceProvisioner) Apply( } } + // Cleanup the user key before we run Chef-Client to prevent issues + // with rights caused by changing settings during the run. + once.Do(cleanupUserKey) + o.Output("Starting initial Chef-Client run...") if err := p.runChefClient(o, comm); err != nil { return err @@ -352,11 +359,28 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis } if vaults, ok := c.Config["vault_json"].(string); ok { - var m map[string]string + var m map[string]interface{} if err := json.Unmarshal([]byte(vaults), &m); err != nil { return nil, fmt.Errorf("Error parsing vault_json: %v", err) } - p.vaults = m + + v := make(map[string][]string) + for vault, items := range m { + switch items := items.(type) { + case []interface{}: + for _, item := range items { + if item, ok := item.(string); ok { + v[vault] = append(v[vault], item) + } + } + case interface{}: + if item, ok := items.(string); ok { + v[vault] = append(v[vault], item) + } + } + } + + p.vaults = v } return p, nil @@ -544,16 +568,18 @@ func (p *Provisioner) configureVaultsFunc( path.Join(confDir, p.UserName+".pem"), ) - for vault, item := range p.vaults { - updateCmd := fmt.Sprintf("%s vault update %s %s -A %s -M client %s", - knifeCmd, - vault, - item, - p.NodeName, - options, - ) - if err := p.runCommand(o, comm, updateCmd); err != nil { - return err + for vault, items := range p.vaults { + for _, item := range items { + updateCmd := fmt.Sprintf("%s vault update %s %s -A %s -M client %s", + knifeCmd, + vault, + item, + p.NodeName, + options, + ) + if err := p.runCommand(o, comm, updateCmd); err != nil { + return err + } } } diff --git a/builtin/provisioners/chef/resource_provisioner_test.go b/builtin/provisioners/chef/resource_provisioner_test.go index 8d9603f98..692c89f92 100644 --- a/builtin/provisioners/chef/resource_provisioner_test.go +++ b/builtin/provisioners/chef/resource_provisioner_test.go @@ -221,3 +221,128 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) { } } } + +func TestResourceProvider_configureVaults(t *testing.T) { + cases := map[string]struct { + Config *terraform.ResourceConfig + GemCmd string + KnifeCmd string + ConfDir string + Commands map[string]bool + }{ + "Linux Vault string": { + Config: testConfig(t, map[string]interface{}{ + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "vault_json": `{"vault1": "item1"}`, + }), + + GemCmd: linuxGemCmd, + KnifeCmd: linuxKnifeCmd, + ConfDir: linuxConfDir, + + Commands: map[string]bool{ + fmt.Sprintf("%s install chef-vault", linuxGemCmd): true, + fmt.Sprintf("%s vault update vault1 item1 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true, + }, + }, + + "Linux Vault []string": { + Config: testConfig(t, map[string]interface{}{ + "fetch_chef_certificates": true, + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "vault_json": `{"vault1": ["item1", "item2"]}`, + }), + + GemCmd: linuxGemCmd, + KnifeCmd: linuxKnifeCmd, + ConfDir: linuxConfDir, + + Commands: map[string]bool{ + fmt.Sprintf("%s install chef-vault", linuxGemCmd): true, + fmt.Sprintf("%s vault update vault1 item1 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true, + fmt.Sprintf("%s vault update vault1 item2 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", linuxKnifeCmd, linuxConfDir, linuxConfDir): true, + }, + }, + + "Windows Vault string": { + Config: testConfig(t, map[string]interface{}{ + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "vault_json": `{"vault1": "item1"}`, + }), + + GemCmd: windowsGemCmd, + KnifeCmd: windowsKnifeCmd, + ConfDir: windowsConfDir, + + Commands: map[string]bool{ + fmt.Sprintf("%s install chef-vault", windowsGemCmd): true, + fmt.Sprintf("%s vault update vault1 item1 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true, + }, + }, + + "Windows Vault []string": { + Config: testConfig(t, map[string]interface{}{ + "fetch_chef_certificates": true, + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "vault_json": `{"vault1": ["item1", "item2"]}`, + }), + + GemCmd: windowsGemCmd, + KnifeCmd: windowsKnifeCmd, + ConfDir: windowsConfDir, + + Commands: map[string]bool{ + fmt.Sprintf("%s install chef-vault", windowsGemCmd): true, + fmt.Sprintf("%s vault update vault1 item1 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true, + fmt.Sprintf("%s vault update vault1 item2 -A nodename1 -M client -c %s/client.rb "+ + "-u bob --key %s/bob.pem", windowsKnifeCmd, windowsConfDir, windowsConfDir): true, + }, + }, + } + + r := new(ResourceProvisioner) + o := new(terraform.MockUIOutput) + c := new(communicator.MockCommunicator) + + for k, tc := range cases { + c.Commands = tc.Commands + + p, err := r.decodeConfig(tc.Config) + if err != nil { + t.Fatalf("Error: %v", err) + } + + p.configureVaults = p.configureVaultsFunc(tc.GemCmd, tc.KnifeCmd, tc.ConfDir) + p.useSudo = !p.PreventSudo + + err = p.configureVaults(o, c) + if err != nil { + t.Fatalf("Test %q failed: %v", k, err) + } + } +}