From 968472a63e4542a2c5e2693c4489da4776dac996 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 15 Sep 2016 14:20:18 +0200 Subject: [PATCH] Support recreating clients and configuring Chef Vaults (#8577) Fixes #3605 and adds the functionality suggested in PR #7440. This PR is using a different appraoch that (IMHO) feels cleaner and (even more important) adds support for Windows at the same time. --- .../resource_cloudstack_ssh_keypair.go | 10 +- .../chef/linux_provisioner_test.go | 213 +++---- .../provisioners/chef/resource_provisioner.go | 531 +++++++++++------- .../chef/resource_provisioner_test.go | 56 +- .../test-fixtures/encrypted_data_bag_secret | 1 - .../chef/test-fixtures/validator.pem | 1 - .../chef/windows_provisioner_test.go | 144 ++--- communicator/ssh/communicator_test.go | 44 -- communicator/ssh/provisioner.go | 10 +- .../docs/provisioners/chef.html.markdown | 28 +- 10 files changed, 496 insertions(+), 542 deletions(-) delete mode 100644 builtin/provisioners/chef/test-fixtures/encrypted_data_bag_secret delete mode 100644 builtin/provisioners/chef/test-fixtures/validator.pem diff --git a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go index 508077c41..f4fc09988 100644 --- a/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go +++ b/builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go @@ -5,7 +5,6 @@ import ( "log" "strings" - "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/schema" "github.com/xanzy/go-cloudstack/cloudstack" ) @@ -56,19 +55,14 @@ func resourceCloudStackSSHKeyPairCreate(d *schema.ResourceData, meta interface{} if publicKey != "" { // Register supplied key - key, _, err := pathorcontents.Read(publicKey) - if err != nil { - return fmt.Errorf("Error reading the public key: %v", err) - } - - p := cs.SSH.NewRegisterSSHKeyPairParams(name, string(key)) + p := cs.SSH.NewRegisterSSHKeyPairParams(name, publicKey) // If there is a project supplied, we retrieve and set the project id if err := setProjectid(p, cs, d); err != nil { return err } - _, err = cs.SSH.RegisterSSHKeyPair(p) + _, err := cs.SSH.RegisterSSHKeyPair(p) if err != nil { return err } diff --git a/builtin/provisioners/chef/linux_provisioner_test.go b/builtin/provisioners/chef/linux_provisioner_test.go index c33840583..7d2339e04 100644 --- a/builtin/provisioners/chef/linux_provisioner_test.go +++ b/builtin/provisioners/chef/linux_provisioner_test.go @@ -16,11 +16,11 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { }{ "Sudo": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -32,13 +32,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { "NoSudo": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", - "secret_key_path": "encrypted_data_bag_secret", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -50,13 +50,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { "HTTPProxy": { Config: testConfig(t, map[string]interface{}{ - "http_proxy": "http://proxy.local", - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "http_proxy": "http://proxy.local", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -68,13 +68,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { "HTTPSProxy": { Config: testConfig(t, map[string]interface{}{ - "https_proxy": "https://proxy.local", - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "https_proxy": "https://proxy.local", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -86,14 +86,14 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { "NoProxy": { Config: testConfig(t, map[string]interface{}{ - "http_proxy": "http://proxy.local", - "no_proxy": []interface{}{"http://local.local", "http://local.org"}, - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "http_proxy": "http://proxy.local", + "no_proxy": []interface{}{"http://local.local", "http://local.org"}, + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -108,13 +108,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { "Version": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", - "version": "11.18.6", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "version": "11.18.6", }), Commands: map[string]bool{ @@ -154,13 +154,13 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { }{ "Sudo": { Config: testConfig(t, map[string]interface{}{ - "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -180,22 +180,22 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ linuxConfDir + "/client.rb": defaultLinuxClientConf, - linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", + linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", linuxConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, linuxConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE", - linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", + linuxConfDir + "/bob.pem": "USER-KEY", }, }, "NoSudo": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -204,25 +204,25 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ linuxConfDir + "/client.rb": defaultLinuxClientConf, - linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", + linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", linuxConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, - linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", + linuxConfDir + "/bob.pem": "USER-KEY", }, }, "Proxy": { Config: testConfig(t, map[string]interface{}{ - "http_proxy": "http://proxy.local", - "https_proxy": "https://proxy.local", - "no_proxy": []interface{}{"http://local.local", "https://local.local"}, - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "ssl_verify_mode": "verify_none", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "http_proxy": "http://proxy.local", + "https_proxy": "https://proxy.local", + "no_proxy": []interface{}{"http://local.local", "https://local.local"}, + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "ssl_verify_mode": "verify_none", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -231,54 +231,9 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ linuxConfDir + "/client.rb": proxyLinuxClientConf, - linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", + linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", linuxConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, - linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", - }, - }, - - "Attributes": { - Config: testConfig(t, map[string]interface{}{ - "attributes": []map[string]interface{}{ - map[string]interface{}{ - "key1": []map[string]interface{}{ - map[string]interface{}{ - "subkey1": []map[string]interface{}{ - map[string]interface{}{ - "subkey2a": []interface{}{ - "val1", "val2", "val3", - }, - "subkey2b": []map[string]interface{}{ - map[string]interface{}{ - "subkey3": "value3", - }, - }, - }, - }, - }, - }, - "key2": "value2", - }, - }, - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", - }), - - Commands: map[string]bool{ - "mkdir -p " + linuxConfDir: true, - }, - - Uploads: map[string]string{ - linuxConfDir + "/client.rb": defaultLinuxClientConf, - linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", - linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", - linuxConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + - `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, + linuxConfDir + "/bob.pem": "USER-KEY", }, }, @@ -286,13 +241,13 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { Config: testConfig(t, map[string]interface{}{ "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -301,8 +256,8 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ linuxConfDir + "/client.rb": defaultLinuxClientConf, - linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", - linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", + linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", + linuxConfDir + "/bob.pem": "USER-KEY", linuxConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, }, @@ -332,13 +287,11 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { } const defaultLinuxClientConf = `log_location STDOUT -chef_server_url "https://chef.local" -validation_client_name "validator" +chef_server_url "https://chef.local/" node_name "nodename1"` const proxyLinuxClientConf = `log_location STDOUT -chef_server_url "https://chef.local" -validation_client_name "validator" +chef_server_url "https://chef.local/" node_name "nodename1" http_proxy "http://proxy.local" diff --git a/builtin/provisioners/chef/resource_provisioner.go b/builtin/provisioners/chef/resource_provisioner.go index 276c4d3af..cf767c765 100644 --- a/builtin/provisioners/chef/resource_provisioner.go +++ b/builtin/provisioners/chef/resource_provisioner.go @@ -16,7 +16,6 @@ import ( "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" - "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/go-homedir" "github.com/mitchellh/go-linereader" @@ -29,19 +28,21 @@ const ( firstBoot = "first-boot.json" logfileDir = "logfiles" linuxChefCmd = "chef-client" - linuxKnifeCmd = "knife" linuxConfDir = "/etc/chef" + linuxNoOutput = "> /dev/null 2>&1" + linuxGemCmd = "/opt/chef/embedded/bin/gem" + linuxKnifeCmd = "knife" secretKey = "encrypted_data_bag_secret" - validationKey = "validation.pem" windowsChefCmd = "cmd /c chef-client" - windowsKnifeCmd = "cmd /c knife" windowsConfDir = "C:/chef" + windowsNoOutput = "> nul 2>&1" + windowsGemCmd = "C:/opscode/chef/embedded/bin/gem" + windowsKnifeCmd = "cmd /c knife" ) const clientConf = ` log_location STDOUT chef_server_url "{{ .ServerURL }}" -validation_client_name "{{ .ValidationClientName }}" node_name "{{ .NodeName }}" {{ if .UsePolicyfile }} use_policyfile true @@ -79,43 +80,50 @@ enable_reporting false {{ end }} ` -// Provisioner represents a specificly configured chef provisioner +// Provisioner represents a Chef provisioner type Provisioner struct { - Attributes interface{} `mapstructure:"attributes"` - AttributesJSON string `mapstructure:"attributes_json"` - ClientOptions []string `mapstructure:"client_options"` - DisableReporting bool `mapstructure:"disable_reporting"` - Environment string `mapstructure:"environment"` - FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"` - LogToFile bool `mapstructure:"log_to_file"` - UsePolicyfile bool `mapstructure:"use_policyfile"` - PolicyGroup string `mapstructure:"policy_group"` - PolicyName string `mapstructure:"policy_name"` - HTTPProxy string `mapstructure:"http_proxy"` - HTTPSProxy string `mapstructure:"https_proxy"` - NOProxy []string `mapstructure:"no_proxy"` - NodeName string `mapstructure:"node_name"` - OhaiHints []string `mapstructure:"ohai_hints"` - OSType string `mapstructure:"os_type"` - PreventSudo bool `mapstructure:"prevent_sudo"` - RunList []string `mapstructure:"run_list"` - SecretKey string `mapstructure:"secret_key"` - ServerURL string `mapstructure:"server_url"` - SkipInstall bool `mapstructure:"skip_install"` - SSLVerifyMode string `mapstructure:"ssl_verify_mode"` - ValidationClientName string `mapstructure:"validation_client_name"` - ValidationKey string `mapstructure:"validation_key"` - Version string `mapstructure:"version"` + AttributesJSON string `mapstructure:"attributes_json"` + ClientOptions []string `mapstructure:"client_options"` + DisableReporting bool `mapstructure:"disable_reporting"` + Environment string `mapstructure:"environment"` + FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"` + LogToFile bool `mapstructure:"log_to_file"` + UsePolicyfile bool `mapstructure:"use_policyfile"` + PolicyGroup string `mapstructure:"policy_group"` + PolicyName string `mapstructure:"policy_name"` + HTTPProxy string `mapstructure:"http_proxy"` + HTTPSProxy string `mapstructure:"https_proxy"` + NOProxy []string `mapstructure:"no_proxy"` + NodeName string `mapstructure:"node_name"` + OhaiHints []string `mapstructure:"ohai_hints"` + OSType string `mapstructure:"os_type"` + RecreateClient bool `mapstructure:"recreate_client"` + PreventSudo bool `mapstructure:"prevent_sudo"` + RunList []string `mapstructure:"run_list"` + SecretKey string `mapstructure:"secret_key"` + ServerURL string `mapstructure:"server_url"` + SkipInstall bool `mapstructure:"skip_install"` + SSLVerifyMode string `mapstructure:"ssl_verify_mode"` + UserName string `mapstructure:"user_name"` + UserKey string `mapstructure:"user_key"` + VaultJSON string `mapstructure:"vault_json"` + Version string `mapstructure:"version"` - installChefClient func(terraform.UIOutput, communicator.Communicator) error + attributes map[string]interface{} + vaults map[string]string + + cleanupUserKeyCmd string createConfigFiles func(terraform.UIOutput, communicator.Communicator) error + installChefClient func(terraform.UIOutput, communicator.Communicator) error fetchChefCertificates func(terraform.UIOutput, communicator.Communicator) error + generateClientKey func(terraform.UIOutput, communicator.Communicator) error + configureVaults func(terraform.UIOutput, communicator.Communicator) error runChefClient func(terraform.UIOutput, communicator.Communicator) error useSudo bool // Deprecated Fields - SecretKeyPath string `mapstructure:"secret_key_path"` - ValidationKeyPath string `mapstructure:"validation_key_path"` + ValidationClientName string `mapstructure:"validation_client_name"` + ValidationKey string `mapstructure:"validation_key"` } // ResourceProvisioner represents a generic chef provisioner @@ -146,15 +154,21 @@ func (r *ResourceProvisioner) Apply( // Set some values based on the targeted OS switch p.OSType { case "linux": - p.installChefClient = p.linuxInstallChefClient + p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem")) p.createConfigFiles = p.linuxCreateConfigFiles + p.installChefClient = p.linuxInstallChefClient p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir) + p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput) + p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir) p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" case "windows": - p.installChefClient = p.windowsInstallChefClient + p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem") p.createConfigFiles = p.windowsCreateConfigFiles + p.installChefClient = p.windowsInstallChefClient p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir) + p.generateClientKey = p.generateClientKeyFunc(windowsKnifeCmd, windowsConfDir, windowsNoOutput) + p.configureVaults = p.configureVaultsFunc(windowsGemCmd, windowsKnifeCmd, windowsConfDir) p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) p.useSudo = false default: @@ -177,6 +191,14 @@ func (r *ResourceProvisioner) Apply( } defer comm.Disconnect() + // Make sure we always delete the user key from the new node! + defer 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()) + } + }() + if !p.SkipInstall { if err := p.installChefClient(o, comm); err != nil { return err @@ -195,6 +217,18 @@ func (r *ResourceProvisioner) Apply( } } + o.Output("Generate the private key...") + if err := p.generateClientKey(o, comm); err != nil { + return err + } + + if p.VaultJSON != "" { + o.Output("Configure Chef vaults...") + if err := p.configureVaults(o, comm); err != nil { + return err + } + } + o.Output("Starting initial Chef-Client run...") if err := p.runChefClient(o, comm); err != nil { return err @@ -212,38 +246,42 @@ func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string } if p.NodeName == "" { - es = append(es, fmt.Errorf("Key not found: node_name")) + es = append(es, errors.New("Key not found: node_name")) } if !p.UsePolicyfile && p.RunList == nil { - es = append(es, fmt.Errorf("Key not found: run_list")) + es = append(es, errors.New("Key not found: run_list")) } if p.ServerURL == "" { - es = append(es, fmt.Errorf("Key not found: server_url")) - } - if p.ValidationClientName == "" { - es = append(es, fmt.Errorf("Key not found: validation_client_name")) - } - if p.ValidationKey == "" && p.ValidationKeyPath == "" { - es = append(es, fmt.Errorf( - "One of validation_key or the deprecated validation_key_path must be provided")) + es = append(es, errors.New("Key not found: server_url")) } if p.UsePolicyfile && p.PolicyName == "" { - es = append(es, fmt.Errorf("Policyfile enabled but key not found: policy_name")) + es = append(es, errors.New("Policyfile enabled but key not found: policy_name")) } if p.UsePolicyfile && p.PolicyGroup == "" { - es = append(es, fmt.Errorf("Policyfile enabled but key not found: policy_group")) + es = append(es, errors.New("Policyfile enabled but key not found: policy_group")) } - if p.ValidationKeyPath != "" { - ws = append(ws, "validation_key_path is deprecated, please use "+ - "validation_key instead and load the key contents via file()") + if p.UserName == "" && p.ValidationClientName == "" { + es = append(es, errors.New( + "One of user_name or the deprecated validation_client_name must be provided")) } - if p.SecretKeyPath != "" { - ws = append(ws, "secret_key_path is deprecated, please use "+ - "secret_key instead and load the key contents via file()") + if p.UserKey == "" && p.ValidationKey == "" { + es = append(es, errors.New( + "One of user_key or the deprecated validation_key must be provided")) } - if _, ok := c.Config["attributes"]; ok { - ws = append(ws, "using map style attribute values is deprecated, "+ - " please use a single raw JSON string instead") + if p.ValidationClientName != "" { + ws = append(ws, "validation_client_name is deprecated, please use user_name instead") + } + if p.ValidationKey != "" { + ws = append(ws, "validation_key is deprecated, please use user_key instead") + + if p.RecreateClient { + es = append(es, errors.New( + "Cannot use recreate_client=true with the deprecated validation_key, please provide a user_key")) + } + if p.VaultJSON != "" { + es = append(es, errors.New( + "Cannot configure chef vaults using the deprecated validation_key, please provide a user_key")) + } } return ws, es @@ -282,6 +320,9 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis return nil, err } + // Make sure the supplied URL has a trailing slash + p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/" + if p.Environment == "" { p.Environment = defaultEnv } @@ -294,69 +335,126 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis p.OhaiHints[i] = hintPath } - if p.ValidationKey == "" && p.ValidationKeyPath != "" { - p.ValidationKey = p.ValidationKeyPath + if p.UserName == "" && p.ValidationClientName != "" { + p.UserName = p.ValidationClientName } - if p.SecretKey == "" && p.SecretKeyPath != "" { - p.SecretKey = p.SecretKeyPath + if p.UserKey == "" && p.ValidationKey != "" { + p.UserKey = p.ValidationKey } - if attrs, ok := c.Config["attributes"]; ok { - p.Attributes, err = rawToJSON(attrs) - if err != nil { - return nil, fmt.Errorf("Error parsing the attributes: %v", err) - } - } - - if attrs, ok := c.Config["attributes_json"]; ok { + if attrs, ok := c.Config["attributes_json"].(string); ok { var m map[string]interface{} - if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil { - return nil, fmt.Errorf("Error parsing the attributes: %v", err) + if err := json.Unmarshal([]byte(attrs), &m); err != nil { + return nil, fmt.Errorf("Error parsing attributes_json: %v", err) } - p.Attributes = m + p.attributes = m + } + + if vaults, ok := c.Config["vault_json"].(string); ok { + var m map[string]string + if err := json.Unmarshal([]byte(vaults), &m); err != nil { + return nil, fmt.Errorf("Error parsing vault_json: %v", err) + } + p.vaults = m } return p, nil } -func rawToJSON(raw interface{}) (interface{}, error) { - switch s := raw.(type) { - case []map[string]interface{}: - if len(s) != 1 { - return nil, errors.New("unexpected input while parsing raw config to JSON") - } - - var err error - for k, v := range s[0] { - s[0][k], err = rawToJSON(v) - if err != nil { - return nil, err - } - } - - return s[0], nil - default: - return s, nil +func (p *Provisioner) deployConfigFiles( + o terraform.UIOutput, + comm communicator.Communicator, + confDir string) error { + // Copy the user key to the new instance + pk := strings.NewReader(p.UserKey) + if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil { + return fmt.Errorf("Uploading user key failed: %v", err) } + + if p.SecretKey != "" { + // Copy the secret key to the new instance + s := strings.NewReader(p.SecretKey) + if err := comm.Upload(path.Join(confDir, secretKey), s); err != nil { + return fmt.Errorf("Uploading %s failed: %v", secretKey, err) + } + } + + // Make sure the SSLVerifyMode value is written as a symbol + if p.SSLVerifyMode != "" && !strings.HasPrefix(p.SSLVerifyMode, ":") { + p.SSLVerifyMode = fmt.Sprintf(":%s", p.SSLVerifyMode) + } + + // Make strings.Join available for use within the template + funcMap := template.FuncMap{ + "join": strings.Join, + } + + // Create a new template and parse the client config into it + t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf)) + + var buf bytes.Buffer + err := t.Execute(&buf, p) + if err != nil { + return fmt.Errorf("Error executing %s template: %s", clienrb, err) + } + + // Copy the client config to the new instance + if err := comm.Upload(path.Join(confDir, clienrb), &buf); err != nil { + return fmt.Errorf("Uploading %s failed: %v", clienrb, err) + } + + // Create a map with first boot settings + fb := make(map[string]interface{}) + if p.attributes != nil { + fb = p.attributes + } + + // Check if the run_list was also in the attributes and if so log a warning + // that it will be overwritten with the value of the run_list argument. + if _, found := fb["run_list"]; found { + log.Printf("[WARNING] Found a 'run_list' specified in the configured attributes! " + + "This value will be overwritten by the value of the `run_list` argument!") + } + + // Add the initial runlist to the first boot settings + if !p.UsePolicyfile { + fb["run_list"] = p.RunList + } + + // Marshal the first boot settings to JSON + d, err := json.Marshal(fb) + if err != nil { + return fmt.Errorf("Failed to create %s data: %s", firstBoot, err) + } + + // Copy the first-boot.json to the new instance + if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil { + return fmt.Errorf("Uploading %s failed: %v", firstBoot, err) + } + + return nil } -// retryFunc is used to retry a function for a given duration -func retryFunc(timeout time.Duration, f func() error) error { - finish := time.After(timeout) - for { - err := f() - if err == nil { - return nil - } - log.Printf("Retryable error: %v", err) - - select { - case <-finish: +func (p *Provisioner) deployOhaiHints( + o terraform.UIOutput, + comm communicator.Communicator, + hintDir string) error { + for _, hint := range p.OhaiHints { + // Open the hint file + f, err := os.Open(hint) + if err != nil { return err - case <-time.After(3 * time.Second): + } + defer f.Close() + + // Copy the hint to the new instance + if err := comm.Upload(path.Join(hintDir, path.Base(hint)), f); err != nil { + return fmt.Errorf("Uploading %s failed: %v", path.Base(hint), err) } } + + return nil } func (p *Provisioner) fetchChefCertificatesFunc( @@ -370,6 +468,99 @@ func (p *Provisioner) fetchChefCertificatesFunc( } } +func (p *Provisioner) generateClientKeyFunc( + knifeCmd string, + confDir string, + noOutput string) func(terraform.UIOutput, communicator.Communicator) error { + return func(o terraform.UIOutput, comm communicator.Communicator) error { + options := fmt.Sprintf("-c %s -u %s --key %s", + path.Join(confDir, clienrb), + p.UserName, + path.Join(confDir, p.UserName+".pem"), + ) + + // See if we already have a node object + getNodeCmd := fmt.Sprintf("%s node show %s %s %s", knifeCmd, p.NodeName, options, noOutput) + node := p.runCommand(o, comm, getNodeCmd) == nil + + // See if we already have a client object + getClientCmd := fmt.Sprintf("%s client show %s %s %s", knifeCmd, p.NodeName, options, noOutput) + client := p.runCommand(o, comm, getClientCmd) == nil + + // If we have a client, we can only continue if we are to recreate the client + if client && !p.RecreateClient { + return fmt.Errorf( + "Chef client %q already exists, set recreate_client=true to automatically recreate the client", p.NodeName) + } + + // If the node exists, try to delete it + if node { + deleteNodeCmd := fmt.Sprintf("%s node delete %s -y %s", + knifeCmd, + p.NodeName, + options, + ) + if err := p.runCommand(o, comm, deleteNodeCmd); err != nil { + return err + } + } + + // If the client exists, try to delete it + if client { + deleteClientCmd := fmt.Sprintf("%s client delete %s -y %s", + knifeCmd, + p.NodeName, + options, + ) + if err := p.runCommand(o, comm, deleteClientCmd); err != nil { + return err + } + } + + // Create the new client object + createClientCmd := fmt.Sprintf("%s client create %s -d -f %s %s", + knifeCmd, + p.NodeName, + path.Join(confDir, "client.pem"), + options, + ) + + return p.runCommand(o, comm, createClientCmd) + } +} + +func (p *Provisioner) configureVaultsFunc( + gemCmd string, + knifeCmd string, + confDir string) func(terraform.UIOutput, communicator.Communicator) error { + return func(o terraform.UIOutput, comm communicator.Communicator) error { + if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil { + return err + } + + options := fmt.Sprintf("-c %s -u %s --key %s", + path.Join(confDir, clienrb), + p.UserName, + 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 + } + } + + return nil + } +} + func (p *Provisioner) runChefClientFunc( chefCmd string, confDir string) func(terraform.UIOutput, communicator.Communicator) error { @@ -430,117 +621,11 @@ func (p *Provisioner) Output(output string) { } } -func (p *Provisioner) deployConfigFiles( - o terraform.UIOutput, - comm communicator.Communicator, - confDir string) error { - contents, _, err := pathorcontents.Read(p.ValidationKey) - if err != nil { - return err - } - f := strings.NewReader(contents) - - // Copy the validation key to the new instance - if err := comm.Upload(path.Join(confDir, validationKey), f); err != nil { - return fmt.Errorf("Uploading %s failed: %v", validationKey, err) - } - - if p.SecretKey != "" { - contents, _, err := pathorcontents.Read(p.SecretKey) - if err != nil { - return err - } - s := strings.NewReader(contents) - // Copy the secret key to the new instance - if err := comm.Upload(path.Join(confDir, secretKey), s); err != nil { - return fmt.Errorf("Uploading %s failed: %v", secretKey, err) - } - } - - // Make sure the SSLVerifyMode value is written as a symbol - if p.SSLVerifyMode != "" && !strings.HasPrefix(p.SSLVerifyMode, ":") { - p.SSLVerifyMode = fmt.Sprintf(":%s", p.SSLVerifyMode) - } - - // Make strings.Join available for use within the template - funcMap := template.FuncMap{ - "join": strings.Join, - } - - // Create a new template and parse the client config into it - t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf)) - - var buf bytes.Buffer - err = t.Execute(&buf, p) - if err != nil { - return fmt.Errorf("Error executing %s template: %s", clienrb, err) - } - - // Copy the client config to the new instance - if err := comm.Upload(path.Join(confDir, clienrb), &buf); err != nil { - return fmt.Errorf("Uploading %s failed: %v", clienrb, err) - } - - // Create a map with first boot settings - fb := make(map[string]interface{}) - if p.Attributes != nil { - fb = p.Attributes.(map[string]interface{}) - } - - // Check if the run_list was also in the attributes and if so log a warning - // that it will be overwritten with the value of the run_list argument. - if _, found := fb["run_list"]; found { - log.Printf("[WARNING] Found a 'run_list' specified in the configured attributes! " + - "This value will be overwritten by the value of the `run_list` argument!") - } - - // Add the initial runlist to the first boot settings - if !p.UsePolicyfile { - fb["run_list"] = p.RunList - } - - // Marshal the first boot settings to JSON - d, err := json.Marshal(fb) - if err != nil { - return fmt.Errorf("Failed to create %s data: %s", firstBoot, err) - } - - // Copy the first-boot.json to the new instance - if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil { - return fmt.Errorf("Uploading %s failed: %v", firstBoot, err) - } - - return nil -} - -func (p *Provisioner) deployOhaiHints( - o terraform.UIOutput, - comm communicator.Communicator, - hintDir string) error { - for _, hint := range p.OhaiHints { - // Open the hint file - f, err := os.Open(hint) - if err != nil { - return err - } - defer f.Close() - - // Copy the hint to the new instance - if err := comm.Upload(path.Join(hintDir, path.Base(hint)), f); err != nil { - return fmt.Errorf("Uploading %s failed: %v", path.Base(hint), err) - } - } - - return nil -} - // runCommand is used to run already prepared commands func (p *Provisioner) runCommand( o terraform.UIOutput, comm communicator.Communicator, command string) error { - var err error - // Unless prevented, prefix the command with sudo if p.useSudo { command = "sudo " + command @@ -559,7 +644,8 @@ func (p *Provisioner) runCommand( Stderr: errW, } - if err := comm.Start(cmd); err != nil { + err := comm.Start(cmd) + if err != nil { return fmt.Errorf("Error executing command %q: %v", cmd.Command, err) } @@ -575,12 +661,7 @@ func (p *Provisioner) runCommand( <-outDoneCh <-errDoneCh - // If we have an error, return it out now that we've cleaned up - if err != nil { - return err - } - - return nil + return err } func (p *Provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { @@ -590,3 +671,21 @@ func (p *Provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan< o.Output(line) } } + +// retryFunc is used to retry a function for a given duration +func retryFunc(timeout time.Duration, f func() error) error { + finish := time.After(timeout) + for { + err := f() + if err == nil { + return nil + } + log.Printf("Retryable error: %v", err) + + select { + case <-finish: + return err + case <-time.After(3 * time.Second): + } + } +} diff --git a/builtin/provisioners/chef/resource_provisioner_test.go b/builtin/provisioners/chef/resource_provisioner_test.go index d3f9684da..8d9603f98 100644 --- a/builtin/provisioners/chef/resource_provisioner_test.go +++ b/builtin/provisioners/chef/resource_provisioner_test.go @@ -16,12 +16,12 @@ func TestResourceProvisioner_impl(t *testing.T) { func TestResourceProvider_Validate_good(t *testing.T) { c := testConfig(t, map[string]interface{}{ - "environment": "_default", - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key": "contentsofsomevalidator.pem", + "environment": "_default", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }) r := new(ResourceProvisioner) warn, errs := r.Validate(c) @@ -65,11 +65,11 @@ func TestResourceProvider_runChefClient(t *testing.T) { }{ "Sudo": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), ChefCmd: linuxChefCmd, @@ -85,12 +85,12 @@ func TestResourceProvider_runChefClient(t *testing.T) { "NoSudo": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), ChefCmd: linuxChefCmd, @@ -106,13 +106,13 @@ func TestResourceProvider_runChefClient(t *testing.T) { "Environment": { Config: testConfig(t, map[string]interface{}{ - "environment": "production", - "node_name": "nodename1", - "prevent_sudo": true, - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "environment": "production", + "node_name": "nodename1", + "prevent_sudo": true, + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), ChefCmd: windowsChefCmd, @@ -162,8 +162,8 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) { "node_name": "nodename1", "run_list": []interface{}{"cookbook::recipe"}, "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "user_name": "bob", + "user_key": "USER-KEY", }), KnifeCmd: linuxKnifeCmd, @@ -184,8 +184,8 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) { "prevent_sudo": true, "run_list": []interface{}{"cookbook::recipe"}, "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "user_name": "bob", + "user_key": "USER-KEY", }), KnifeCmd: windowsKnifeCmd, diff --git a/builtin/provisioners/chef/test-fixtures/encrypted_data_bag_secret b/builtin/provisioners/chef/test-fixtures/encrypted_data_bag_secret deleted file mode 100644 index 97249fae5..000000000 --- a/builtin/provisioners/chef/test-fixtures/encrypted_data_bag_secret +++ /dev/null @@ -1 +0,0 @@ -SECRET-KEY-FILE diff --git a/builtin/provisioners/chef/test-fixtures/validator.pem b/builtin/provisioners/chef/test-fixtures/validator.pem deleted file mode 100644 index 03e6d6de7..000000000 --- a/builtin/provisioners/chef/test-fixtures/validator.pem +++ /dev/null @@ -1 +0,0 @@ -VALIDATOR-PEM-FILE diff --git a/builtin/provisioners/chef/windows_provisioner_test.go b/builtin/provisioners/chef/windows_provisioner_test.go index 18a9b44d9..659953d97 100644 --- a/builtin/provisioners/chef/windows_provisioner_test.go +++ b/builtin/provisioners/chef/windows_provisioner_test.go @@ -17,11 +17,11 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) { }{ "Default": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -35,13 +35,13 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) { "Proxy": { Config: testConfig(t, map[string]interface{}{ - "http_proxy": "http://proxy.local", - "no_proxy": []interface{}{"http://local.local", "http://local.org"}, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", + "http_proxy": "http://proxy.local", + "no_proxy": []interface{}{"http://local.local", "http://local.org"}, + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -55,12 +55,12 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) { "Version": { Config: testConfig(t, map[string]interface{}{ - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "validator.pem", - "version": "11.18.6", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", + "version": "11.18.6", }), Commands: map[string]bool{ @@ -103,13 +103,13 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { }{ "Default": { Config: testConfig(t, map[string]interface{}{ - "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -121,25 +121,25 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ windowsConfDir + "/client.rb": defaultWindowsClientConf, - windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", + windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", windowsConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, windowsConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE", - windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", + windowsConfDir + "/bob.pem": "USER-KEY", }, }, "Proxy": { Config: testConfig(t, map[string]interface{}{ - "http_proxy": "http://proxy.local", - "https_proxy": "https://proxy.local", - "no_proxy": []interface{}{"http://local.local", "https://local.local"}, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "ssl_verify_mode": "verify_none", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "http_proxy": "http://proxy.local", + "https_proxy": "https://proxy.local", + "no_proxy": []interface{}{"http://local.local", "https://local.local"}, + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "ssl_verify_mode": "verify_none", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -149,52 +149,8 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ windowsConfDir + "/client.rb": proxyWindowsClientConf, windowsConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, - windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", - windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", - }, - }, - - "Attributes": { - Config: testConfig(t, map[string]interface{}{ - "attributes": []map[string]interface{}{ - map[string]interface{}{ - "key1": []map[string]interface{}{ - map[string]interface{}{ - "subkey1": []map[string]interface{}{ - map[string]interface{}{ - "subkey2a": []interface{}{ - "val1", "val2", "val3", - }, - "subkey2b": []map[string]interface{}{ - map[string]interface{}{ - "subkey3": "value3", - }, - }, - }, - }, - }, - }, - "key2": "value2", - }, - }, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", - }), - - Commands: map[string]bool{ - fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true, - }, - - Uploads: map[string]string{ - windowsConfDir + "/client.rb": defaultWindowsClientConf, - windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", - windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", - windowsConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + - `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, + windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", + windowsConfDir + "/bob.pem": "USER-KEY", }, }, @@ -202,12 +158,12 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { Config: testConfig(t, map[string]interface{}{ "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, - "node_name": "nodename1", - "run_list": []interface{}{"cookbook::recipe"}, - "secret_key_path": "test-fixtures/encrypted_data_bag_secret", - "server_url": "https://chef.local", - "validation_client_name": "validator", - "validation_key_path": "test-fixtures/validator.pem", + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "secret_key": "SECRET-KEY", + "server_url": "https://chef.local", + "user_name": "bob", + "user_key": "USER-KEY", }), Commands: map[string]bool{ @@ -216,8 +172,8 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { Uploads: map[string]string{ windowsConfDir + "/client.rb": defaultWindowsClientConf, - windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", - windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", + windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY", + windowsConfDir + "/bob.pem": "USER-KEY", windowsConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, }, @@ -358,13 +314,11 @@ Start-Process -FilePath msiexec -ArgumentList /qn, /i, $dest -Wait ` const defaultWindowsClientConf = `log_location STDOUT -chef_server_url "https://chef.local" -validation_client_name "validator" +chef_server_url "https://chef.local/" node_name "nodename1"` const proxyWindowsClientConf = `log_location STDOUT -chef_server_url "https://chef.local" -validation_client_name "validator" +chef_server_url "https://chef.local/" node_name "nodename1" http_proxy "http://proxy.local" diff --git a/communicator/ssh/communicator_test.go b/communicator/ssh/communicator_test.go index 4a9e816f3..c1ed00e2e 100644 --- a/communicator/ssh/communicator_test.go +++ b/communicator/ssh/communicator_test.go @@ -5,9 +5,7 @@ package ssh import ( "bytes" "fmt" - "io/ioutil" "net" - "os" "regexp" "strings" "testing" @@ -167,48 +165,6 @@ func TestStart(t *testing.T) { } } -func TestStart_KeyFile(t *testing.T) { - address := newMockLineServer(t) - parts := strings.Split(address, ":") - - keyFile, err := ioutil.TempFile("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - keyFilePath := keyFile.Name() - keyFile.Write([]byte(testClientPrivateKey)) - keyFile.Close() - defer os.Remove(keyFilePath) - - r := &terraform.InstanceState{ - Ephemeral: terraform.EphemeralState{ - ConnInfo: map[string]string{ - "type": "ssh", - "user": "user", - "key_file": keyFilePath, - "host": parts[0], - "port": parts[1], - "timeout": "30s", - }, - }, - } - - c, err := New(r) - if err != nil { - t.Fatalf("error creating communicator: %s", err) - } - - var cmd remote.Cmd - stdout := new(bytes.Buffer) - cmd.Command = "echo foo" - cmd.Stdout = stdout - - err = c.Start(&cmd) - if err != nil { - t.Fatalf("error executing remote command: %s", err) - } -} - func TestScriptPath(t *testing.T) { cases := []struct { Input string diff --git a/communicator/ssh/provisioner.go b/communicator/ssh/provisioner.go index 07d50a051..9d0a6b687 100644 --- a/communicator/ssh/provisioner.go +++ b/communicator/ssh/provisioner.go @@ -9,7 +9,6 @@ import ( "time" "github.com/hashicorp/terraform/communicator/shared" - "github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/mapstructure" "github.com/xanzy/ssh-agent" @@ -225,14 +224,9 @@ func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) { } func readPrivateKey(pk string) (ssh.AuthMethod, error) { - key, _, err := pathorcontents.Read(pk) - if err != nil { - return nil, fmt.Errorf("Failed to read private key %q: %s", pk, err) - } - // We parse the private key on our own first so that we can // show a nicer error if the private key has a password. - block, _ := pem.Decode([]byte(key)) + block, _ := pem.Decode([]byte(pk)) if block == nil { return nil, fmt.Errorf("Failed to read key %q: no key found", pk) } @@ -242,7 +236,7 @@ func readPrivateKey(pk string) (ssh.AuthMethod, error) { "not supported. Please decrypt the key prior to use.", pk) } - signer, err := ssh.ParsePrivateKey([]byte(key)) + signer, err := ssh.ParsePrivateKey([]byte(pk)) if err != nil { return nil, fmt.Errorf("Failed to parse key file %q: %s", pk, err) } diff --git a/website/source/docs/provisioners/chef.html.markdown b/website/source/docs/provisioners/chef.html.markdown index a32cdd2ba..f84b7c366 100644 --- a/website/source/docs/provisioners/chef.html.markdown +++ b/website/source/docs/provisioners/chef.html.markdown @@ -43,8 +43,9 @@ resource "aws_instance" "web" { node_name = "webserver1" secret_key = "${file("../encrypted_data_bag_secret")}" server_url = "https://chef.company.com/organizations/org1" - validation_client_name = "chef-validator" - validation_key = "${file("../chef-validator.pem")}" + receate_client = true + user_name = "bob" + user_key = "${file("../bob.pem")}" version = "12.4.1" } } @@ -95,13 +96,16 @@ The following arguments are supported: and running the initial Chef Client run. This option is only used with `ssh` type [connections](/docs/provisioners/connection.html). +* `recreate_client (boolean)` - (Optional) If true, first delete the existing Chef Node and + Client before registering the new Chef Client. + * `run_list (array)` - (Required) A list with recipes that will be invoked during the initial Chef Client run. The run-list will also be saved to the Chef Server after a successful initial run. * `secret_key (string)` - (Optional) The contents of the secret key that is used by the client to decrypt data bags on the Chef Server. The key will be uploaded to the remote - machine. These can be loaded from a file on disk using the [`file()` interpolation + machine. This can be loaded from a file on disk using the [`file()` interpolation function](/docs/configuration/interpolation.html#file_path_). * `server_url (string)` - (Required) The URL to the Chef server. This includes the path to @@ -114,20 +118,22 @@ The following arguments are supported: * `ssl_verify_mode (string)` - (Optional) Use to set the verify mode for Chef Client HTTPS requests. -* `validation_client_name (string)` - (Required) The name of the validation client to use - for the initial communication with the Chef Server. +* `user_name (string)` - (Required) The name of an existing Chef user to use for registering + the new Chef Client and (optionally) configure Chef Vaults. -* `validation_key (string)` - (Required) The contents of the validation key that is needed - by the node to register itself with the Chef Server. The key will be uploaded to the remote - machine. These can be loaded from a file on disk using the [`file()` +* `user_key (string)` - (Required) The contents of the user key that will be used to + authenticate with the Chef Server. This can be loaded from a file on disk using the [`file()` interpolation function](/docs/configuration/interpolation.html#file_path_). +* `vault_json (string)` - (Optional) A raw JSON string with Chef Vaults and Items to give + the new node access to. These can also be loaded from a file on disk using the [`file() + ` interpolation function](/docs/configuration/interpolation.html#file_path_). + * `version (string)` - (Optional) The Chef Client version to install on the remote machine. If not set the latest available version will be installed. These are supported for backwards compatibility and may be removed in a future version: -* `attributes (map)` - __Deprecated: please use `attributes_json` instead__. -* `secret_key_path (string)` - __Deprecated: please use `secret_key` instead__. -* `validation_key_path (string)` - __Deprecated: please use `validation_key` instead__. +* `validation_client_name (string)` - __Deprecated: please use `user_name` instead__. +* `validation_key (string)` - __Deprecated: please use `user_key` instead__.