From 0bfbe02424a9edf4ff2e271ea65e967beb48729f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 15 Sep 2013 23:28:41 -0700 Subject: [PATCH] provisioner/chef-solo: ability to specify custom chef template --- CHANGELOG.md | 5 +++ provisioner/chef-solo/provisioner.go | 35 ++++++++++++++- provisioner/chef-solo/provisioner_test.go | 43 +++++++++++++++++++ .../docs/provisioners/chef-solo.html.markdown | 26 +++++++++++ 4 files changed, 107 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7598f3970..cf2ee3d54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.3.8 (unreleased) +FEATURES: + +* provisioner/chef-solo: Ability to specify a custom Chef configuration + template. + IMPROVEMENTS: * builder/amazon/*: Interrupts work while waiting for AMI to be ready. diff --git a/provisioner/chef-solo/provisioner.go b/provisioner/chef-solo/provisioner.go index fe96ad467..04bb472b8 100644 --- a/provisioner/chef-solo/provisioner.go +++ b/provisioner/chef-solo/provisioner.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/packer" + "io/ioutil" "os" "path/filepath" "strings" @@ -17,6 +18,7 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` + ConfigTemplate string `mapstructure:"config_template"` CookbookPaths []string `mapstructure:"cookbook_paths"` ExecuteCommand string `mapstructure:"execute_command"` InstallCommand string `mapstructure:"install_command"` @@ -80,7 +82,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs := common.CheckUnusedConfig(md) templates := map[string]*string{ - "staging_dir": &p.config.StagingDir, + "config_template": &p.config.ConfigTemplate, + "staging_dir": &p.config.StagingDir, } for n, ptr := range templates { @@ -121,6 +124,17 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + if p.config.ConfigTemplate != "" { + fi, err := os.Stat(p.config.ConfigTemplate) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad config template path: %s", err)) + } else if fi.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Config template path must be a file: %s", err)) + } + } + for _, path := range p.config.CookbookPaths { pFileInfo, err := os.Stat(path) @@ -216,7 +230,24 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, local cookbook_paths[i] = fmt.Sprintf(`"%s"`, path) } - configString, err := p.config.tpl.Process(DefaultConfigTemplate, &ConfigTemplate{ + // Read the template + tpl := DefaultConfigTemplate + if p.config.ConfigTemplate != "" { + f, err := os.Open(p.config.ConfigTemplate) + if err != nil { + return "", err + } + defer f.Close() + + tplBytes, err := ioutil.ReadAll(f) + if err != nil { + return "", err + } + + tpl = string(tplBytes) + } + + configString, err := p.config.tpl.Process(tpl, &ConfigTemplate{ CookbookPaths: strings.Join(cookbook_paths, ","), }) if err != nil { diff --git a/provisioner/chef-solo/provisioner_test.go b/provisioner/chef-solo/provisioner_test.go index 332718d01..dc8bea1e7 100644 --- a/provisioner/chef-solo/provisioner_test.go +++ b/provisioner/chef-solo/provisioner_test.go @@ -19,6 +19,49 @@ func TestProvisioner_Impl(t *testing.T) { } } +func TestProvisionerPrepare_configTemplate(t *testing.T) { + var err error + var p Provisioner + + // Test no config template + config := testConfig() + delete(config, "config_template") + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with a file + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + + config = testConfig() + config["config_template"] = tf.Name() + p = Provisioner{} + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with a directory + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + config = testConfig() + config["config_template"] = td + p = Provisioner{} + err = p.Prepare(config) + if err == nil { + t.Fatal("should have err") + } +} + func TestProvisionerPrepare_cookbookPaths(t *testing.T) { var p Provisioner diff --git a/website/source/docs/provisioners/chef-solo.html.markdown b/website/source/docs/provisioners/chef-solo.html.markdown index df6101b90..12f3c5724 100644 --- a/website/source/docs/provisioners/chef-solo.html.markdown +++ b/website/source/docs/provisioners/chef-solo.html.markdown @@ -32,6 +32,13 @@ The example below is fully functional and expects cookbooks in the The reference of available configuration options is listed below. No configuration is actually required, but at least `run_list` is recommended. +* `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. + * `cookbook_paths` (array of strings) - This is an array of paths to "cookbooks" directories on your local filesystem. These will be uploaded to the remote machine in the directory specified by the `staging_directory`. @@ -70,6 +77,25 @@ configuration is actually required, but at least `run_list` is recommended. this folder. If the permissions are not correct, use a shell provisioner prior to this to configure it properly. +## 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: + +``` +cookbook_path [{{.CookbookPaths}}] +``` + +This template is a [configuration template](/docs/templates/configuration-templates.html) +and has a set of variables available to use: + +* `CookbookPaths` is the set of cookbook paths ready to embedded directly + into a Ruby array to configure Chef. + ## Execute Command By default, Packer uses the following command (broken across multiple lines