diff --git a/plugin/provisioner-chef-client/main.go b/plugin/provisioner-chef-client/main.go index 019b67abd..630de88e3 100644 --- a/plugin/provisioner-chef-client/main.go +++ b/plugin/provisioner-chef-client/main.go @@ -6,10 +6,10 @@ import ( ) func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterProvisioner(new(chefclient.Provisioner)) - server.Serve() + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterProvisioner(new(chefclient.Provisioner)) + server.Serve() } diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index ef8b86635..02a9390f6 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -25,14 +25,14 @@ type Config struct { InstallCommand string `mapstructure:"install_command"` Json map[string]interface{} NodeName string `mapstructure:"node_name"` - RunList []string `mapstructure:"run_list"` PreventSudo bool `mapstructure:"prevent_sudo"` - ServerUrl string `mapstructure:"chef_server_url"` + RunList []string `mapstructure:"run_list"` + ServerUrl string `mapstructure:"server_url"` SkipCleanClient bool `mapstructure:"skip_clean_client"` SkipCleanNode bool `mapstructure:"skip_clean_node"` SkipInstall bool `mapstructure:"skip_install"` StagingDir string `mapstructure:"staging_directory"` - ValidationCommand string `mapstructure:"validation_command"` + ValidationKeyPath string `mapstructure:"validation_key_path"` tpl *packer.ConfigTemplate } @@ -42,8 +42,9 @@ type Provisioner struct { } type ConfigTemplate struct { - NodeName string - ServerUrl string + NodeName string + ServerUrl string + ValidationKeyPath string } type ExecuteTemplate struct { @@ -79,11 +80,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { "{{if .Sudo}}sudo {{end}}bash" } - if p.config.ValidationCommand == "" { - p.config.ValidationCommand = "{{if .Sudo}}sudo {{end}} mv " + - "/tmp/validation.pem /etc/chef/validation.pem" - } - if p.config.RunList == nil { p.config.RunList = make([]string, 0) } @@ -127,9 +123,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } validates := map[string]*string{ - "execute_command": &p.config.ExecuteCommand, - "install_command": &p.config.InstallCommand, - "validation_command": &p.config.ValidationCommand, + "execute_command": &p.config.ExecuteCommand, + "install_command": &p.config.InstallCommand, } for n, ptr := range validates { @@ -150,6 +145,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + if p.config.ServerUrl == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("server_url must be set")) + } + // Process the user variables within the JSON and set the JSON. // Do this early so that we can validate and show errors. p.config.Json, err = p.processJsonUserVars() @@ -166,6 +166,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + nodeName := p.config.NodeName + remoteValidationKeyPath := "" + serverUrl := p.config.ServerUrl + if !p.config.SkipInstall { if err := p.installChef(ui, comm); err != nil { return fmt.Errorf("Error installing Chef: %s", err) @@ -176,14 +180,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error creating staging directory: %s", err) } - if err := p.moveValidation(ui, comm); err != nil { - return fmt.Errorf("Error moving validation.pem: %s", err) + if p.config.ValidationKeyPath != "" { + remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir) + if err := p.copyValidationKey(ui, comm, remoteValidationKeyPath); err != nil { + return fmt.Errorf("Error copying validation key: %s", err) + } } - nodeName := p.config.NodeName - serverUrl := p.config.ServerUrl - - configPath, err := p.createConfig(ui, comm, nodeName, serverUrl) + configPath, err := p.createConfig( + ui, comm, nodeName, serverUrl, remoteValidationKeyPath) if err != nil { return fmt.Errorf("Error creating Chef config file: %s", err) } @@ -237,7 +242,7 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds return comm.UploadDir(dst, src, nil) } -func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string) (string, error) { +func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, remoteKeyPath string) (string, error) { ui.Message("Creating configuration file 'client.rb'") // Read the template @@ -258,8 +263,9 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeN } configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{ - NodeName: nodeName, - ServerUrl: serverUrl, + NodeName: nodeName, + ServerUrl: serverUrl, + ValidationKeyPath: remoteKeyPath, }) if err != nil { return "", err @@ -414,26 +420,20 @@ func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error return nil } -func (p *Provisioner) moveValidation(ui packer.Ui, comm packer.Communicator) error { - ui.Message("Moving validation.pem...") +func (p *Provisioner) copyValidationKey(ui packer.Ui, comm packer.Communicator, remotePath string) error { + ui.Message("Uploading validation key...") - command, err := p.config.tpl.Process(p.config.ValidationCommand, &InstallChefTemplate{ - Sudo: !p.config.PreventSudo, - }) + // First upload the validation key to a writable location + f, err := os.Open(p.config.ValidationKeyPath) if err != nil { return err } + defer f.Close() - cmd := &packer.RemoteCmd{Command: command} - if err := cmd.StartWithUi(comm, ui); err != nil { + if err := comm.Upload(remotePath, f); err != nil { return err } - if cmd.ExitStatus != 0 { - return fmt.Errorf( - "Move script exited with non-zero exit status %d", cmd.ExitStatus) - } - return nil } @@ -482,6 +482,9 @@ log_level :info log_location STDOUT chef_server_url "{{.ServerUrl}}" validation_client_name "chef-validator" +{{if ne .ValidationKeyPath ""}} +validation_key "{{.ValidationKeyPath}}" +{{end}} {{if ne .NodeName ""}} node_name "{{.NodeName}}" {{end}} diff --git a/provisioner/chef-client/provisioner_test.go b/provisioner/chef-client/provisioner_test.go index 9096f51f9..b9d3b9c43 100644 --- a/provisioner/chef-client/provisioner_test.go +++ b/provisioner/chef-client/provisioner_test.go @@ -9,7 +9,9 @@ import ( ) func testConfig() map[string]interface{} { - return map[string]interface{}{} + return map[string]interface{}{ + "server_url": "foo", + } } func TestProvisioner_Impl(t *testing.T) { @@ -67,7 +69,6 @@ func TestProvisionerPrepare_commands(t *testing.T) { commands := []string{ "execute_command", "install_command", - "validation_command", } for _, command := range commands { @@ -98,3 +99,23 @@ func TestProvisionerPrepare_commands(t *testing.T) { } } } + +func TestProvisionerPrepare_serverUrl(t *testing.T) { + var p Provisioner + + // Test not set + config := testConfig() + delete(config, "server_url") + err := p.Prepare(config) + if err == nil { + t.Fatal("should error") + } + + // Test set + config = testConfig() + config["server_url"] = "foo" + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/website/source/docs/provisioners/chef-client.html.markdown b/website/source/docs/provisioners/chef-client.html.markdown new file mode 100644 index 000000000..beb370ae1 --- /dev/null +++ b/website/source/docs/provisioners/chef-client.html.markdown @@ -0,0 +1,152 @@ +--- +layout: "docs" +page_title: "Chef-Client Provisioner" +--- + +# Chef Client Provisioner + +Type: `chef-client` + +The Chef Client provisioner installs and configures software on machines built +by Packer using [chef-client](http://docs.opscode.com/chef_client.html). +Packer configures a Chef client to talk to a remote Chef Server to +provision the machine. + +The provisioner will even install Chef onto your machine if it isn't already +installed, using the official Chef installers provided by Opscode. + +## Basic Example + +The example below is fully functional. It will install Chef onto the +remote machine and run Chef client. + +
+{
+  "type": "chef-client",
+  "server_url": "http://mychefserver.com:4000/"
+}
+
+ +## Configuration Reference + +The reference of available configuration options is listed below. No +configuration is actually required, but `node_name` is recommended +since it will allow the provisioner to clean up the node/client. + +* `config_template` (string) - Path to a template that will be used for + the Chef configuration file. By default Packer only sets configuration + it needs to match the settings set in the provisioner configuration. If + you need to set configurations that the Packer provisioner doesn't support, + then you should use a custom configuration template. See the dedicated + "Chef Configuration" section below for more details. + +* `execute_command` (string) - The command used to execute Chef. This has + various [configuration template variables](/docs/templates/configuration-templates.html) + available. See below for more information. + +* `install_command` (string) - The command used to install Chef. This has + various [configuration template variables](/docs/templates/configuration-templates.html) + available. See below for more information. + +* `json` (object) - An arbitrary mapping of JSON that will be available as + node attributes while running Chef. + +* `node_name` (string) - The name of the node to register with the Chef + Server. This is optional and by defalt is empty. If you don't set this, + Packer can't clean up the node from the Chef Server using knife. + +* `prevent_sudo` (boolean) - By default, the configured commands that are + executed to install and run Chef are executed with `sudo`. If this is true, + then the sudo will be omitted. + +* `run_list` (array of strings) - The [run list](http://docs.opscode.com/essentials_node_object_run_lists.html) + for Chef. By default this is empty, and will use the run list sent + down by the Chef Server. + +* `server_url` (string) - The URL to the Chef server. This is required. + +* `skip_clean_client` (boolean) - If true, Packer won't remove the client + from the Chef server after it is done running. By default, this is false. + +* `skip_clean_node` (boolean) - If true, Packer won't remove the node + from the Chef server after it is done running. By default, this is false. + This will be true by default if `node_name` is not set. + +* `skip_install` (boolean) - If true, Chef will not automatically be installed + on the machine using the Opscode omnibus installers. + +* `staging_directory` (string) - This is the directory where all the configuration + of Chef by Packer will be placed. By default this is "/tmp/packer-chef-solo". + This directory doesn't need to exist but must have proper permissions so that + the SSH user that Packer uses is able to create directories and write into + this folder. If the permissions are not correct, use a shell provisioner + prior to this to configure it properly. + +* `validation_key_path` (string) - Path to the validation key for communicating + with the Chef Server. This will be uploaded to the remote machine. If this + is NOT set, then it is your responsibility via other means (shell provisioner, + etc.) to get a validation key to where Chef expects it. + +## Chef Configuration + +By default, Packer uses a simple Chef configuration file in order to set +the options specified for the provisioner. But Chef is a complex tool that +supports many configuration options. Packer allows you to specify a custom +configuration template if you'd like to set custom configurations. + +The default value for the configuration template is: + +``` +log_level :info +log_location STDOUT +chef_server_url "{{.ServerUrl}}" +validation_client_name "chef-validator" +{{if ne .ValidationKeyPath ""}} +validation_key "{{.ValidationKeyPath}}" +{{end}} +{{if ne .NodeName ""}} +node_name "{{.NodeName}}" +{{end}} +``` + +This template is a [configuration template](/docs/templates/configuration-templates.html) +and has a set of variables available to use: + +* `NodeName` - The node name set in the configuration. +* `ServerUrl` - The URL of the Chef Server set in the configuration. +* `ValidationKeyPath` - Path to the validation key, if it is set. + +## Execute Command + +By default, Packer uses the following command (broken across multiple lines +for readability) to execute Chef: + +``` +{{if .Sudo}}sudo {{end}}chef-client \ + --no-color \ + -c {{.ConfigPath}} \ + -j {{.JsonPath}} +``` + +This command can be customized using the `execute_command` configuration. +As you can see from the default value above, the value of this configuration +can contain various template variables, defined below: + +* `ConfigPath` - The path to the Chef configuration file. + file. +* `JsonPath` - The path to the JSON attributes file for the node. +* `Sudo` - A boolean of whether to `sudo` the command or not, depending on + the value of the `prevent_sudo` configuration. + +## Install Command + +By default, Packer uses the following command (broken across multiple lines +for readability) to install Chef. This command can be customized if you want +to install Chef in another way. + +``` +curl -L https://www.opscode.com/chef/install.sh | \ + {{if .Sudo}}sudo{{end}} bash +``` + +This command can be customized using the `install_command` configuration. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 30744b372..20839a143 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -46,6 +46,7 @@
  • Shell Scripts
  • File Uploads
  • Ansible
  • +
  • Chef Client
  • Chef Solo
  • Puppet
  • Salt