diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index 04bb472b8..d06c3785f 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -20,6 +20,8 @@ type Config struct { ConfigTemplate string `mapstructure:"config_template"` CookbookPaths []string `mapstructure:"cookbook_paths"` + RolesPath string `mapstructure:"roles_path"` + DataBagsPath string `mapstructure:"data_bags_path"` ExecuteCommand string `mapstructure:"execute_command"` InstallCommand string `mapstructure:"install_command"` RemoteCookbookPaths []string `mapstructure:"remote_cookbook_paths"` @@ -37,7 +39,9 @@ type Provisioner struct { } type ConfigTemplate struct { - CookbookPaths string + CookbookPaths string + RolesPath string + DataBagsPath string } type ExecuteTemplate struct { @@ -143,6 +147,24 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs, fmt.Errorf("Bad cookbook path '%s': %s", path, err)) } } + + if p.config.RolesPath != "" { + pFileInfo, err := os.Stat(p.config.RolesPath) + + if err != nil || !pFileInfo.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad roles path '%s': %s", p.config.RolesPath, err)) + } + } + + if p.config.DataBagsPath != "" { + pFileInfo, err := os.Stat(p.config.DataBagsPath) + + if err != nil || !pFileInfo.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad data bags path '%s': %s", p.config.DataBagsPath, err)) + } + } // Process the user variables within the JSON and set the JSON. // Do this early so that we can validate and show errors. @@ -179,8 +201,24 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { cookbookPaths = append(cookbookPaths, targetPath) } + + rolesPath := "" + if p.config.RolesPath != "" { + rolesPath := fmt.Sprintf("%s/roles", p.config.StagingDir) + if err := p.uploadDirectory(ui, comm, rolesPath, p.config.RolesPath); err != nil { + return fmt.Errorf("Error uploading roles: %s", err) + } + } + + dataBagsPath := "" + if p.config.DataBagsPath != "" { + dataBagsPath := fmt.Sprintf("%s/data_bags", p.config.StagingDir) + if err := p.uploadDirectory(ui, comm, dataBagsPath, p.config.DataBagsPath); err != nil { + return fmt.Errorf("Error uploading data bags: %s", err) + } + } - configPath, err := p.createConfig(ui, comm, cookbookPaths) + configPath, err := p.createConfig(ui, comm, cookbookPaths, rolesPath, dataBagsPath) if err != nil { return fmt.Errorf("Error creating Chef config file: %s", err) } @@ -217,7 +255,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, localCookbooks []string) (string, error) { +func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, localCookbooks []string, rolesPath string, dataBagsPath string) (string, error) { ui.Message("Creating configuration file 'solo.rb'") cookbook_paths := make([]string, len(p.config.RemoteCookbookPaths)+len(localCookbooks)) @@ -229,6 +267,16 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local i = len(p.config.RemoteCookbookPaths) + i cookbook_paths[i] = fmt.Sprintf(`"%s"`, path) } + + roles_path := "" + if rolesPath != "" { + roles_path = fmt.Sprintf(`"%s"`, rolesPath) + } + + data_bags_path := "" + if dataBagsPath != "" { + data_bags_path = fmt.Sprintf(`"%s"`, dataBagsPath) + } // Read the template tpl := DefaultConfigTemplate @@ -249,6 +297,8 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{ CookbookPaths: strings.Join(cookbook_paths, ","), + RolesPath: roles_path, + DataBagsPath: data_bags_path, }) if err != nil { return "", err @@ -399,5 +449,11 @@ func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { } var DefaultConfigTemplate = ` -cookbook_path [{{.CookbookPaths}}] +cookbook_path [{{.CookbookPaths}}] +{{if .RolesPath != ""}} +role_path {{.RolesPath}} +{{end}} +{{if .DataBagsPath != ""}} +data_bag_path {{.DataBagsPath}} +{{end}} ` diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index dc8bea1e7..10a342dad 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -75,11 +75,25 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) { t.Fatalf("err: %s", err) } + rolesPath, err := ioutil.TempDir("", "roles") + if err != nil { + t.Fatalf("err: %s", err) + } + + dataBagsPath, err := ioutil.TempDir("", "data_bags") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(path1) defer os.Remove(path2) + defer os.Remove(rolesPath) + defer os.Remove(dataBagsPath) config := testConfig() config["cookbook_paths"] = []string{path1, path2} + config["roles_path"] = rolesPath + config["data_bags_path"] = dataBagsPath err = p.Prepare(config) if err != nil { @@ -93,6 +107,14 @@ func TestProvisionerPrepare_cookbookPaths(t *testing.T) { if p.config.CookbookPaths[0] != path1 || p.config.CookbookPaths[1] != path2 { t.Fatalf("unexpected: %#v", p.config.CookbookPaths) } + + if p.config.RolesPath != rolesPath { + t.Fatalf("unexpected: %#v", p.config.RolesPath) + } + + if p.config.DataBagsPath != dataBagsPath { + t.Fatalf("unexpected: %#v", p.config.DataBagsPath) + } } func TestProvisionerPrepare_json(t *testing.T) { diff --git a/website/source/docs/provisioners/chef-solo.html.markdown b/website/source/docs/provisioners/chef-solo.html.markdown index 12f3c5724..0b1c2204b 100644 --- a/website/source/docs/provisioners/chef-solo.html.markdown +++ b/website/source/docs/provisioners/chef-solo.html.markdown @@ -44,6 +44,14 @@ configuration is actually required, but at least `run_list` is recommended. to the remote machine in the directory specified by the `staging_directory`. By default, this is empty. +* `roles_path` (string) - The path to the "roles" directory on your local filesystem. + These will be uploaded to the remote machine in the directory specified by the + `staging_directory`. By default, this is empty. + +* `data_bags_path` (string) - The path to the "data_bags" directory on your local filesystem. + These will be uploaded to the remote machine in the directory specified by the + `staging_directory`. By default, this is empty. + * `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.