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.
This commit is contained in:
Sander van Harmelen 2016-09-15 14:20:18 +02:00 committed by GitHub
parent 0f6098c4ed
commit 968472a63e
10 changed files with 496 additions and 542 deletions

View File

@ -5,7 +5,6 @@ import (
"log" "log"
"strings" "strings"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack" "github.com/xanzy/go-cloudstack/cloudstack"
) )
@ -56,19 +55,14 @@ func resourceCloudStackSSHKeyPairCreate(d *schema.ResourceData, meta interface{}
if publicKey != "" { if publicKey != "" {
// Register supplied key // Register supplied key
key, _, err := pathorcontents.Read(publicKey) p := cs.SSH.NewRegisterSSHKeyPairParams(name, publicKey)
if err != nil {
return fmt.Errorf("Error reading the public key: %v", err)
}
p := cs.SSH.NewRegisterSSHKeyPairParams(name, string(key))
// If there is a project supplied, we retrieve and set the project id // If there is a project supplied, we retrieve and set the project id
if err := setProjectid(p, cs, d); err != nil { if err := setProjectid(p, cs, d); err != nil {
return err return err
} }
_, err = cs.SSH.RegisterSSHKeyPair(p) _, err := cs.SSH.RegisterSSHKeyPair(p)
if err != nil { if err != nil {
return err return err
} }

View File

@ -16,11 +16,11 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -32,13 +32,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "secret_key": "SECRET-KEY",
"validation_client_name": "validator", "server_url": "https://chef.local",
"validation_key_path": "validator.pem", "user_name": "bob",
"secret_key_path": "encrypted_data_bag_secret", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -50,13 +50,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"HTTPProxy": { "HTTPProxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -68,13 +68,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"HTTPSProxy": { "HTTPSProxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -86,14 +86,14 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"NoProxy": { "NoProxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"no_proxy": []interface{}{"http://local.local", "http://local.org"}, "no_proxy": []interface{}{"http://local.local", "http://local.org"},
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -108,13 +108,13 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"Version": { "Version": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
"version": "11.18.6", "version": "11.18.6",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -154,13 +154,13 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -180,22 +180,22 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
linuxConfDir + "/client.rb": defaultLinuxClientConf, 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 + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`,
linuxConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE", linuxConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE",
linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", linuxConfDir + "/bob.pem": "USER-KEY",
}, },
}, },
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -204,25 +204,25 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
linuxConfDir + "/client.rb": defaultLinuxClientConf, 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 + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`,
linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", linuxConfDir + "/bob.pem": "USER-KEY",
}, },
}, },
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"no_proxy": []interface{}{"http://local.local", "https://local.local"}, "no_proxy": []interface{}{"http://local.local", "https://local.local"},
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"ssl_verify_mode": "verify_none", "ssl_verify_mode": "verify_none",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -231,54 +231,9 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
linuxConfDir + "/client.rb": proxyLinuxClientConf, 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 + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`,
linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", linuxConfDir + "/bob.pem": "USER-KEY",
},
},
"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"]}`,
}, },
}, },
@ -286,13 +241,13 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`,
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -301,8 +256,8 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
linuxConfDir + "/client.rb": defaultLinuxClientConf, linuxConfDir + "/client.rb": defaultLinuxClientConf,
linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", linuxConfDir + "/encrypted_data_bag_secret": "SECRET-KEY",
linuxConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", linuxConfDir + "/bob.pem": "USER-KEY",
linuxConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + linuxConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`,
}, },
@ -332,13 +287,11 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
} }
const defaultLinuxClientConf = `log_location STDOUT const defaultLinuxClientConf = `log_location STDOUT
chef_server_url "https://chef.local" chef_server_url "https://chef.local/"
validation_client_name "validator"
node_name "nodename1"` node_name "nodename1"`
const proxyLinuxClientConf = `log_location STDOUT const proxyLinuxClientConf = `log_location STDOUT
chef_server_url "https://chef.local" chef_server_url "https://chef.local/"
validation_client_name "validator"
node_name "nodename1" node_name "nodename1"
http_proxy "http://proxy.local" http_proxy "http://proxy.local"

View File

