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:
parent
0f6098c4ed
commit
968472a63e
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
SECRET-KEY-FILE
|
|
@ -1 +0,0 @@
|
|||
VALIDATOR-PEM-FILE
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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__.
|
||||
|
|
Loading…
Reference in New Issue