Refactored quite a few things after review...

Also renamed the provisioner to just `chef` as it’s out intention to
end up with one provisioner for all types of `chef` clients.
This commit is contained in:
Sander van Harmelen 2015-05-08 23:25:24 +02:00
parent d4150d5b1a
commit c19d92fb67
11 changed files with 92 additions and 65 deletions

View File

@ -1,7 +1,7 @@
package main package main
import ( import (
"github.com/hashicorp/terraform/builtin/provisioners/chef-client" "github.com/hashicorp/terraform/builtin/provisioners/chef"
"github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -9,7 +9,7 @@ import (
func main() { func main() {
plugin.Serve(&plugin.ServeOpts{ plugin.Serve(&plugin.ServeOpts{
ProvisionerFunc: func() terraform.ResourceProvisioner { ProvisionerFunc: func() terraform.ResourceProvisioner {
return new(chefclient.ResourceProvisioner) return new(chef.ResourceProvisioner)
}, },
}) })
} }

View File

@ -1,4 +1,4 @@
package chefclient package chef
import ( import (
"bytes" "bytes"
@ -22,9 +22,12 @@ import (
) )
const ( const (
clienrb = "client.rb"
defaultChefEnv = "_default"
firstBoot = "first-boot.json" firstBoot = "first-boot.json"
logfileDir = "logfiles" logfileDir = "logfiles"
linuxConfDir = "/etc/chef" linuxConfDir = "/etc/chef"
validationKey = "validation.pem"
windowsConfDir = "C:/chef" windowsConfDir = "C:/chef"
) )
@ -52,7 +55,7 @@ ENV['HTTPS_PROXY'] = "{{ .HTTPSProxy }}"
// Provisioner represents a specificly configured chef provisioner // Provisioner represents a specificly configured chef provisioner
type Provisioner struct { type Provisioner struct {
Attributes interface{} `mapstructure:"-"` Attributes interface{} `mapstructure:"attributes"`
Environment string `mapstructure:"environment"` Environment string `mapstructure:"environment"`
LogToFile bool `mapstructure:"log_to_file"` LogToFile bool `mapstructure:"log_to_file"`
HTTPProxy string `mapstructure:"http_proxy"` HTTPProxy string `mapstructure:"http_proxy"`
@ -169,6 +172,7 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis
p := new(Provisioner) p := new(Provisioner)
decConf := &mapstructure.DecoderConfig{ decConf := &mapstructure.DecoderConfig{
ErrorUnused: true,
WeaklyTypedInput: true, WeaklyTypedInput: true,
Result: p, Result: p,
} }
@ -182,7 +186,7 @@ func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provis
} }
if p.Environment == "" { if p.Environment == "" {
p.Environment = "_default" p.Environment = defaultChefEnv
} }
if attrs, ok := c.Raw["attributes"]; ok { if attrs, ok := c.Raw["attributes"]; ok {
@ -241,7 +245,7 @@ func (p *Provisioner) runChefClientFunc(
cmd := fmt.Sprintf("chef-client -j %q -E %q", fb, p.Environment) cmd := fmt.Sprintf("chef-client -j %q -E %q", fb, p.Environment)
if p.LogToFile { if p.LogToFile {
if err := os.MkdirAll(logfileDir, 0777); err != nil { if err := os.MkdirAll(logfileDir, 0755); err != nil {
return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err) return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err)
} }
@ -290,16 +294,16 @@ func (p *Provisioner) deployConfigFiles(
o terraform.UIOutput, o terraform.UIOutput,
comm communicator.Communicator, comm communicator.Communicator,
confDir string) error { confDir string) error {
// Open the validation .pem file // Open the validation key file
f, err := os.Open(p.ValidationKeyPath) f, err := os.Open(p.ValidationKeyPath)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
// Copy the validation .pem to the new instance // Copy the validation key to the new instance
if err := comm.Upload(path.Join(confDir, "validation.pem"), f); err != nil { if err := comm.Upload(path.Join(confDir, validationKey), f); err != nil {
return fmt.Errorf("Uploading validation.pem failed: %v", err) return fmt.Errorf("Uploading %s failed: %v", validationKey, err)
} }
// Make strings.Join available for use within the template // Make strings.Join available for use within the template
@ -307,18 +311,18 @@ func (p *Provisioner) deployConfigFiles(
"join": strings.Join, "join": strings.Join,
} }
// Create a new template and parse the client.rb into it // Create a new template and parse the client config into it
t := template.Must(template.New("client.rb").Funcs(funcMap).Parse(clientConf)) t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf))
var buf bytes.Buffer var buf bytes.Buffer
err = t.Execute(&buf, p) err = t.Execute(&buf, p)
if err != nil { if err != nil {
return fmt.Errorf("Error executing client.rb template: %s", err) return fmt.Errorf("Error executing %s template: %s", clienrb, err)
} }
// Copy the client.rb to the new instance // Copy the client config to the new instance
if err := comm.Upload(path.Join(confDir, "client.rb"), &buf); err != nil { if err := comm.Upload(path.Join(confDir, clienrb), &buf); err != nil {
return fmt.Errorf("Uploading client.rb failed: %v", err) return fmt.Errorf("Uploading %s failed: %v", clienrb, err)
} }
// Create a map with first boot settings // Create a map with first boot settings
@ -327,6 +331,13 @@ func (p *Provisioner) deployConfigFiles(
fb = p.Attributes.(map[string]interface{}) 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 // Add the initial runlist to the first boot settings
fb["run_list"] = p.RunList fb["run_list"] = p.RunList
@ -338,7 +349,7 @@ func (p *Provisioner) deployConfigFiles(
// Copy the first-boot.json to the new instance // Copy the first-boot.json to the new instance
if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil { if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil {
return fmt.Errorf("Uploading first-boot.json failed: %v", err) return fmt.Errorf("Uploading %s failed: %v", firstBoot, err)
} }
return nil return nil
@ -369,6 +380,7 @@ func (p *Provisioner) runCommand(
Stderr: errW, Stderr: errW,
} }
log.Printf("[DEBUG] Executing remote command: %q", cmd.Command)
if err := comm.Start(cmd); err != nil { if err := comm.Start(cmd); err != nil {
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err) return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
} }

View File

@ -1,4 +1,4 @@
package chefclient package chef
import ( import (
"testing" "testing"

View File

@ -1,42 +1,44 @@
package chefclient package chef
import ( import (
"bytes" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
const (
installURL = "https://www.chef.io/chef/install.sh"
)
func (p *Provisioner) sshInstallChefClient( func (p *Provisioner) sshInstallChefClient(
o terraform.UIOutput, o terraform.UIOutput,
comm communicator.Communicator) error { comm communicator.Communicator) error {
var installCmd bytes.Buffer
// Build up a single command based on the given config options // Build up the command prefix
installCmd.WriteString("curl") prefix := ""
if p.HTTPProxy != "" { if p.HTTPProxy != "" {
installCmd.WriteString(" --proxy " + p.HTTPProxy) prefix += fmt.Sprintf("proxy_http='%s' ", p.HTTPProxy)
} }
if p.NOProxy != nil { if p.NOProxy != nil {
installCmd.WriteString(" --noproxy " + strings.Join(p.NOProxy, ",")) prefix += fmt.Sprintf("no_proxy='%s' ", strings.Join(p.NOProxy, ","))
} }
installCmd.WriteString(" -LO https://www.chef.io/chef/install.sh 2>/dev/null &&")
if !p.PreventSudo {
installCmd.WriteString(" sudo")
}
installCmd.WriteString(" bash ./install.sh")
if p.Version != "" {
installCmd.WriteString(" -v " + p.Version)
}
installCmd.WriteString(" &&")
if !p.PreventSudo {
installCmd.WriteString(" sudo")
}
installCmd.WriteString(" rm -f install.sh")
// Execute the command to install Chef Client // First download the install.sh script from Chef
return p.runCommand(o, comm, installCmd.String()) err := p.runCommand(o, comm, fmt.Sprintf("%scurl -LO %s", prefix, installURL))
if err != nil {
return err
}
// Then execute the install.sh scrip to download and install Chef Client
err = p.runCommand(o, comm, fmt.Sprintf("%sbash ./install.sh -v %s", prefix, p.Version))
if err != nil {
return err
}
// And finally cleanup the install.sh script again
return p.runCommand(o, comm, fmt.Sprintf("%srm -f install.sh", prefix))
} }
func (p *Provisioner) sshCreateConfigFiles( func (p *Provisioner) sshCreateConfigFiles(

View File

@ -1,4 +1,4 @@
package chefclient package chef
import ( import (
"testing" "testing"
@ -22,8 +22,9 @@ func TestResourceProvider_sshInstallChefClient(t *testing.T) {
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
"sudo curl -LO https://www.chef.io/chef/install.sh 2>/dev/null && " + "sudo curl -LO https://www.chef.io/chef/install.sh": true,
"sudo bash ./install.sh && sudo rm -f install.sh": true, "sudo bash ./install.sh -v ": true,
"sudo rm -f install.sh": true,
}, },
}, },
@ -38,8 +39,9 @@ func TestResourceProvider_sshInstallChefClient(t *testing.T) {
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
"curl -LO https://www.chef.io/chef/install.sh 2>/dev/null && " + "curl -LO https://www.chef.io/chef/install.sh": true,
"bash ./install.sh && rm -f install.sh": true, "bash ./install.sh -v ": true,
"rm -f install.sh": true,
}, },
}, },
@ -55,8 +57,9 @@ func TestResourceProvider_sshInstallChefClient(t *testing.T) {
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
"curl --proxy http://proxy.local -LO https://www.chef.io/chef/install.sh 2>/dev/null && " + "proxy_http='http://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true,
"bash ./install.sh && rm -f install.sh": true, "proxy_http='http://proxy.local' bash ./install.sh -v ": true,
"proxy_http='http://proxy.local' rm -f install.sh": true,
}, },
}, },
@ -73,8 +76,11 @@ func TestResourceProvider_sshInstallChefClient(t *testing.T) {
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
"curl --proxy http://proxy.local --noproxy http://local.local,http://local.org -LO " + "proxy_http='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
"https://www.chef.io/chef/install.sh 2>/dev/null && bash ./install.sh && " + "curl -LO https://www.chef.io/chef/install.sh": true,
"proxy_http='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
"bash ./install.sh -v ": true,
"proxy_http='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
"rm -f install.sh": true, "rm -f install.sh": true,
}, },
}, },
@ -91,8 +97,9 @@ func TestResourceProvider_sshInstallChefClient(t *testing.T) {
}), }),
Commands: map[string]bool{ Commands: map[string]bool{
"curl -LO https://www.chef.io/chef/install.sh 2>/dev/null && " + "curl -LO https://www.chef.io/chef/install.sh": true,
"bash ./install.sh -v 11.18.6 && rm -f install.sh": true, "bash ./install.sh -v 11.18.6": true,
"rm -f install.sh": true,
}, },
}, },
} }