@ -16,7 +16,6 @@ import (
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/communicator/remote"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/mitchellh/go-linereader" "github.com/mitchellh/go-linereader"
@ -29,19 +28,21 @@ const (
firstBoot = "first-boot.json" firstBoot = "first-boot.json"
logfileDir = "logfiles" logfileDir = "logfiles"
linuxChefCmd = "chef-client" linuxChefCmd = "chef-client"
linuxKnifeCmd = "knife"
linuxConfDir = "/etc/chef" linuxConfDir = "/etc/chef"
linuxNoOutput = "> /dev/null 2>&1"
linuxGemCmd = "/opt/chef/embedded/bin/gem"
linuxKnifeCmd = "knife"
secretKey = "encrypted_data_bag_secret" secretKey = "encrypted_data_bag_secret"
validationKey = "validation.pem"
windowsChefCmd = "cmd /c chef-client" windowsChefCmd = "cmd /c chef-client"
windowsKnifeCmd = "cmd /c knife"
windowsConfDir = "C:/chef" windowsConfDir = "C:/chef"
windowsNoOutput = "> nul 2>&1"
windowsGemCmd = "C:/opscode/chef/embedded/bin/gem"
windowsKnifeCmd = "cmd /c knife"
) )
const clientConf = ` const clientConf = `
log_location STDOUT log_location STDOUT
chef_server_url "{{ .ServerURL }}" chef_server_url "{{ .ServerURL }}"
validation_client_name "{{ .ValidationClientName }}"
node_name "{{ .NodeName }}" node_name "{{ .NodeName }}"
{{ if .UsePolicyfile }} {{ if .UsePolicyfile }}
use_policyfile true use_policyfile true
@ -79,43 +80,50 @@ enable_reporting false
{{ end }} {{ end }}
` `
// Provisioner represents a specificly configured chef provisioner // Provisioner represents a Chef provisioner
type Provisioner struct { type Provisioner struct {
Attributes interface{} `mapstructure:"attributes"` AttributesJSON string `mapstructure:"attributes_json"`
AttributesJSON string `mapstructure:"attributes_json"` ClientOptions []string `mapstructure:"client_options"`
ClientOptions []string `mapstructure:"client_options"` DisableReporting bool `mapstructure:"disable_reporting"`
DisableReporting bool `mapstructure:"disable_reporting"` Environment string `mapstructure:"environment"`
Environment string `mapstructure:"environment"` FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"`
FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"` LogToFile bool `mapstructure:"log_to_file"`
LogToFile bool `mapstructure:"log_to_file"` UsePolicyfile bool `mapstructure:"use_policyfile"`
UsePolicyfile bool `mapstructure:"use_policyfile"` PolicyGroup string `mapstructure:"policy_group"`
PolicyGroup string `mapstructure:"policy_group"` PolicyName string `mapstructure:"policy_name"`
PolicyName string `mapstructure:"policy_name"` HTTPProxy string `mapstructure:"http_proxy"`
HTTPProxy string `mapstructure:"http_proxy"` HTTPSProxy string `mapstructure:"https_proxy"`
HTTPSProxy string `mapstructure:"https_proxy"` NOProxy []string `mapstructure:"no_proxy"`
NOProxy []string `mapstructure:"no_proxy"` NodeName string `mapstructure:"node_name"`
NodeName string `mapstructure:"node_name"` OhaiHints []string `mapstructure:"ohai_hints"`
OhaiHints []string `mapstructure:"ohai_hints"` OSType string `mapstructure:"os_type"`
OSType string `mapstructure:"os_type"` RecreateClient bool `mapstructure:"recreate_client"`
PreventSudo bool `mapstructure:"prevent_sudo"` PreventSudo bool `mapstructure:"prevent_sudo"`
RunList []string `mapstructure:"run_list"` RunList []string `mapstructure:"run_list"`
SecretKey string `mapstructure:"secret_key"` SecretKey string `mapstructure:"secret_key"`
ServerURL string `mapstructure:"server_url"` ServerURL string `mapstructure:"server_url"`
SkipInstall bool `mapstructure:"skip_install"` SkipInstall bool `mapstructure:"skip_install"`
SSLVerifyMode string `mapstructure:"ssl_verify_mode"` SSLVerifyMode string `mapstructure:"ssl_verify_mode"`
ValidationClientName string `mapstructure:"validation_client_name"` UserName string `mapstructure:"user_name"`
ValidationKey string `mapstructure:"validation_key"` UserKey string `mapstructure:"user_key"`
Version string `mapstructure:"version"` 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 createConfigFiles func(terraform.UIOutput, communicator.Communicator) error
installChefClient func(terraform.UIOutput, communicator.Communicator) error
fetchChefCertificates 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 runChefClient func(terraform.UIOutput, communicator.Communicator) error
useSudo bool useSudo bool
// Deprecated Fields // Deprecated Fields
SecretKeyPath string `mapstructure:"secret_key_path"` ValidationClientName string `mapstructure:"validation_client_name"`
ValidationKeyPath string `mapstructure:"validation_key_path"` ValidationKey string `mapstructure:"validation_key"`
} }
// ResourceProvisioner represents a generic chef provisioner // ResourceProvisioner represents a generic chef provisioner
@ -146,15 +154,21 @@ func (r *ResourceProvisioner) Apply(
// Set some values based on the targeted OS // Set some values based on the targeted OS
switch p.OSType { switch p.OSType {
case "linux": case "linux":
p.installChefClient = p.linuxInstallChefClient p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem"))
p.createConfigFiles = p.linuxCreateConfigFiles p.createConfigFiles = p.linuxCreateConfigFiles
p.installChefClient = p.linuxInstallChefClient
p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir) 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.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir)
p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root"
case "windows": 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.createConfigFiles = p.windowsCreateConfigFiles
p.installChefClient = p.windowsInstallChefClient
p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir) 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.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir)
p.useSudo = false p.useSudo = false
default: default:
@ -177,6 +191,14 @@ func (r *ResourceProvisioner) Apply(
} }
defer comm.Disconnect() 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 !p.SkipInstall {
if err := p.installChefClient(o, comm); err != nil { if err := p.installChefClient(o, comm); err != nil {
return err 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...") o.Output("Starting initial Chef-Client run...")
if err := p.runChefClient(o, comm); err != nil { if err := p.runChefClient(o, comm); err != nil {
return err return err
@ -212,38 +246,42 @@ func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string
} }
if p.NodeName == "" { 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 { 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 == "" { if p.ServerURL == "" {
es = append(es, fmt.Errorf("Key not found: server_url")) es = append(es, errors.New("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"))
} }
if p.UsePolicyfile && p.PolicyName == "" { 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 == "" { 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 != "" { if p.UserName == "" && p.ValidationClientName == "" {
ws = append(ws, "validation_key_path is deprecated, please use "+ es = append(es, errors.New(
"validation_key instead and load the key contents via file()") "One of user_name or the deprecated validation_client_name must be provided"))
} }
if p.SecretKeyPath != "" { if p.UserKey == "" && p.ValidationKey == "" {
ws = append(ws, "secret_key_path is deprecated, please use "+ es = append(es, errors.New(
"secret_key instead and load the key contents via file()") "One of user_key or the deprecated validation_key must be provided"))
} }
if _, ok := c.Config["attributes"]; ok { if p.ValidationClientName != "" {
ws = append(ws, "using map style attribute values is deprecated, "+ ws = append(ws, "validation_client_name is deprecated, please use user_name instead")
" please use a single raw JSON string 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 return ws, es
@ -282,6 +320,9 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis
return nil, err return nil, err
} }
// Make sure the supplied URL has a trailing slash
p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/"
if p.Environment == "" { if p.Environment == "" {
p.Environment = defaultEnv p.Environment = defaultEnv
} }
@ -294,69 +335,126 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis
p.OhaiHints[i] = hintPath p.OhaiHints[i] = hintPath
} }
if p.ValidationKey == "" && p.ValidationKeyPath != "" { if p.UserName == "" && p.ValidationClientName != "" {
p.ValidationKey = p.ValidationKeyPath p.UserName = p.ValidationClientName
} }
if p.SecretKey == "" && p.SecretKeyPath != "" { if p.UserKey == "" && p.ValidationKey != "" {
p.SecretKey = p.SecretKeyPath p.UserKey = p.ValidationKey
} }
if attrs, ok := c.Config["attributes"]; ok { if attrs, ok := c.Config["attributes_json"].(string); 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 {
var m map[string]interface{} var m map[string]interface{}
if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil { if err := json.Unmarshal([]byte(attrs), &m); err != nil {
return nil, fmt.Errorf("Error parsing the attributes: %v", err) 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 return p, nil
} }
func rawToJSON(raw interface{}) (interface{}, error) { func (p *Provisioner) deployConfigFiles(
switch s := raw.(type) { o terraform.UIOutput,
case []map[string]interface{}: comm communicator.Communicator,
if len(s) != 1 { confDir string) error {
return nil, errors.New("unexpected input while parsing raw config to JSON") // 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 {
var err error return fmt.Errorf("Uploading user key failed: %v", err)
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
} }
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 (p *Provisioner) deployOhaiHints(
func retryFunc(timeout time.Duration, f func() error) error { o terraform.UIOutput,
finish := time.After(timeout) comm communicator.Communicator,
for { hintDir string) error {
err := f() for _, hint := range p.OhaiHints {
if err == nil { // Open the hint file
return nil f, err := os.Open(hint)
} if err != nil {
log.Printf("Retryable error: %v", err)
select {
case <-finish:
return err 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( 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( func (p *Provisioner) runChefClientFunc(
chefCmd string, chefCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error { 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 // runCommand is used to run already prepared commands
func (p *Provisioner) runCommand( func (p *Provisioner) runCommand(
o terraform.UIOutput, o terraform.UIOutput,
comm communicator.Communicator, comm communicator.Communicator,
command string) error { command string) error {
var err error
// Unless prevented, prefix the command with sudo // Unless prevented, prefix the command with sudo
if p.useSudo { if p.useSudo {
command = "sudo " + command command = "sudo " + command
@ -559,7 +644,8 @@ func (p *Provisioner) runCommand(
Stderr: errW, 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) return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
} }
@ -575,12 +661,7 @@ func (p *Provisioner) runCommand(
<-outDoneCh <-outDoneCh
<-errDoneCh <-errDoneCh
// If we have an error, return it out now that we've cleaned up return err
if err != nil {
return err
}
return nil
} }
func (p *Provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { 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) 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):
}
}
}

View File

@ -16,12 +16,12 @@ func TestResourceProvisioner_impl(t *testing.T) {
func TestResourceProvider_Validate_good(t *testing.T) { func TestResourceProvider_Validate_good(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"environment": "_default", "environment": "_default",
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key": "contentsofsomevalidator.pem", "user_key": "USER-KEY",
}) })
r := new(ResourceProvisioner) r := new(ResourceProvisioner)
warn, errs := r.Validate(c) warn, errs := r.Validate(c)
@ -65,11 +65,11 @@ func TestResourceProvider_runChefClient(t *testing.T) {
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
ChefCmd: linuxChefCmd, ChefCmd: linuxChefCmd,
@ -85,12 +85,12 @@ func TestResourceProvider_runChefClient(t *testing.T) {
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
ChefCmd: linuxChefCmd, ChefCmd: linuxChefCmd,
@ -106,13 +106,13 @@ func TestResourceProvider_runChefClient(t *testing.T) {
"Environment": { "Environment": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"environment": "production", "environment": "production",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
ChefCmd: windowsChefCmd, ChefCmd: windowsChefCmd,
@ -162,8 +162,8 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
KnifeCmd: linuxKnifeCmd, KnifeCmd: linuxKnifeCmd,
@ -184,8 +184,8 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
KnifeCmd: windowsKnifeCmd, KnifeCmd: windowsKnifeCmd,

View File

@ -1 +0,0 @@
SECRET-KEY-FILE

View File

@ -1 +0,0 @@
VALIDATOR-PEM-FILE

View File

@ -17,11 +17,11 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
}{ }{
"Default": { "Default": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -35,13 +35,13 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"no_proxy": []interface{}{"http://local.local", "http://local.org"}, "no_proxy": []interface{}{"http://local.local", "http://local.org"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -55,12 +55,12 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
"Version": { "Version": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "validator.pem", "user_key": "USER-KEY",
"version": "11.18.6", "version": "11.18.6",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -103,13 +103,13 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
}{ }{
"Default": { "Default": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -121,25 +121,25 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
windowsConfDir + "/client.rb": defaultWindowsClientConf, 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 + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`,
windowsConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE", windowsConfDir + "/ohai/hints/ohaihint.json": "OHAI-HINT-FILE",
windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", windowsConfDir + "/bob.pem": "USER-KEY",
}, },
}, },
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"no_proxy": []interface{}{"http://local.local", "https://local.local"}, "no_proxy": []interface{}{"http://local.local", "https://local.local"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"ssl_verify_mode": "verify_none", "ssl_verify_mode": "verify_none",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -149,52 +149,8 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
windowsConfDir + "/client.rb": proxyWindowsClientConf, windowsConfDir + "/client.rb": proxyWindowsClientConf,
windowsConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`, windowsConfDir + "/first-boot.json": `{"run_list":["cookbook::recipe"]}`,
windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY",
windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", windowsConfDir + "/bob.pem": "USER-KEY",
},
},
"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"]}`,
}, },
}, },
@ -202,12 +158,12 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
Config: testConfig(t, map[string]interface{}{ Config: testConfig(t, map[string]interface{}{
"attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`,
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"secret_key_path": "test-fixtures/encrypted_data_bag_secret", "secret_key": "SECRET-KEY",
"server_url": "https://chef.local", "server_url": "https://chef.local",
"validation_client_name": "validator", "user_name": "bob",
"validation_key_path": "test-fixtures/validator.pem", "user_key": "USER-KEY",
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
@ -216,8 +172,8 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
Uploads: map[string]string{ Uploads: map[string]string{
windowsConfDir + "/client.rb": defaultWindowsClientConf, windowsConfDir + "/client.rb": defaultWindowsClientConf,
windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY-FILE", windowsConfDir + "/encrypted_data_bag_secret": "SECRET-KEY",
windowsConfDir + "/validation.pem": "VALIDATOR-PEM-FILE", windowsConfDir + "/bob.pem": "USER-KEY",
windowsConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + windowsConfDir + "/first-boot.json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, `"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 const defaultWindowsClientConf = `log_location STDOUT
chef_server_url "https://chef.local" chef_server_url "https://chef.local/"
validation_client_name "validator"
node_name "nodename1"` node_name "nodename1"`
const proxyWindowsClientConf = `log_location STDOUT const proxyWindowsClientConf = `log_location STDOUT
chef_server_url "https://chef.local" chef_server_url "https://chef.local/"
validation_client_name "validator"
node_name "nodename1" node_name "nodename1"
http_proxy "http://proxy.local" http_proxy "http://proxy.local"

View File

@ -5,9 +5,7 @@ package ssh
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os"
"regexp" "regexp"
"strings" "strings"
"testing" "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) { func TestScriptPath(t *testing.T) {
cases := []struct { cases := []struct {
Input string Input string

View File

@ -9,7 +9,6 @@ import (
"time" "time"
"github.com/hashicorp/terraform/communicator/shared" "github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/xanzy/ssh-agent" "github.com/xanzy/ssh-agent"
@ -225,14 +224,9 @@ func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
} }
func readPrivateKey(pk string) (ssh.AuthMethod, 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 // We parse the private key on our own first so that we can
// show a nicer error if the private key has a password. // show a nicer error if the private key has a password.
block, _ := pem.Decode([]byte(key)) block, _ := pem.Decode([]byte(pk))
if block == nil { if block == nil {
return nil, fmt.Errorf("Failed to read key %q: no key found", pk) 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) "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 { if err != nil {
return nil, fmt.Errorf("Failed to parse key file %q: %s", pk, err) return nil, fmt.Errorf("Failed to parse key file %q: %s", pk, err)
} }

View File

@ -43,8 +43,9 @@ resource "aws_instance" "web" {
node_name = "webserver1" node_name = "webserver1"
secret_key = "${file("../encrypted_data_bag_secret")}" secret_key = "${file("../encrypted_data_bag_secret")}"
server_url = "https://chef.company.com/organizations/org1" server_url = "https://chef.company.com/organizations/org1"
validation_client_name = "chef-validator" receate_client = true
validation_key = "${file("../chef-validator.pem")}" user_name = "bob"
user_key = "${file("../bob.pem")}"
version = "12.4.1" 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 and running the initial Chef Client run. This option is only used with `ssh` type
[connections](/docs/provisioners/connection.html). [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 * `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 Chef Client run. The run-list will also be saved to the Chef Server after a successful
initial run. initial run.
* `secret_key (string)` - (Optional) The contents of the secret key that is used * `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 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_). function](/docs/configuration/interpolation.html#file_path_).
* `server_url (string)` - (Required) The URL to the Chef server. This includes the path to * `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 * `ssl_verify_mode (string)` - (Optional) Use to set the verify mode for Chef Client HTTPS
requests. requests.
* `validation_client_name (string)` - (Required) The name of the validation client to use * `user_name (string)` - (Required) The name of an existing Chef user to use for registering
for the initial communication with the Chef Server. the new Chef Client and (optionally) configure Chef Vaults.
* `validation_key (string)` - (Required) The contents of the validation key that is needed * `user_key (string)` - (Required) The contents of the user key that will be used to
by the node to register itself with the Chef Server. The key will be uploaded to the remote authenticate with the Chef Server. This can be loaded from a file on disk using the [`file()`
machine. These can be loaded from a file on disk using the [`file()`
interpolation function](/docs/configuration/interpolation.html#file_path_). 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. * `version (string)` - (Optional) The Chef Client version to install on the remote machine.
If not set the latest available version will be installed. If not set the latest available version will be installed.
These are supported for backwards compatibility and may be removed in a These are supported for backwards compatibility and may be removed in a
future version: future version:
* `attributes (map)` - __Deprecated: please use `attributes_json` instead__. * `validation_client_name (string)` - __Deprecated: please use `user_name` instead__.
* `secret_key_path (string)` - __Deprecated: please use `secret_key` instead__. * `validation_key (string)` - __Deprecated: please use `user_key` instead__.
* `validation_key_path (string)` - __Deprecated: please use `validation_key` instead__.