diff --git a/builtin/provisioners/chef/linux_provisioner_test.go b/builtin/provisioners/chef/linux_provisioner_test.go index 89fae2b3f..ec72f7deb 100644 --- a/builtin/provisioners/chef/linux_provisioner_test.go +++ b/builtin/provisioners/chef/linux_provisioner_test.go @@ -280,6 +280,32 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, }, }, + + "Attributes JSON": { + 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", + }), + + 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"]}`, + }, + }, } r := new(ResourceProvisioner) diff --git a/builtin/provisioners/chef/resource_provisioner.go b/builtin/provisioners/chef/resource_provisioner.go index 34c6a5f1f..085603948 100644 --- a/builtin/provisioners/chef/resource_provisioner.go +++ b/builtin/provisioners/chef/resource_provisioner.go @@ -24,16 +24,18 @@ import ( ) const ( - clienrb = "client.rb" - defaultEnv = "_default" - firstBoot = "first-boot.json" - logfileDir = "logfiles" - linuxChefCmd = "chef-client" - linuxConfDir = "/etc/chef" - secretKey = "encrypted_data_bag_secret" - validationKey = "validation.pem" - windowsChefCmd = "cmd /c chef-client" - windowsConfDir = "C:/chef" + clienrb = "client.rb" + defaultEnv = "_default" + firstBoot = "first-boot.json" + logfileDir = "logfiles" + linuxChefCmd = "chef-client" + linuxKnifeCmd = "knife" + linuxConfDir = "/etc/chef" + secretKey = "encrypted_data_bag_secret" + validationKey = "validation.pem" + windowsChefCmd = "cmd /c chef-client" + windowsKnifeCmd = "cmd /c knife" + windowsConfDir = "C:/chef" ) const clientConf = ` @@ -74,34 +76,37 @@ ENV['no_proxy'] = "{{ join .NOProxy "," }}" // Provisioner represents a specificly configured chef provisioner type Provisioner struct { - Attributes interface{} `mapstructure:"attributes"` - ClientOptions []string `mapstructure:"client_options"` - DisableReporting bool `mapstructure:"disable_reporting"` - Environment string `mapstructure:"environment"` - 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"` + 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"` - installChefClient func(terraform.UIOutput, communicator.Communicator) error - createConfigFiles func(terraform.UIOutput, communicator.Communicator) error - runChefClient func(terraform.UIOutput, communicator.Communicator) error - useSudo bool + installChefClient func(terraform.UIOutput, communicator.Communicator) error + createConfigFiles func(terraform.UIOutput, communicator.Communicator) error + fetchChefCertificates func(terraform.UIOutput, communicator.Communicator) error + runChefClient func(terraform.UIOutput, communicator.Communicator) error + useSudo bool // Deprecated Fields SecretKeyPath string `mapstructure:"secret_key_path"` @@ -138,11 +143,13 @@ func (r *ResourceProvisioner) Apply( case "linux": p.installChefClient = p.linuxInstallChefClient p.createConfigFiles = p.linuxCreateConfigFiles + p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxChefCmd, linuxConfDir) p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" case "windows": p.installChefClient = p.windowsInstallChefClient p.createConfigFiles = p.windowsCreateConfigFiles + p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsChefCmd, windowsConfDir) p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) p.useSudo = false default: @@ -176,6 +183,13 @@ func (r *ResourceProvisioner) Apply( return err } + if p.FetchChefCertificates { + o.Output("Fetch Chef certificates...") + if err := p.fetchChefCertificates(o, comm); err != nil { + return err + } + } + o.Output("Starting initial Chef-Client run...") if err := p.runChefClient(o, comm); err != nil { return err @@ -222,6 +236,10 @@ func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string ws = append(ws, "secret_key_path is deprecated, please use "+ "secret_key instead and load the key contents via file()") } + if _, ok := c.Config["attributes"]; ok { + ws = append(ws, "using map style attribute values is deprecated, "+ + " please use a single raw JSON string instead") + } return ws, es } @@ -286,6 +304,14 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis } } + if attrs, ok := c.Config["attributes_json"]; 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) + } + p.Attributes = m + } + return p, nil } @@ -306,7 +332,7 @@ func rawToJSON(raw interface{}) (interface{}, error) { return s[0], nil default: - return raw, nil + return s, nil } } @@ -328,6 +354,17 @@ func retryFunc(timeout time.Duration, f func() error) error { } } +func (p *Provisioner) fetchChefCertificatesFunc( + knifeCmd string, + confDir string) func(terraform.UIOutput, communicator.Communicator) error { + return func(o terraform.UIOutput, comm communicator.Communicator) error { + clientrb := path.Join(confDir, clienrb) + cmd := fmt.Sprintf("%s ssl fetch -c %s", knifeCmd, clientrb) + + return p.runCommand(o, comm, cmd) + } +} + func (p *Provisioner) runChefClientFunc( chefCmd string, confDir string) func(terraform.UIOutput, communicator.Communicator) error { diff --git a/builtin/provisioners/chef/resource_provisioner_test.go b/builtin/provisioners/chef/resource_provisioner_test.go index 40625196a..d3f9684da 100644 --- a/builtin/provisioners/chef/resource_provisioner_test.go +++ b/builtin/provisioners/chef/resource_provisioner_test.go @@ -16,7 +16,6 @@ func TestResourceProvisioner_impl(t *testing.T) { func TestResourceProvider_Validate_good(t *testing.T) { c := testConfig(t, map[string]interface{}{ - "attributes": []interface{}{"key1 { subkey1 = value1 }"}, "environment": "_default", "node_name": "nodename1", "run_list": []interface{}{"cookbook::recipe"}, @@ -149,3 +148,76 @@ func TestResourceProvider_runChefClient(t *testing.T) { } } } + +func TestResourceProvider_fetchChefCertificates(t *testing.T) { + cases := map[string]struct { + Config *terraform.ResourceConfig + KnifeCmd string + ConfDir string + Commands map[string]bool + }{ + "Sudo": { + Config: testConfig(t, map[string]interface{}{ + "fetch_chef_certificates": true, + "node_name": "nodename1", + "run_list": []interface{}{"cookbook::recipe"}, + "server_url": "https://chef.local", + "validation_client_name": "validator", + "validation_key_path": "test-fixtures/validator.pem", + }), + + KnifeCmd: linuxKnifeCmd, + + ConfDir: linuxConfDir, + + Commands: map[string]bool{ + fmt.Sprintf(`sudo %s ssl fetch -c %s`, + linuxKnifeCmd, + path.Join(linuxConfDir, "client.rb")): true, + }, + }, + + "NoSudo": { + Config: testConfig(t, map[string]interface{}{ + "fetch_chef_certificates": true, + "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", + }), + + KnifeCmd: windowsKnifeCmd, + + ConfDir: windowsConfDir, + + Commands: map[string]bool{ + fmt.Sprintf(`%s ssl fetch -c %s`, + windowsKnifeCmd, + path.Join(windowsConfDir, "client.rb")): true, + }, + }, + } + + r := new(ResourceProvisioner) + o := new(terraform.MockUIOutput) + c := new(communicator.MockCommunicator) + + for k, tc := range cases { + c.Commands = tc.Commands + + p, err := r.decodeConfig(tc.Config) + if err != nil { + t.Fatalf("Error: %v", err) + } + + p.fetchChefCertificates = p.fetchChefCertificatesFunc(tc.KnifeCmd, tc.ConfDir) + p.useSudo = !p.PreventSudo + + err = p.fetchChefCertificates(o, c) + if err != nil { + t.Fatalf("Test %q failed: %v", k, err) + } + } +} diff --git a/builtin/provisioners/chef/windows_provisioner_test.go b/builtin/provisioners/chef/windows_provisioner_test.go index 13604d6c9..8dd0dee28 100644 --- a/builtin/provisioners/chef/windows_provisioner_test.go +++ b/builtin/provisioners/chef/windows_provisioner_test.go @@ -196,6 +196,31 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { `"subkey2b":{"subkey3":"value3"}}},"key2":"value2","run_list":["cookbook::recipe"]}`, }, }, + + "Attributes JSON": { + 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", + }), + + 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"]}`, + }, + }, } r := new(ResourceProvisioner) diff --git a/website/source/docs/provisioners/chef.html.markdown b/website/source/docs/provisioners/chef.html.markdown index e4b89f23f..934527269 100644 --- a/website/source/docs/provisioners/chef.html.markdown +++ b/website/source/docs/provisioners/chef.html.markdown @@ -25,14 +25,19 @@ available on the target machine. resource "aws_instance" "web" { ... provisioner "chef" { - attributes { - "key" = "value" - "app" { - "cluster1" { - "nodes" = ["webserver1", "webserver2"] + attributes_json = <