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"]}`, `"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) r := new(ResourceProvisioner)

View File

@ -24,16 +24,18 @@ import (
) )
const ( const (
clienrb = "client.rb" clienrb = "client.rb"
defaultEnv = "_default" defaultEnv = "_default"
firstBoot = "first-boot.json" firstBoot = "first-boot.json"
logfileDir = "logfiles" logfileDir = "logfiles"
linuxChefCmd = "chef-client" linuxChefCmd = "chef-client"
linuxConfDir = "/etc/chef" linuxKnifeCmd = "knife"
secretKey = "encrypted_data_bag_secret" linuxConfDir = "/etc/chef"
validationKey = "validation.pem" secretKey = "encrypted_data_bag_secret"
windowsChefCmd = "cmd /c chef-client" validationKey = "validation.pem"
windowsConfDir = "C:/chef" windowsChefCmd = "cmd /c chef-client"
windowsKnifeCmd = "cmd /c knife"
windowsConfDir = "C:/chef"
) )
const clientConf = ` const clientConf = `
@ -74,34 +76,37 @@ ENV['no_proxy'] = "{{ join .NOProxy "," }}"
// Provisioner represents a specificly configured chef provisioner // Provisioner represents a specificly configured chef provisioner
type Provisioner struct { type Provisioner struct {
Attributes interface{} `mapstructure:"attributes"` Attributes interface{} `mapstructure:"attributes"`
ClientOptions []string `mapstructure:"client_options"` AttributesJSON string `mapstructure:"attributes_json"`
DisableReporting bool `mapstructure:"disable_reporting"` ClientOptions []string `mapstructure:"client_options"`
Environment string `mapstructure:"environment"` DisableReporting bool `mapstructure:"disable_reporting"`
LogToFile bool `mapstructure:"log_to_file"` Environment string `mapstructure:"environment"`
UsePolicyfile bool `mapstructure:"use_policyfile"` FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"`
PolicyGroup string `mapstructure:"policy_group"` LogToFile bool `mapstructure:"log_to_file"`
PolicyName string `mapstructure:"policy_name"` UsePolicyfile bool `mapstructure:"use_policyfile"`
HTTPProxy string `mapstructure:"http_proxy"` PolicyGroup string `mapstructure:"policy_group"`
HTTPSProxy string `mapstructure:"https_proxy"` PolicyName string `mapstructure:"policy_name"`
NOProxy []string `mapstructure:"no_proxy"` HTTPProxy string `mapstructure:"http_proxy"`
NodeName string `mapstructure:"node_name"` HTTPSProxy string `mapstructure:"https_proxy"`
OhaiHints []string `mapstructure:"ohai_hints"` NOProxy []string `mapstructure:"no_proxy"`
OSType string `mapstructure:"os_type"` NodeName string `mapstructure:"node_name"`
PreventSudo bool `mapstructure:"prevent_sudo"` OhaiHints []string `mapstructure:"ohai_hints"`
RunList []string `mapstructure:"run_list"` OSType string `mapstructure:"os_type"`
SecretKey string `mapstructure:"secret_key"` PreventSudo bool `mapstructure:"prevent_sudo"`
ServerURL string `mapstructure:"server_url"` RunList []string `mapstructure:"run_list"`
SkipInstall bool `mapstructure:"skip_install"` SecretKey string `mapstructure:"secret_key"`
SSLVerifyMode string `mapstructure:"ssl_verify_mode"` ServerURL string `mapstructure:"server_url"`
ValidationClientName string `mapstructure:"validation_client_name"` SkipInstall bool `mapstructure:"skip_install"`
ValidationKey string `mapstructure:"validation_key"` SSLVerifyMode string `mapstructure:"ssl_verify_mode"`
Version string `mapstructure:"version"` ValidationClientName string `mapstructure:"validation_client_name"`
ValidationKey string `mapstructure:"validation_key"`
Version string `mapstructure:"version"`
installChefClient func(terraform.UIOutput, communicator.Communicator) error installChefClient func(terraform.UIOutput, communicator.Communicator) error
createConfigFiles func(terraform.UIOutput, communicator.Communicator) error createConfigFiles func(terraform.UIOutput, communicator.Communicator) error
runChefClient func(terraform.UIOutput, communicator.Communicator) error fetchChefCertificates func(terraform.UIOutput, communicator.Communicator) error
useSudo bool runChefClient func(terraform.UIOutput, communicator.Communicator) error
useSudo bool
// Deprecated Fields // Deprecated Fields
SecretKeyPath string `mapstructure:"secret_key_path"` SecretKeyPath string `mapstructure:"secret_key_path"`
@ -138,11 +143,13 @@ func (r *ResourceProvisioner) Apply(
case "linux": case "linux":
p.installChefClient = p.linuxInstallChefClient p.installChefClient = p.linuxInstallChefClient
p.createConfigFiles = p.linuxCreateConfigFiles p.createConfigFiles = p.linuxCreateConfigFiles
p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxChefCmd, 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.installChefClient = p.windowsInstallChefClient
p.createConfigFiles = p.windowsCreateConfigFiles p.createConfigFiles = p.windowsCreateConfigFiles
p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsChefCmd, windowsConfDir)
p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir)
p.useSudo = false p.useSudo = false
default: default:
@ -176,6 +183,13 @@ func (r *ResourceProvisioner) Apply(
return err 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...") 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
@ -222,6 +236,10 @@ func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string
ws = append(ws, "secret_key_path is deprecated, please use "+ ws = append(ws, "secret_key_path is deprecated, please use "+
"secret_key instead and load the key contents via file()") "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 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 return p, nil
} }
@ -306,7 +332,7 @@ func rawToJSON(raw interface{}) (interface{}, error) {
return s[0], nil return s[0], nil
default: 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( func (p *Provisioner) runChefClientFunc(
chefCmd string, chefCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error { 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) { func TestResourceProvider_Validate_good(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"attributes": []interface{}{"key1 { subkey1 = value1 }"},
"environment": "_default", "environment": "_default",
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "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"]}`, `"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) r := new(ResourceProvisioner)

View File

@ -25,14 +25,19 @@ available on the target machine.
resource "aws_instance" "web" { resource "aws_instance" "web" {
... ...
provisioner "chef" { provisioner "chef" {
attributes { attributes_json = <<EOF
"key" = "value" {
"app" { "key": "value",
"cluster1" { "app": {
"nodes" = ["webserver1", "webserver2"] "cluster1": {
"nodes": [
"webserver1",
"webserver2"
]
} }
} }
} }
EOF
environment = "_default" environment = "_default"
run_list = ["cookbook::recipe"] run_list = ["cookbook::recipe"]
node_name = "webserver1" node_name = "webserver1"
@ -49,11 +54,12 @@ resource "aws_instance" "web" {
The following arguments are supported: The following arguments are supported:
* `attributes (map)` - (Optional) A map with initial node attributes for the new node. * `attributes_json (string)` - (Optional) A raw JSON string with initial node attributes
See example. 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 * `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 * `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) 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 * `environment (string)` - (Optional) The Chef environment the new node will be joining
(defaults `_default`). (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 * `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 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 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 These are supported for backwards compatibility and may be removed in a
future version: 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__. * `secret_key_path (string)` - __Deprecated: please use `secret_key` instead__.
* `validation_key_path (string)` - __Deprecated: please use `validation_key` instead__.