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