View File

@ -1,4 +1,4 @@
package chefclient package chef
import ( import (
"fmt" "fmt"

View File

@ -1,4 +1,4 @@
package chefclient package chef
import ( import (
"fmt" "fmt"

View File

@ -1,16 +1,22 @@
--- ---
layout: "docs" layout: "docs"
page_title: "Provisioner: chef-client" page_title: "Provisioner: chef"
sidebar_current: "docs-provisioners-chef-client" sidebar_current: "docs-provisioners-chef"
description: |- description: |-
The `chef-client` provisioner invokes a Chef Client run on a remote resource after first installing and configuring Chef Client on the remote resource. The `chef-client` provisioner supports both `ssh` and `winrm` type connections. The `chef` provisioner invokes a Chef Client run on a remote resource after first installing and configuring Chef Client on the remote resource. The `chef` provisioner supports both `ssh` and `winrm` type connections.
--- ---
# chef Provisioner # Chef Provisioner
The `chef-client` provisioner invokes a Chef Client run on a remote resource after first The `chef` provisioner invokes a Chef Client run on a remote resource after first installing
installing and configuring Chef Client on the remote resource. The `chef-client` provisioner and configuring Chef Client on the remote resource. The `chef` provisioner supports both `ssh`
supports both `ssh` and `winrm` type [connections](/docs/provisioners/connection.html). and `winrm` type [connections](/docs/provisioners/connection.html).
## Requirements
In order for the `chef` provisioner to work properly, you need either `cURL` (when using
a `ssh` type connection) or `PowerShell 2.0` (when using a `winrm` type connection) to be
available on the target machine.
## Example usage ## Example usage
@ -18,7 +24,7 @@ supports both `ssh` and `winrm` type [connections](/docs/provisioners/connection
# Start a initial chef run on a resource # Start a initial chef run on a resource
resource "aws_instance" "web" { resource "aws_instance" "web" {
... ...
provisioner "chef-client" { provisioner "chef" {
attributes { attributes {
"key" = "value" "key" = "value"
"app" { "app" {
@ -73,7 +79,7 @@ The following arguments are supported:
the organization. See the example. the organization. See the example.
* `skip_install (boolean)` - (Optional) Skip the installation of Chef Client on the remote * `skip_install (boolean)` - (Optional) Skip the installation of Chef Client on the remote
machine. This assumes Chef Client is already installed when you run the `chef-client` machine. This assumes Chef Client is already installed when you run the `chef`
provisioner. provisioner.
* `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

View File

@ -178,9 +178,9 @@
<li<%= sidebar_current("docs-provisioners") %>> <li<%= sidebar_current("docs-provisioners") %>>
<a href="/docs/provisioners/index.html">Provisioners</a> <a href="/docs/provisioners/index.html">Provisioners</a>
<ul class="nav"> <ul class="nav">
<li<%= sidebar_current("docs-provisioners-chef-client") %>> <li<%= sidebar_current("docs-provisioners-chef") %>>
+ <a href="/docs/provisioners/chef-client.html">chef-client</a> <a href="/docs/provisioners/chef.html">chef</a>
+ </li> </li>
<li<%= sidebar_current("docs-provisioners-connection") %>> <li<%= sidebar_current("docs-provisioners-connection") %>>
<a href="/docs/provisioners/connection.html">connection</a> <a href="/docs/provisioners/connection.html">connection</a>