Merge pull request #4906 from svanharmelen/f-chef-attribute-file

provisioner/chef: make the Chef `attributes` param also accept a raw JSON string
This commit is contained in:
Sander van Harmelen 2016-02-11 20:57:56 +01:00
commit 7c32752332
5 changed files with 219 additions and 48 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = <<EOF
{
"key": "value",
"app": {
"cluster1": {
"nodes": [
"webserver1",
"webserver2"
]
}
}
}
EOF
environment = "_default"
run_list = ["cookbook::recipe"]
node_name = "webserver1"
@ -49,11 +54,12 @@ resource "aws_instance" "web" {
The following arguments are supported:
* `attributes (map)` - (Optional) A map with initial node attributes for the new node.
See example.
* `attributes_json (string)` - (Optional) A raw JSON string with initial node attributes
for the new node. These can also be loaded from a file on disk using the [`file()`
interpolation function](/docs/configuration/interpolation.html#file_path_).
* `client_options (array)` - (Optional) A list of optional Chef Client configuration
options. See the Chef Client [documentation](https://docs.chef.io/config_rb_client.html) for all available options.
options. See the [Chef Client ](https://docs.chef.io/config_rb_client.html) documentation for all available options.
* `disable_reporting (boolean)` - (Optional) If true the Chef Client will not try to send
reporting data (used by Chef Reporting) to the Chef Server (defaults false)
@ -61,6 +67,10 @@ The following arguments are supported:
* `environment (string)` - (Optional) The Chef environment the new node will be joining
(defaults `_default`).
* `fetch_chef_certificates (boolean)` (Optional) If true the SSL certificates configured
on your Chef server will be fetched and trusted. See the knife [ssl_fetch](https://docs.chef.io/knife_ssl_fetch.html)
documentation for more details.
* `log_to_file (boolean)` - (Optional) If true, the output of the initial Chef Client run
will be logged to a local file instead of the console. The file will be created in a
subdirectory called `logfiles` created in your current directory. The filename will be
@ -118,5 +128,6 @@ The following arguments are supported:
These are supported for backwards compatibility and may be removed in a
future version:
* `validation_key_path (string)` - __Deprecated: please use `validation_key` instead__.
* `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__.