From 45a16fceacca4ecb6b2e710ed29229657736a5bc Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Fri, 5 Jul 2013 00:26:48 -0400 Subject: [PATCH 01/11] First pass at Chef Solo provisioner. --- config.go | 3 +- plugin/provisioner-chef-solo/main.go | 10 + provisioner/chef-solo/provisioner.go | 359 +++++++++++++++++++++++++++ 3 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 plugin/provisioner-chef-solo/main.go create mode 100644 provisioner/chef-solo/provisioner.go diff --git a/config.go b/config.go index f2ed48956..4fb32d00b 100644 --- a/config.go +++ b/config.go @@ -35,7 +35,8 @@ const defaultConfig = ` }, "provisioners": { - "shell": "packer-provisioner-shell" + "shell": "packer-provisioner-shell", + "chef-solo": "packer-provisioner-chef-solo" } } ` diff --git a/plugin/provisioner-chef-solo/main.go b/plugin/provisioner-chef-solo/main.go new file mode 100644 index 000000000..1474c0197 --- /dev/null +++ b/plugin/provisioner-chef-solo/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "../../provisioner/chef-solo" +) + +func main() { + plugin.ServeProvisioner(new(chefSolo.Provisioner)) +} diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go new file mode 100644 index 000000000..b26e8a1cb --- /dev/null +++ b/provisioner/chef-solo/provisioner.go @@ -0,0 +1,359 @@ +// This package implements a provisioner for Packer that executes +// shell scripts within the remote machine. +package chefSolo + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "github.com/mitchellh/iochan" + "github.com/mitchellh/mapstructure" + "github.com/mitchellh/packer/packer" + "io" + "io/ioutil" + "log" + "os" + "strings" + "text/template" + "path/filepath" +) + +const RemoteStagingPath = "/tmp/provision/chef-solo" +const RemoteFileCachePath = "/tmp/provision/chef-solo" +const RemoteCookbookPath = "/tmp/provision/chef-solo/cookbooks" +const DefaultCookbookPath = "cookbooks" + +var Ui packer.Ui + +type config struct { + // An array of local paths of cookbooks to upload. + CookbookPaths []string `mapstructure:"cookbook_paths"` + + // The local path of the cookbooks to upload. + CookbookPath string `mapstructure:"cookbook_path"` + + // An array of recipes to run. + RunList []string `mapstructure:"run_list"` + + // An array of environment variables that will be injected before + // your command(s) are executed. + JsonFile string `mapstructure:"json_file"` +} + +type Provisioner struct { + config config +} + +type ExecuteRecipeTemplate struct { + SoloRbPath string + JsonPath string + RunList string +} + +func (p *Provisioner) Prepare(raws ...interface{}) error { + errs := make([]error, 0) + for _, raw := range raws { + if err := mapstructure.Decode(raw, &p.config); err != nil { + return err + } + } + + if p.config.CookbookPaths == nil { + p.config.CookbookPaths = make([]string, 0) + } + + if p.config.CookbookPath == "" { + p.config.CookbookPath = DefaultCookbookPath + } + + if len(p.config.CookbookPaths) > 0 && p.config.CookbookPath != "" { + errs = append(errs, errors.New("Only one of cookbooks or cookbook can be specified.")) + } + + if len(p.config.CookbookPaths) == 0 { + p.config.CookbookPaths = append(p.config.CookbookPaths, p.config.CookbookPath) + } + + if p.config.RunList == nil { + p.config.RunList = make([]string, 0) + } + + if p.config.JsonFile != "" { + if _, err := os.Stat(p.config.JsonFile); err != nil { + errs = append(errs, fmt.Errorf("Bad JSON attributes file '%s': %s", p.config.JsonFile, err)) + } + } + + for _, path := range p.config.CookbookPaths { + pFileInfo, err := os.Stat(path) + + if err != nil || !pFileInfo.IsDir() { + errs = append(errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err)) + } + } + + if len(errs) > 0 { + return &packer.MultiError{errs} + } + + return nil +} + +func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + cookbookPaths := make([]string, len(p.config.CookbookPaths)) + copy(cookbookPaths, p.config.CookbookPaths) + + Ui = ui + + // Generic setup for Chef runs + err := InstallChefSolo(comm) + if err != nil { + return fmt.Errorf("Error installing Chef Solo: %s", err) + } + + err = CreateRemoteDirectory(RemoteCookbookPath, comm) + if err != nil { + return fmt.Errorf("Error creating remote staging directory: %s", err) + } + + soloRbPath, err := CreateSoloRb(p.config.CookbookPaths, comm) + if err != nil { + return fmt.Errorf("Error creating Chef Solo configuration file: %s", err) + } + + jsonPath, err := CreateAttributesJson(p.config.JsonFile, comm) + if err != nil { + return fmt.Errorf("Error uploading JSON attributes file: %s", err) + } + + // Upload all cookbooks + for _, path := range cookbookPaths { + ui.Say(fmt.Sprintf("Copying cookbook path: %s", path)) + err = UploadLocalDirectory(path, comm) + if err != nil { + return fmt.Errorf("Error uploading cookbooks: %s", err) + } + } + + // Execute requested recipes + for _, recipe := range p.config.RunList { + ui.Say(fmt.Sprintf("chef-solo running recipe: %s", recipe)) + // Compile the command + var command bytes.Buffer + t := template.Must(template.New("chef-run").Parse("sudo chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}} -o {{.RunList}}")) + t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, recipe}) + + err = executeCommand(command.String(), comm) + if err != nil { + return fmt.Errorf("Error running recipe %s: %s", recipe, err) + } + } + + return nil +} + +func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) { + visitPath := func (path string, f os.FileInfo, err error) (err2 error) { + var remotePath = RemoteCookbookPath + "/" + path + if f.IsDir() { + // Make remote directory + err = CreateRemoteDirectory(remotePath, comm) + if err != nil { + return err + } + } else { + // Upload file to existing directory + file, err := os.Open(path) + if err != nil { + return fmt.Errorf("Error opening file: %s", err) + } + + err = comm.Upload(remotePath, file) + if err != nil { + return fmt.Errorf("Error uploading file: %s", err) + } + } + return + } + + err = filepath.Walk(localDir, visitPath) + if err != nil { + return fmt.Errorf("Error uploading cookbook %s: %s", localDir, err) + } + + return nil +} + +func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { + //Ui.Say(fmt.Sprintf("Creating directory: %s", path)) + var copyCommand = []string{"mkdir -p", path} + + var cmd packer.RemoteCmd + cmd.Command = strings.Join(copyCommand, " ") + + var stdout bytes.Buffer + cmd.Stdout = &stdout + + // Start the command + if err := comm.Start(&cmd); err != nil { + return fmt.Errorf("Unable to create remote directory %s: %d", path, err) + } + + // Wait for it to complete + cmd.Wait() + + return +} + +func CreateSoloRb(cookbookPaths []string, comm packer.Communicator) (str string, err error) { + Ui.Say(fmt.Sprintf("Creating Chef configuration file...")) + + remotePath := RemoteStagingPath + "/solo.rb" + tf, err := ioutil.TempFile("", "packer-chef-solo-rb") + if err != nil { + return "", fmt.Errorf("Error preparing Chef solo.rb: %s", err) + } + + // Write our contents to it + writer := bufio.NewWriter(tf) + + // Messy, messy... + cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbookPaths, "\",\"" + RemoteCookbookPath + "/") + "\"" + contents := "file_cache_path \"" + RemoteFileCachePath + "\"\ncookbook_path [" + cbPathsCat + "]\n" + + if _, err := writer.WriteString(contents); err != nil { + return "", fmt.Errorf("Error preparing solo.rb: %s", err) + } + + if err := writer.Flush(); err != nil { + return "", fmt.Errorf("Error preparing solo.rb: %s", err) + } + + name := tf.Name() + tf.Close() + f, err := os.Open(name) + comm.Upload(remotePath, f) + + defer os.Remove(name) + + // Upload the Chef Solo configuration file to the cookbook directory. + + if err != nil { + return "", fmt.Errorf("Error uploading Chef Solo configuration file: %s", err) + } + //executeCommand("sudo cat " + remotePath, comm) + return remotePath, nil +} + +func CreateAttributesJson(jsonFile string, comm packer.Communicator) (str string, err error) { + Ui.Say(fmt.Sprintf("Uploading Chef attributes file %s", jsonFile)) + remotePath := RemoteStagingPath + "/node.json" + + // Create an empty JSON file if none given + if jsonFile == "" { + tf, err := ioutil.TempFile("", "packer-chef-solo-json") + if err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + defer os.Remove(tf.Name()) + + // Write our contents to it + writer := bufio.NewWriter(tf) + if _, err := writer.WriteString("{}"); err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + + if err := writer.Flush(); err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + + jsonFile = tf.Name() + tf.Close() + } + + log.Printf("Opening %s for reading", jsonFile) + f, err := os.Open(jsonFile) + if err != nil { + return "", fmt.Errorf("Error opening JSON attributes file: %s", err) + } + + log.Printf("Uploading %s => %s", jsonFile, remotePath) + err = comm.Upload(remotePath, f) + if err != nil { + return "", fmt.Errorf("Error uploading JSON attributes file: %s", err) + } + + return remotePath, nil +} + +func InstallChefSolo(comm packer.Communicator) (err error) { + Ui.Say(fmt.Sprintf("Installing Chef Solo")) + var installCommand = "curl -L https://www.opscode.com/chef/install.sh | sudo bash" + err = executeCommand(installCommand, comm) + if err != nil { + return fmt.Errorf("Unable to install Chef Solo: %d", err) + } + + return nil +} + +func executeCommand(command string, comm packer.Communicator) (err error) { + // Setup the remote command + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + var cmd packer.RemoteCmd + cmd.Command = command + cmd.Stdout = stdout_w + cmd.Stderr = stderr_w + + //Ui.Say(fmt.Sprintf("Executing command: %s", cmd.Command)) + log.Printf("Executing command: %s", cmd.Command) + err = comm.Start(&cmd) + if err != nil { + return fmt.Errorf("Failed executing command: %s", err) + } + + exitChan := make(chan int, 1) + stdoutChan := iochan.DelimReader(stdout_r, '\n') + stderrChan := iochan.DelimReader(stderr_r, '\n') + + go func() { + defer stdout_w.Close() + defer stderr_w.Close() + + cmd.Wait() + exitChan <- cmd.ExitStatus + }() + +OutputLoop: + for { + select { + case output := <-stderrChan: + Ui.Message(strings.TrimSpace(output)) + case output := <-stdoutChan: + Ui.Message(strings.TrimSpace(output)) + case exitStatus := <-exitChan: + log.Printf("Chef Solo provisioner exited with status %d", exitStatus) + + if exitStatus != 0 { + return fmt.Errorf("Command exited with non-zero exit status: %d", exitStatus) + } + + break OutputLoop + } + } + + // Make sure we finish off stdout/stderr because we may have gotten + // a message from the exit channel first. + for output := range stdoutChan { + Ui.Message(output) + } + + for output := range stderrChan { + Ui.Message(output) + } + + return nil +} From 608d874b452d15af767ae458ce13948e9e6521a6 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Fri, 5 Jul 2013 21:57:56 -0400 Subject: [PATCH 02/11] Make JSON attributes work with real JSON! --- provisioner/chef-solo/provisioner.go | 101 ++++++++++++++++----------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index b26e8a1cb..25936e1e0 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -17,6 +17,7 @@ import ( "strings" "text/template" "path/filepath" + "encoding/json" ) const RemoteStagingPath = "/tmp/provision/chef-solo" @@ -36,9 +37,14 @@ type config struct { // An array of recipes to run. RunList []string `mapstructure:"run_list"` - // An array of environment variables that will be injected before - // your command(s) are executed. - JsonFile string `mapstructure:"json_file"` + // A string of JSON that will be used as the JSON attributes for the + // Chef run. + Json map[string]interface{} + + UseSudo bool `mapstructure:"use_sudo"` + + // If true + SkipInstall bool `mapstructure:"skip_install"` } type Provisioner struct { @@ -48,7 +54,6 @@ type Provisioner struct { type ExecuteRecipeTemplate struct { SoloRbPath string JsonPath string - RunList string } func (p *Provisioner) Prepare(raws ...interface{}) error { @@ -79,10 +84,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.RunList = make([]string, 0) } - if p.config.JsonFile != "" { - if _, err := os.Stat(p.config.JsonFile); err != nil { - errs = append(errs, fmt.Errorf("Bad JSON attributes file '%s': %s", p.config.JsonFile, err)) + if p.config.Json != nil { + if _, err := json.Marshal(p.config.Json); err != nil { + errs = append(errs, fmt.Errorf("Bad JSON: %s", err)) } + } else { + p.config.Json = make(map[string]interface{}) } for _, path := range p.config.CookbookPaths { @@ -122,7 +129,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error creating Chef Solo configuration file: %s", err) } - jsonPath, err := CreateAttributesJson(p.config.JsonFile, comm) + jsonPath, err := CreateAttributesJson(p.config.Json, p.config.RunList, comm) if err != nil { return fmt.Errorf("Error uploading JSON attributes file: %s", err) } @@ -137,18 +144,19 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } // Execute requested recipes - for _, recipe := range p.config.RunList { - ui.Say(fmt.Sprintf("chef-solo running recipe: %s", recipe)) - // Compile the command - var command bytes.Buffer - t := template.Must(template.New("chef-run").Parse("sudo chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}} -o {{.RunList}}")) - t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, recipe}) - - err = executeCommand(command.String(), comm) - if err != nil { - return fmt.Errorf("Error running recipe %s: %s", recipe, err) - } + ui.Say("Beginning Chef Solo run") + + // Compile the command + var command bytes.Buffer + t := template.Must(template.New("chef-run").Parse("sudo chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) + t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath}) + + err = executeCommand(command.String(), comm) + if err != nil { + return fmt.Errorf("Error running Chef Solo: %s", err) } + + return fmt.Errorf("Die") return nil } @@ -246,32 +254,43 @@ func CreateSoloRb(cookbookPaths []string, comm packer.Communicator) (str string, return remotePath, nil } -func CreateAttributesJson(jsonFile string, comm packer.Communicator) (str string, err error) { - Ui.Say(fmt.Sprintf("Uploading Chef attributes file %s", jsonFile)) +func CreateAttributesJson(jsonAttrs map[string]interface{}, runList []string, comm packer.Communicator) (str string, err error) { + Ui.Say(fmt.Sprintf("Creating and uploading Chef attributes file")) remotePath := RemoteStagingPath + "/node.json" - // Create an empty JSON file if none given - if jsonFile == "" { - tf, err := ioutil.TempFile("", "packer-chef-solo-json") - if err != nil { - return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) - } - defer os.Remove(tf.Name()) - - // Write our contents to it - writer := bufio.NewWriter(tf) - if _, err := writer.WriteString("{}"); err != nil { - return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) - } - - if err := writer.Flush(); err != nil { - return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) - } - - jsonFile = tf.Name() - tf.Close() + var formattedRunList []string + for _, value := range runList { + formattedRunList = append(formattedRunList, "recipe[" + value + "]") } + // Add RunList to JSON + jsonAttrs["run_list"] = formattedRunList + + // Convert to JSON string + jsonString, err := json.MarshalIndent(jsonAttrs, "", " ") + if err != nil { + return "", fmt.Errorf("Error parsing JSON attributes: %s", err) + } + + tf, err := ioutil.TempFile("", "packer-chef-solo-json") + if err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + defer os.Remove(tf.Name()) + + // Write our contents to it + writer := bufio.NewWriter(tf) + if _, err := writer.WriteString(string(jsonString)); err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + + if err := writer.Flush(); err != nil { + return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) + } + + jsonFile := tf.Name() + tf.Close() + log.Printf("Opening %s for reading", jsonFile) f, err := os.Open(jsonFile) if err != nil { From bb3ff626239682075d9d75201c89dc6d4691d10d Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Fri, 5 Jul 2013 22:33:24 -0400 Subject: [PATCH 03/11] Rename configuration `cookbook_paths` to `cookbooks_paths` to conform to Vagrant wording. Remove configuration `cookbook_path` to simplify some things. --- provisioner/chef-solo/provisioner.go | 58 ++++++++++------------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 25936e1e0..9dbbcd2f8 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -5,7 +5,6 @@ package chefSolo import ( "bufio" "bytes" - "errors" "fmt" "github.com/mitchellh/iochan" "github.com/mitchellh/mapstructure" @@ -29,13 +28,10 @@ var Ui packer.Ui type config struct { // An array of local paths of cookbooks to upload. - CookbookPaths []string `mapstructure:"cookbook_paths"` - - // The local path of the cookbooks to upload. - CookbookPath string `mapstructure:"cookbook_path"` + CookbooksPaths []string `mapstructure:"cookbooks_paths"` // An array of recipes to run. - RunList []string `mapstructure:"run_list"` + Recipes []string // A string of JSON that will be used as the JSON attributes for the // Chef run. @@ -64,24 +60,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } - if p.config.CookbookPaths == nil { - p.config.CookbookPaths = make([]string, 0) + if p.config.CookbooksPaths == nil { + p.config.CookbooksPaths = make([]string, 0) } - if p.config.CookbookPath == "" { - p.config.CookbookPath = DefaultCookbookPath - } - - if len(p.config.CookbookPaths) > 0 && p.config.CookbookPath != "" { - errs = append(errs, errors.New("Only one of cookbooks or cookbook can be specified.")) - } - - if len(p.config.CookbookPaths) == 0 { - p.config.CookbookPaths = append(p.config.CookbookPaths, p.config.CookbookPath) - } - - if p.config.RunList == nil { - p.config.RunList = make([]string, 0) + if p.config.Recipes == nil { + p.config.Recipes = make([]string, 0) } if p.config.Json != nil { @@ -92,7 +76,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.Json = make(map[string]interface{}) } - for _, path := range p.config.CookbookPaths { + for _, path := range p.config.CookbooksPaths { pFileInfo, err := os.Stat(path) if err != nil || !pFileInfo.IsDir() { @@ -108,8 +92,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { - cookbookPaths := make([]string, len(p.config.CookbookPaths)) - copy(cookbookPaths, p.config.CookbookPaths) + cookbooksPaths := make([]string, len(p.config.CookbooksPaths)) + copy(cookbooksPaths, p.config.CookbooksPaths) Ui = ui @@ -124,18 +108,18 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error creating remote staging directory: %s", err) } - soloRbPath, err := CreateSoloRb(p.config.CookbookPaths, comm) + soloRbPath, err := CreateSoloRb(p.config.CookbooksPaths, comm) if err != nil { return fmt.Errorf("Error creating Chef Solo configuration file: %s", err) } - jsonPath, err := CreateAttributesJson(p.config.Json, p.config.RunList, comm) + jsonPath, err := CreateAttributesJson(p.config.Json, p.config.Recipes, comm) if err != nil { return fmt.Errorf("Error uploading JSON attributes file: %s", err) } // Upload all cookbooks - for _, path := range cookbookPaths { + for _, path := range cookbooksPaths { ui.Say(fmt.Sprintf("Copying cookbook path: %s", path)) err = UploadLocalDirectory(path, comm) if err != nil { @@ -156,7 +140,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } - return fmt.Errorf("Die") + // return fmt.Errorf("Die") return nil } @@ -214,7 +198,7 @@ func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { return } -func CreateSoloRb(cookbookPaths []string, comm packer.Communicator) (str string, err error) { +func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string, err error) { Ui.Say(fmt.Sprintf("Creating Chef configuration file...")) remotePath := RemoteStagingPath + "/solo.rb" @@ -227,7 +211,7 @@ func CreateSoloRb(cookbookPaths []string, comm packer.Communicator) (str string, writer := bufio.NewWriter(tf) // Messy, messy... - cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbookPaths, "\",\"" + RemoteCookbookPath + "/") + "\"" + cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbooksPaths, "\",\"" + RemoteCookbookPath + "/") + "\"" contents := "file_cache_path \"" + RemoteFileCachePath + "\"\ncookbook_path [" + cbPathsCat + "]\n" if _, err := writer.WriteString(contents); err != nil { @@ -254,17 +238,17 @@ func CreateSoloRb(cookbookPaths []string, comm packer.Communicator) (str string, return remotePath, nil } -func CreateAttributesJson(jsonAttrs map[string]interface{}, runList []string, comm packer.Communicator) (str string, err error) { +func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, comm packer.Communicator) (str string, err error) { Ui.Say(fmt.Sprintf("Creating and uploading Chef attributes file")) remotePath := RemoteStagingPath + "/node.json" - var formattedRunList []string - for _, value := range runList { - formattedRunList = append(formattedRunList, "recipe[" + value + "]") + var formattedRecipes []string + for _, value := range recipes { + formattedRecipes = append(formattedRecipes, "recipe[" + value + "]") } - // Add RunList to JSON - jsonAttrs["run_list"] = formattedRunList + // Add Recipes to JSON + jsonAttrs["run_list"] = formattedRecipes // Convert to JSON string jsonString, err := json.MarshalIndent(jsonAttrs, "", " ") From 552298b68349d8079cec7eeb3ba73466a856e548 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Fri, 5 Jul 2013 23:56:51 -0400 Subject: [PATCH 04/11] Implement `avoid_sudo` configuration option. --- provisioner/chef-solo/provisioner.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 9dbbcd2f8..c9bcaaabe 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -37,7 +37,7 @@ type config struct { // Chef run. Json map[string]interface{} - UseSudo bool `mapstructure:"use_sudo"` + AvoidSudo bool `mapstructure:"avoid_sudo"` // If true SkipInstall bool `mapstructure:"skip_install"` @@ -50,6 +50,11 @@ type Provisioner struct { type ExecuteRecipeTemplate struct { SoloRbPath string JsonPath string + UseSudo bool +} + +type ExecuteInstallChefTemplate struct { + AvoidSudo bool } func (p *Provisioner) Prepare(raws ...interface{}) error { @@ -98,7 +103,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { Ui = ui // Generic setup for Chef runs - err := InstallChefSolo(comm) + err := InstallChefSolo(p.config.AvoidSudo, comm) if err != nil { return fmt.Errorf("Error installing Chef Solo: %s", err) } @@ -132,8 +137,8 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // Compile the command var command bytes.Buffer - t := template.Must(template.New("chef-run").Parse("sudo chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) - t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath}) + t := template.Must(template.New("chef-run").Parse("{{if .UseSudo}}sudo {{end}}chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) + t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, !p.config.AvoidSudo}) err = executeCommand(command.String(), comm) if err != nil { @@ -290,10 +295,14 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co return remotePath, nil } -func InstallChefSolo(comm packer.Communicator) (err error) { +func InstallChefSolo(avoidSudo bool, comm packer.Communicator) (err error) { Ui.Say(fmt.Sprintf("Installing Chef Solo")) - var installCommand = "curl -L https://www.opscode.com/chef/install.sh | sudo bash" - err = executeCommand(installCommand, comm) + + var command bytes.Buffer + t := template.Must(template.New("install-chef").Parse("curl -L https://www.opscode.com/chef/install.sh | {{if .UseSudo}}sudo {{end}}bash")) + t.Execute(&command, map[string]bool{"UseSudo": !avoidSudo}) + + err = executeCommand(command.String(), comm) if err != nil { return fmt.Errorf("Unable to install Chef Solo: %d", err) } From e5e97f3cc16b8b37342cf566057eff603833bd9f Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Sat, 6 Jul 2013 00:15:21 -0400 Subject: [PATCH 05/11] Implement `skip_install` configuration to skip Chef installation. --- provisioner/chef-solo/provisioner.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index c9bcaaabe..0128d789c 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -37,9 +37,10 @@ type config struct { // Chef run. Json map[string]interface{} + // Option to avoid sudo use when executing commands. Defaults to false. AvoidSudo bool `mapstructure:"avoid_sudo"` - // If true + // If true, skips installing Chef. Defaults to false. SkipInstall bool `mapstructure:"skip_install"` } @@ -100,12 +101,14 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { cookbooksPaths := make([]string, len(p.config.CookbooksPaths)) copy(cookbooksPaths, p.config.CookbooksPaths) + var err error Ui = ui - // Generic setup for Chef runs - err := InstallChefSolo(p.config.AvoidSudo, comm) - if err != nil { - return fmt.Errorf("Error installing Chef Solo: %s", err) + if !p.config.SkipInstall { + err = InstallChefSolo(p.config.AvoidSudo, comm) + if err != nil { + return fmt.Errorf("Error installing Chef Solo: %s", err) + } } err = CreateRemoteDirectory(RemoteCookbookPath, comm) @@ -145,7 +148,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } - // return fmt.Errorf("Die") + return fmt.Errorf("Die") return nil } From 3416f0760cd5166ec57fd6105cd679c3801326b4 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Sat, 6 Jul 2013 00:17:28 -0400 Subject: [PATCH 06/11] Remove errant die statement. --- provisioner/chef-solo/provisioner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 0128d789c..6650b17c3 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -148,7 +148,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } - return fmt.Errorf("Die") + // return fmt.Errorf("Die") return nil } From 69f0049a4490fd0739ce4e3f829f71fefa5f2590 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Sat, 6 Jul 2013 00:37:59 -0400 Subject: [PATCH 07/11] Rename configuration `avoid_sudo` to `prevent_sudo` --- provisioner/chef-solo/provisioner.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 6650b17c3..3cd9bd103 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -38,7 +38,7 @@ type config struct { Json map[string]interface{} // Option to avoid sudo use when executing commands. Defaults to false. - AvoidSudo bool `mapstructure:"avoid_sudo"` + PreventSudo bool `mapstructure:"prevent_sudo"` // If true, skips installing Chef. Defaults to false. SkipInstall bool `mapstructure:"skip_install"` @@ -51,11 +51,11 @@ type Provisioner struct { type ExecuteRecipeTemplate struct { SoloRbPath string JsonPath string - UseSudo bool + Sudo bool } type ExecuteInstallChefTemplate struct { - AvoidSudo bool + PreventSudo bool } func (p *Provisioner) Prepare(raws ...interface{}) error { @@ -105,7 +105,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { Ui = ui if !p.config.SkipInstall { - err = InstallChefSolo(p.config.AvoidSudo, comm) + err = InstallChefSolo(p.config.PreventSudo, comm) if err != nil { return fmt.Errorf("Error installing Chef Solo: %s", err) } @@ -140,8 +140,8 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { // Compile the command var command bytes.Buffer - t := template.Must(template.New("chef-run").Parse("{{if .UseSudo}}sudo {{end}}chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) - t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, !p.config.AvoidSudo}) + t := template.Must(template.New("chef-run").Parse("{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) + t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, !p.config.PreventSudo}) err = executeCommand(command.String(), comm) if err != nil { @@ -298,12 +298,12 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co return remotePath, nil } -func InstallChefSolo(avoidSudo bool, comm packer.Communicator) (err error) { +func InstallChefSolo(preventSudo bool, comm packer.Communicator) (err error) { Ui.Say(fmt.Sprintf("Installing Chef Solo")) var command bytes.Buffer - t := template.Must(template.New("install-chef").Parse("curl -L https://www.opscode.com/chef/install.sh | {{if .UseSudo}}sudo {{end}}bash")) - t.Execute(&command, map[string]bool{"UseSudo": !avoidSudo}) + t := template.Must(template.New("install-chef").Parse("curl -L https://www.opscode.com/chef/install.sh | {{if .sudo}}sudo {{end}}bash")) + t.Execute(&command, map[string]bool{"sudo": !preventSudo}) err = executeCommand(command.String(), comm) if err != nil { From a84f26bfdfd95cb07fec4aff9594ba7e1b02b362 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Thu, 11 Jul 2013 10:47:51 -0400 Subject: [PATCH 08/11] Add basic test file for provisioner-chef-solo. --- provisioner/chef-solo/provisioner.go | 105 +++++++++++----------- provisioner/chef-solo/provisioner_test.go | 68 ++++++++++++++ 2 files changed, 121 insertions(+), 52 deletions(-) create mode 100644 provisioner/chef-solo/provisioner_test.go diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 3cd9bd103..128770639 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -5,6 +5,7 @@ package chefSolo import ( "bufio" "bytes" + "encoding/json" "fmt" "github.com/mitchellh/iochan" "github.com/mitchellh/mapstructure" @@ -13,16 +14,15 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "strings" "text/template" - "path/filepath" - "encoding/json" ) const RemoteStagingPath = "/tmp/provision/chef-solo" const RemoteFileCachePath = "/tmp/provision/chef-solo" const RemoteCookbookPath = "/tmp/provision/chef-solo/cookbooks" -const DefaultCookbookPath = "cookbooks" +const DefaultCookbooksPath = "cookbooks" var Ui packer.Ui @@ -36,10 +36,10 @@ type config struct { // A string of JSON that will be used as the JSON attributes for the // Chef run. Json map[string]interface{} - + // Option to avoid sudo use when executing commands. Defaults to false. PreventSudo bool `mapstructure:"prevent_sudo"` - + // If true, skips installing Chef. Defaults to false. SkipInstall bool `mapstructure:"skip_install"` } @@ -50,8 +50,8 @@ type Provisioner struct { type ExecuteRecipeTemplate struct { SoloRbPath string - JsonPath string - Sudo bool + JsonPath string + Sudo bool } type ExecuteInstallChefTemplate struct { @@ -65,9 +65,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return err } } - + if p.config.CookbooksPaths == nil { - p.config.CookbooksPaths = make([]string, 0) + p.config.CookbooksPaths = []string{DefaultCookbooksPath} + } if p.config.Recipes == nil { @@ -84,7 +85,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { for _, path := range p.config.CookbooksPaths { pFileInfo, err := os.Stat(path) - + if err != nil || !pFileInfo.IsDir() { errs = append(errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err)) } @@ -100,32 +101,32 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { cookbooksPaths := make([]string, len(p.config.CookbooksPaths)) copy(cookbooksPaths, p.config.CookbooksPaths) - + var err error Ui = ui - + if !p.config.SkipInstall { err = InstallChefSolo(p.config.PreventSudo, comm) if err != nil { return fmt.Errorf("Error installing Chef Solo: %s", err) } } - + err = CreateRemoteDirectory(RemoteCookbookPath, comm) if err != nil { return fmt.Errorf("Error creating remote staging directory: %s", err) } - + soloRbPath, err := CreateSoloRb(p.config.CookbooksPaths, comm) if err != nil { return fmt.Errorf("Error creating Chef Solo configuration file: %s", err) } - + jsonPath, err := CreateAttributesJson(p.config.Json, p.config.Recipes, comm) if err != nil { return fmt.Errorf("Error uploading JSON attributes file: %s", err) } - + // Upload all cookbooks for _, path := range cookbooksPaths { ui.Say(fmt.Sprintf("Copying cookbook path: %s", path)) @@ -134,27 +135,27 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error uploading cookbooks: %s", err) } } - + // Execute requested recipes ui.Say("Beginning Chef Solo run") - + // Compile the command var command bytes.Buffer t := template.Must(template.New("chef-run").Parse("{{if .Sudo}}sudo {{end}}chef-solo --no-color -c {{.SoloRbPath}} -j {{.JsonPath}}")) t.Execute(&command, &ExecuteRecipeTemplate{soloRbPath, jsonPath, !p.config.PreventSudo}) - + err = executeCommand(command.String(), comm) if err != nil { return fmt.Errorf("Error running Chef Solo: %s", err) } - + // return fmt.Errorf("Die") return nil } func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) { - visitPath := func (path string, f os.FileInfo, err error) (err2 error) { + visitPath := func(path string, f os.FileInfo, err error) (err2 error) { var remotePath = RemoteCookbookPath + "/" + path if f.IsDir() { // Make remote directory @@ -168,7 +169,7 @@ func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) if err != nil { return fmt.Errorf("Error opening file: %s", err) } - + err = comm.Upload(remotePath, file) if err != nil { return fmt.Errorf("Error uploading file: %s", err) @@ -176,52 +177,52 @@ func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) } return } - + err = filepath.Walk(localDir, visitPath) if err != nil { return fmt.Errorf("Error uploading cookbook %s: %s", localDir, err) } - + return nil } func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { //Ui.Say(fmt.Sprintf("Creating directory: %s", path)) var copyCommand = []string{"mkdir -p", path} - + var cmd packer.RemoteCmd cmd.Command = strings.Join(copyCommand, " ") - + var stdout bytes.Buffer cmd.Stdout = &stdout - + // Start the command if err := comm.Start(&cmd); err != nil { - return fmt.Errorf("Unable to create remote directory %s: %d", path, err) + return fmt.Errorf("Unable to create remote directory %s: %d", path, err) } // Wait for it to complete cmd.Wait() - + return } func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string, err error) { Ui.Say(fmt.Sprintf("Creating Chef configuration file...")) - + remotePath := RemoteStagingPath + "/solo.rb" tf, err := ioutil.TempFile("", "packer-chef-solo-rb") if err != nil { return "", fmt.Errorf("Error preparing Chef solo.rb: %s", err) } - + // Write our contents to it writer := bufio.NewWriter(tf) - + // Messy, messy... - cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbooksPaths, "\",\"" + RemoteCookbookPath + "/") + "\"" + cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbooksPaths, "\",\""+RemoteCookbookPath+"/") + "\"" contents := "file_cache_path \"" + RemoteFileCachePath + "\"\ncookbook_path [" + cbPathsCat + "]\n" - + if _, err := writer.WriteString(contents); err != nil { return "", fmt.Errorf("Error preparing solo.rb: %s", err) } @@ -229,16 +230,16 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string if err := writer.Flush(); err != nil { return "", fmt.Errorf("Error preparing solo.rb: %s", err) } - + name := tf.Name() tf.Close() f, err := os.Open(name) comm.Upload(remotePath, f) - + defer os.Remove(name) - + // Upload the Chef Solo configuration file to the cookbook directory. - + if err != nil { return "", fmt.Errorf("Error uploading Chef Solo configuration file: %s", err) } @@ -249,21 +250,21 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, comm packer.Communicator) (str string, err error) { Ui.Say(fmt.Sprintf("Creating and uploading Chef attributes file")) remotePath := RemoteStagingPath + "/node.json" - + var formattedRecipes []string for _, value := range recipes { - formattedRecipes = append(formattedRecipes, "recipe[" + value + "]") + formattedRecipes = append(formattedRecipes, "recipe["+value+"]") } - + // Add Recipes to JSON jsonAttrs["run_list"] = formattedRecipes - + // Convert to JSON string jsonString, err := json.MarshalIndent(jsonAttrs, "", " ") if err != nil { return "", fmt.Errorf("Error parsing JSON attributes: %s", err) } - + tf, err := ioutil.TempFile("", "packer-chef-solo-json") if err != nil { return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) @@ -279,10 +280,10 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co if err := writer.Flush(); err != nil { return "", fmt.Errorf("Error preparing Chef attributes file: %s", err) } - + jsonFile := tf.Name() tf.Close() - + log.Printf("Opening %s for reading", jsonFile) f, err := os.Open(jsonFile) if err != nil { @@ -294,22 +295,22 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co if err != nil { return "", fmt.Errorf("Error uploading JSON attributes file: %s", err) } - + return remotePath, nil } func InstallChefSolo(preventSudo bool, comm packer.Communicator) (err error) { Ui.Say(fmt.Sprintf("Installing Chef Solo")) - + var command bytes.Buffer t := template.Must(template.New("install-chef").Parse("curl -L https://www.opscode.com/chef/install.sh | {{if .sudo}}sudo {{end}}bash")) t.Execute(&command, map[string]bool{"sudo": !preventSudo}) - + err = executeCommand(command.String(), comm) if err != nil { - return fmt.Errorf("Unable to install Chef Solo: %d", err) + return fmt.Errorf("Unable to install Chef Solo: %d", err) } - + return nil } @@ -322,7 +323,7 @@ func executeCommand(command string, comm packer.Communicator) (err error) { cmd.Command = command cmd.Stdout = stdout_w cmd.Stderr = stderr_w - + //Ui.Say(fmt.Sprintf("Executing command: %s", cmd.Command)) log.Printf("Executing command: %s", cmd.Command) err = comm.Start(&cmd) @@ -369,6 +370,6 @@ OutputLoop: for output := range stderrChan { Ui.Message(output) } - + return nil } diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go new file mode 100644 index 000000000..685a300bf --- /dev/null +++ b/provisioner/chef-solo/provisioner_test.go @@ -0,0 +1,68 @@ +package chefSolo + +import ( + "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + // "inline": []interface{}{"foo", "bar"}, + } +} + +func TestProvisioner_Impl(t *testing.T) { + var raw interface{} + raw = &Provisioner{} + if _, ok := raw.(packer.Provisioner); !ok { + t.Fatalf("must be a Provisioner") + } +} + +// Cookbook paths +////////////////// + +func TestProvisionerPrepare_DefaultCookbookPathIsUsed(t *testing.T) { + var p Provisioner + config := testConfig() + + err := p.Prepare(config) + if err == nil { + t.Errorf("expected error to be thrown for unavailable cookbook path") + } + + if len(p.config.CookbooksPaths) != 1 || p.config.CookbooksPaths[0] != DefaultCookbooksPath { + t.Errorf("unexpected default cookbook path: %s", p.config.CookbooksPaths) + } +} + +func TestProvisionerPrepare_GivenCookbookPathsAreAddedToConfig(t *testing.T) { + var p Provisioner + + path1, err := ioutil.TempDir("", "cookbooks_one") + if err != nil { + t.Errorf("err: %s", err) + } + + path2, err := ioutil.TempDir("", "cookbooks_two") + if err != nil { + t.Errorf("err: %s", err) + } + + defer os.Remove(path1) + defer os.Remove(path2) + + config := testConfig() + config["cookbooks_paths"] = []string{path1, path2} + + err = p.Prepare(config) + if err != nil { + t.Errorf("err: %s", err) + } + + if len(p.config.CookbooksPaths) != 2 || p.config.CookbooksPaths[0] != path1 || p.config.CookbooksPaths[1] != path2 { + t.Errorf("unexpected default cookbook path: %s", p.config.CookbooksPaths) + } +} From a6f3bb3bb20016834cace93d25abd882168d73f8 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Thu, 11 Jul 2013 12:22:15 -0400 Subject: [PATCH 09/11] Improve logging. Correct behavior for undefined recipe list. Correct package name to upstream repo. --- plugin/provisioner-chef-solo/main.go | 2 +- provisioner/chef-solo/provisioner.go | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/plugin/provisioner-chef-solo/main.go b/plugin/provisioner-chef-solo/main.go index 3c8f5b028..902eb7392 100644 --- a/plugin/provisioner-chef-solo/main.go +++ b/plugin/provisioner-chef-solo/main.go @@ -1,8 +1,8 @@ package main import ( - "github.com/jvandyke/packer/provisioner/chef-solo" "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/provisioner/chef-solo" ) func main() { diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 128770639..362c8e1a5 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -149,8 +149,6 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } - // return fmt.Errorf("Die") - return nil } @@ -178,6 +176,7 @@ func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) return } + log.Printf("Uploading directory %s", localDir) err = filepath.Walk(localDir, visitPath) if err != nil { return fmt.Errorf("Error uploading cookbook %s: %s", localDir, err) @@ -187,7 +186,8 @@ func UploadLocalDirectory(localDir string, comm packer.Communicator) (err error) } func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { - //Ui.Say(fmt.Sprintf("Creating directory: %s", path)) + log.Printf("Creating remote directory: %s ", path) + var copyCommand = []string{"mkdir -p", path} var cmd packer.RemoteCmd @@ -211,6 +211,7 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string Ui.Say(fmt.Sprintf("Creating Chef configuration file...")) remotePath := RemoteStagingPath + "/solo.rb" + tf, err := ioutil.TempFile("", "packer-chef-solo-rb") if err != nil { return "", fmt.Errorf("Error preparing Chef solo.rb: %s", err) @@ -234,12 +235,13 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string name := tf.Name() tf.Close() f, err := os.Open(name) - comm.Upload(remotePath, f) - defer os.Remove(name) - // Upload the Chef Solo configuration file to the cookbook directory. + log.Printf("Chef configuration file contents: %s", contents) + // Upload the Chef Solo configuration file to the cookbook directory. + log.Printf("Uploading chef configuration file to %s", remotePath) + err = comm.Upload(remotePath, f) if err != nil { return "", fmt.Errorf("Error uploading Chef Solo configuration file: %s", err) } @@ -257,7 +259,10 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co } // Add Recipes to JSON - jsonAttrs["run_list"] = formattedRecipes + if len(formattedRecipes) > 0 { + log.Printf("Overriding node run list: %s", strings.Join(formattedRecipes, ", ")) + jsonAttrs["run_list"] = formattedRecipes + } // Convert to JSON string jsonString, err := json.MarshalIndent(jsonAttrs, "", " ") From ba5d7a9d729642bba2a4ec085cdeac707655e443 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Fri, 12 Jul 2013 11:09:52 -0400 Subject: [PATCH 10/11] Remove redundant code and clean up some string concatenation. Clean up Say statements. --- provisioner/chef-solo/provisioner.go | 48 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 362c8e1a5..622587f1f 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -19,10 +19,12 @@ import ( "text/template" ) -const RemoteStagingPath = "/tmp/provision/chef-solo" -const RemoteFileCachePath = "/tmp/provision/chef-solo" -const RemoteCookbookPath = "/tmp/provision/chef-solo/cookbooks" -const DefaultCookbooksPath = "cookbooks" +const ( + RemoteStagingPath = "/tmp/provision/chef-solo" + RemoteFileCachePath = "/tmp/provision/chef-solo" + RemoteCookbookPath = "/tmp/provision/chef-solo/cookbooks" + DefaultCookbooksPath = "cookbooks" +) var Ui packer.Ui @@ -68,7 +70,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { if p.config.CookbooksPaths == nil { p.config.CookbooksPaths = []string{DefaultCookbooksPath} - } if p.config.Recipes == nil { @@ -99,9 +100,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { - cookbooksPaths := make([]string, len(p.config.CookbooksPaths)) - copy(cookbooksPaths, p.config.CookbooksPaths) - var err error Ui = ui @@ -128,7 +126,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } // Upload all cookbooks - for _, path := range cookbooksPaths { + for _, path := range p.config.CookbooksPaths { ui.Say(fmt.Sprintf("Copying cookbook path: %s", path)) err = UploadLocalDirectory(path, comm) if err != nil { @@ -149,6 +147,8 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } + return fmt.Errorf("SUCESS") + return nil } @@ -208,7 +208,7 @@ func CreateRemoteDirectory(path string, comm packer.Communicator) (err error) { } func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string, err error) { - Ui.Say(fmt.Sprintf("Creating Chef configuration file...")) + Ui.Say("Creating Chef configuration file...") remotePath := RemoteStagingPath + "/solo.rb" @@ -220,11 +220,24 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string // Write our contents to it writer := bufio.NewWriter(tf) - // Messy, messy... - cbPathsCat := "\"" + RemoteCookbookPath + "/" + strings.Join(cookbooksPaths, "\",\""+RemoteCookbookPath+"/") + "\"" - contents := "file_cache_path \"" + RemoteFileCachePath + "\"\ncookbook_path [" + cbPathsCat + "]\n" + var cookbooksPathsFull = make([]string, len(cookbooksPaths)) + for i, path := range cookbooksPaths { + cookbooksPathsFull[i] = "\"" + RemoteCookbookPath + "/" + path + "\"" + } - if _, err := writer.WriteString(contents); err != nil { + var contents bytes.Buffer + var soloRbText = ` + file_cache_path "{{.FileCachePath}}" + cookbook_path [{{.CookbookPath}}] +` + + t := template.Must(template.New("soloRb").Parse(soloRbText)) + t.Execute(&contents, map[string]string{ + "FileCachePath": RemoteFileCachePath, + "CookbookPath": strings.Join(cookbooksPathsFull, ","), + }) + + if _, err := writer.WriteString(contents.String()); err != nil { return "", fmt.Errorf("Error preparing solo.rb: %s", err) } @@ -245,12 +258,12 @@ func CreateSoloRb(cookbooksPaths []string, comm packer.Communicator) (str string if err != nil { return "", fmt.Errorf("Error uploading Chef Solo configuration file: %s", err) } - //executeCommand("sudo cat " + remotePath, comm) + return remotePath, nil } func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, comm packer.Communicator) (str string, err error) { - Ui.Say(fmt.Sprintf("Creating and uploading Chef attributes file")) + Ui.Say("Creating and uploading Chef attributes file") remotePath := RemoteStagingPath + "/node.json" var formattedRecipes []string @@ -305,7 +318,7 @@ func CreateAttributesJson(jsonAttrs map[string]interface{}, recipes []string, co } func InstallChefSolo(preventSudo bool, comm packer.Communicator) (err error) { - Ui.Say(fmt.Sprintf("Installing Chef Solo")) + Ui.Say("Installing Chef Solo") var command bytes.Buffer t := template.Must(template.New("install-chef").Parse("curl -L https://www.opscode.com/chef/install.sh | {{if .sudo}}sudo {{end}}bash")) @@ -329,7 +342,6 @@ func executeCommand(command string, comm packer.Communicator) (err error) { cmd.Stdout = stdout_w cmd.Stderr = stderr_w - //Ui.Say(fmt.Sprintf("Executing command: %s", cmd.Command)) log.Printf("Executing command: %s", cmd.Command) err = comm.Start(&cmd) if err != nil { From 428bc4d7451b39a871b004a36b1a59fc3d253374 Mon Sep 17 00:00:00 2001 From: James Van Dyke Date: Mon, 15 Jul 2013 16:38:26 -0400 Subject: [PATCH 11/11] Remove debugging statement. --- provisioner/chef-solo/provisioner.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 622587f1f..aab945663 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -147,8 +147,6 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return fmt.Errorf("Error running Chef Solo: %s", err) } - return fmt.Errorf("SUCESS") - return nil }