Support recreating clients and configuring Chef Vaults (#8577)

Fixes #3605 and adds the functionality suggested in PR #7440.

This PR is using a different appraoch that (IMHO) feels cleaner and (even more important) adds support for Windows at the same time.
This commit is contained in:
Sander van Harmelen 2016-09-15 14:20:18 +02:00 committed by GitHub
parent 0f6098c4ed
commit 968472a63e
10 changed files with 496 additions and 542 deletions

View File

@ -5,7 +5,6 @@ import (
"log"
"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
}

View File

@ -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"

View File

@ -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):
}
}
}

View File

@ -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,

View File

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

View File

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

View File

@ -17,11 +17,11 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
}{
"Default": {
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"

View File

@ -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

View File

@ -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)
}

View File

@ -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__.