From 03a8e309ba5bdd08851f50ae3ff302bcb7ebe271 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 10:50:33 -0600 Subject: [PATCH 01/23] provisioner(converge): add bootstrapping --- provisioner/converge/provisioner.go | 130 ++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 provisioner/converge/provisioner.go diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go new file mode 100644 index 000000000..45d5078ce --- /dev/null +++ b/provisioner/converge/provisioner.go @@ -0,0 +1,130 @@ +// This package implements a provisioner for Packer that executes +// Converge to provision a remote machine + +package converge + +import ( + "bytes" + "errors" + "fmt" + "log" + "net/http" + + "strings" + + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +// Config for Converge provisioner +type Config struct { + common.PackerConfig `mapstructure:",squash"` + + NoBootstrap bool `mapstructure:"no_bootstrap"` // TODO: add a way to specify bootstrap version + + ctx interpolate.Context +} + +// Provisioner for Converge +type Provisioner struct { + config Config +} + +// Prepare provisioner somehow. TODO: actual docs +func (p *Provisioner) Prepare(raws ...interface{}) error { + err := config.Decode( + &p.config, + &config.DecodeOpts{ + Interpolate: true, + InterpolateContext: &p.config.ctx, + }, + raws..., + ) + + return err +} + +// Provision node somehow. TODO: actual docs +func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + ui.Say("Provisioning with Converge") + + // bootstrapping + if err := p.maybeBootstrap(ui, comm); err != nil { + return err // error messages are already user-friendly + } + + // check version (really, this make sure that Converge is installed before we try to run it) + if err := p.checkVersion(ui, comm); err != nil { + return err // error messages are already user-friendly + } + + return nil +} + +func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) error { + if p.config.NoBootstrap { + return nil + } + ui.Message("bootstrapping converge") + + bootstrap, err := http.Get("https://get.converge.sh") + defer bootstrap.Body.Close() + if err != nil { + return fmt.Errorf("Error downloading bootstrap script: %s", err) // TODO: is github.com/pkg/error allowed? + } + if err := comm.Upload("/tmp/install-converge.sh", bootstrap.Body, nil); err != nil { + return fmt.Errorf("Error uploading script: %s", err) + } + + var out bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: "/bin/sh /tmp/install-converge.sh", + Stdin: nil, + Stdout: &out, + Stderr: &out, + } + + if err = comm.Start(cmd); err != nil { + return fmt.Errorf("Error bootstrapping converge: %s", err) + } + + cmd.Wait() + if cmd.ExitStatus != 0 { + ui.Error(out.String()) + return errors.New("Error bootstrapping converge") + } + + ui.Message(strings.TrimSpace(out.String())) + return nil +} + +func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error { + var versionOut bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: "converge version", + Stdin: nil, + Stdout: &versionOut, + Stderr: &versionOut, + } + if err := comm.Start(cmd); err != nil || cmd.ExitStatus != 0 { + return fmt.Errorf("Error running `converge version`: %s", err) + } + + cmd.Wait() + if cmd.ExitStatus != 0 { + ui.Error(versionOut.String()) + ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) // TODO: check for 127 + return errors.New("Error running `converge version`") + } + + ui.Say(fmt.Sprintf("Provisioning with %s", strings.TrimSpace(versionOut.String()))) + + return nil +} + +// Cancel the provisioning process +func (p *Provisioner) Cancel() { + log.Println("cancel called in Converge provisioner") +} From e2daefab71c9547ab13ee83be40ea0f875655bee Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 11:20:20 -0600 Subject: [PATCH 02/23] provisioner(converge): improve error messages when Converge isn't found --- provisioner/converge/provisioner.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 45d5078ce..49557a67d 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -113,9 +113,17 @@ func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error } cmd.Wait() - if cmd.ExitStatus != 0 { + if cmd.ExitStatus == 127 { + ui.Error("Could not determine Converge version. Is it installed and in PATH?") + if p.config.NoBootstrap { + ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") + } + + return errors.New("could not determine Converge version") + + } else if cmd.ExitStatus != 0 { ui.Error(versionOut.String()) - ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) // TODO: check for 127 + ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) return errors.New("Error running `converge version`") } From 4f0034e5743ddce0e441face6513e95152deefdf Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 11:29:36 -0600 Subject: [PATCH 03/23] provisioner(converge): transfer module directories --- provisioner/converge/provisioner.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 49557a67d..f00e04d82 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -22,11 +22,22 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` + // Bootstrapping NoBootstrap bool `mapstructure:"no_bootstrap"` // TODO: add a way to specify bootstrap version + // Modules + ModuleDirs []ModuleDir `mapstructure:"module_dirs"` + ctx interpolate.Context } +// ModuleDir is a directory to transfer to the remote system +type ModuleDir struct { + Source string `mapstructure:"source"` + Destination string `mapstructure:"destination"` + Exclude []string `mapstructure:"exclude"` +} + // Provisioner for Converge type Provisioner struct { config Config @@ -60,6 +71,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return err // error messages are already user-friendly } + // send module directories to the remote host + if err := p.sendModuleDirectories(ui, comm); err != nil { + return err // error messages are already user-friendly + } + return nil } @@ -132,6 +148,17 @@ func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error return nil } +func (p *Provisioner) sendModuleDirectories(ui packer.Ui, comm packer.Communicator) error { + for _, dir := range p.config.ModuleDirs { + if err := comm.UploadDir(dir.Destination, dir.Source, dir.Exclude); err != nil { + return fmt.Errorf("Could not upload %q: %s", dir.Source, err) + } + ui.Message(fmt.Sprintf("transferred %q to %q", dir.Source, dir.Destination)) + } + + return nil +} + // Cancel the provisioning process func (p *Provisioner) Cancel() { log.Println("cancel called in Converge provisioner") From 73d5593242798f868fcb0f80bc9daf853a7fae7d Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 11:41:52 -0600 Subject: [PATCH 04/23] provisioner(converge): add validate for source and destination --- provisioner/converge/provisioner.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index f00e04d82..57e08ad07 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -53,6 +53,19 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, raws..., ) + if err != nil { + return err + } + + // validate sources and destinations + for i, dir := range p.config.ModuleDirs { + if dir.Source == "" { + return fmt.Errorf("Source (\"source\" key) is required in Converge module dir #%d", i) + } + if dir.Destination == "" { + return fmt.Errorf("Destination (\"destination\" key) is required in Converge module dir #%d", i) + } + } return err } From eca869001847a59b745e34d687f418578cf01dff Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 12:13:51 -0600 Subject: [PATCH 05/23] provisioner(converge): add actual provisioning step --- provisioner/converge/provisioner.go | 68 ++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 57e08ad07..aff92cae6 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -12,6 +12,8 @@ import ( "strings" + "encoding/json" + "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/packer" @@ -27,6 +29,7 @@ type Config struct { // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` + Modules []Module `mapstructure:"modules"` ctx interpolate.Context } @@ -38,6 +41,13 @@ type ModuleDir struct { Exclude []string `mapstructure:"exclude"` } +// Module contains information needed to run a module +type Module struct { + Module string `mapstructure:"module"` + Directory string `mapstructure:"directory"` + Params map[string]string `mapstucture:"params"` +} + // Provisioner for Converge type Provisioner struct { config Config @@ -67,6 +77,19 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + // validate modules + if len(p.config.Modules) == 0 { + return errors.New("Converge requires at least one module (\"modules\" key) to provision the system") + } + for i, module := range p.config.Modules { + if module.Module == "" { + return fmt.Errorf("Module (\"module\" key) is required in Converge module #%d", i) + } + if module.Directory == "" { + module.Directory = "/tmp" + } + } + return err } @@ -89,6 +112,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return err // error messages are already user-friendly } + // apply all the modules + if err := p.applyModules(ui, comm); err != nil { + return err // error messages are already user-friendly + } + return nil } @@ -137,7 +165,7 @@ func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error Stdout: &versionOut, Stderr: &versionOut, } - if err := comm.Start(cmd); err != nil || cmd.ExitStatus != 0 { + if err := comm.Start(cmd); err != nil { return fmt.Errorf("Error running `converge version`: %s", err) } @@ -172,6 +200,44 @@ func (p *Provisioner) sendModuleDirectories(ui packer.Ui, comm packer.Communicat return nil } +func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error { + for _, module := range p.config.Modules { + // create params JSON file + params, err := json.Marshal(module.Params) + if err != nil { + return fmt.Errorf("Could not marshal parameters as JSON: %s", err) + } + + // run Converge in the specified directory + var runOut bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf( + "cd %s && converge apply --local --log-level=WARNING --paramsJSON '%s' %s", + module.Directory, + string(params), + module.Module, + ), + Stdin: nil, + Stdout: &runOut, + Stderr: &runOut, + } + if err := comm.Start(cmd); err != nil { + return fmt.Errorf("Error applying %q: %s", module.Module, err) + } + + cmd.Wait() + if cmd.ExitStatus != 0 { + ui.Error(strings.TrimSpace(runOut.String())) + ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) + return fmt.Errorf("Error applying %q", module.Module) + } + + ui.Message(strings.TrimSpace(runOut.String())) + } + + return nil +} + // Cancel the provisioning process func (p *Provisioner) Cancel() { log.Println("cancel called in Converge provisioner") From b8849a9c2d34fe87586aa44e084c33f274d47ac8 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 12:22:17 -0600 Subject: [PATCH 06/23] provisioner(converge): add version specification for bootstrapping --- provisioner/converge/provisioner.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index aff92cae6..f71e7282a 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -14,18 +14,23 @@ import ( "encoding/json" + "regexp" + "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" ) +var versionRegex = regexp.MustCompile(`[\.\-\d\w]*`) + // Config for Converge provisioner type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - NoBootstrap bool `mapstructure:"no_bootstrap"` // TODO: add a way to specify bootstrap version + NoBootstrap bool `mapstructure:"no_bootstrap"` + Version string `mapstructure:"version"` // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` @@ -67,6 +72,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return err } + // validate version + if !versionRegex.Match([]byte(p.config.Version)) { + return fmt.Errorf("Invalid Converge version %q specified. Valid versions include only letters, numbers, dots, and dashes", p.config.Version) + } + // validate sources and destinations for i, dir := range p.config.ModuleDirs { if dir.Source == "" { @@ -135,9 +145,15 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err return fmt.Errorf("Error uploading script: %s", err) } + // construct command + command := "/bin/sh /tmp/install-converge.sh" + if p.config.Version != "" { + command += " -v " + p.config.Version + } + var out bytes.Buffer cmd := &packer.RemoteCmd{ - Command: "/bin/sh /tmp/install-converge.sh", + Command: command, Stdin: nil, Stdout: &out, Stderr: &out, From 73252b9a9b586f03b5c88e31f7dfa784c43cbbe6 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 12:24:06 -0600 Subject: [PATCH 07/23] provisioner(converge): reverse the meaning of bootstrap --- provisioner/converge/provisioner.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index f71e7282a..78e607652 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -29,8 +29,8 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - NoBootstrap bool `mapstructure:"no_bootstrap"` - Version string `mapstructure:"version"` + Bootstrap bool `mapstructure:"bootstrap"` + Version string `mapstructure:"version"` // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` @@ -131,7 +131,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) error { - if p.config.NoBootstrap { + if !p.config.Bootstrap { return nil } ui.Message("bootstrapping converge") @@ -188,7 +188,7 @@ func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error cmd.Wait() if cmd.ExitStatus == 127 { ui.Error("Could not determine Converge version. Is it installed and in PATH?") - if p.config.NoBootstrap { + if !p.config.Bootstrap { ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") } From b46a402a187dacd3e5b3fa08e9eb21095637dff1 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 14:47:13 -0600 Subject: [PATCH 08/23] provisioner(converge): add tests for Prepare --- provisioner/converge/provisioner.go | 4 +- provisioner/converge/provisioner_test.go | 124 +++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 provisioner/converge/provisioner_test.go diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 78e607652..85dd1da6f 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -22,7 +22,7 @@ import ( "github.com/mitchellh/packer/template/interpolate" ) -var versionRegex = regexp.MustCompile(`[\.\-\d\w]*`) +var versionRegex = regexp.MustCompile(`^[\.\-\da-zA-Z]*$`) // Config for Converge provisioner type Config struct { @@ -96,7 +96,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return fmt.Errorf("Module (\"module\" key) is required in Converge module #%d", i) } if module.Directory == "" { - module.Directory = "/tmp" + p.config.Modules[i].Directory = "/tmp" } } diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go new file mode 100644 index 000000000..1be925fef --- /dev/null +++ b/provisioner/converge/provisioner_test.go @@ -0,0 +1,124 @@ +package converge + +import ( + "strings" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "bootstrap": false, + "version": "", + "module_dirs": []map[string]interface{}{ + { + "source": "from", + "destination": "/opt/converge", + }, + }, + "modules": []map[string]interface{}{ + { + "module": "/opt/converge/test.hcl", + }, + }, + } +} + +func TestProvisioner_Impl(t *testing.T) { + var raw interface{} + raw = &Provisioner{} + if _, ok := raw.(packer.Provisioner); !ok { + t.Fatal("must be a Provisioner") + } +} + +func TestProvisionerPrepare(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + var p Provisioner + config := testConfig() + + // delete any keys that we're testing here to make sure they're actually + // being set by `Prepare` + delete(config["modules"].([]map[string]interface{})[0], "directory") + + if err := p.Prepare(config); err != nil { + t.Errorf("err: %s", err) + } + + if p.config.Modules[0].Directory != "/tmp" { + t.Errorf("unexpected module directory: %s", p.config.Modules[0].Directory) + } + }) + + t.Run("validate", func(t *testing.T) { + t.Run("bad version", func(t *testing.T) { + var p Provisioner + config := testConfig() + config["version"] = "bad version with spaces" + + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if !strings.HasPrefix(err.Error(), "Invalid Converge version") { + t.Errorf("expected error starting with \"Invalid Converge version\". Got: %s", err) + } + }) + + t.Run("module dir", func(t *testing.T) { + t.Run("missing source", func(t *testing.T) { + var p Provisioner + config := testConfig() + delete(config["module_dirs"].([]map[string]interface{})[0], "source") + + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if err.Error() != "Source (\"source\" key) is required in Converge module dir #0" { + t.Errorf("bad error message: %s", err) + } + }) + + t.Run("missing destination", func(t *testing.T) { + var p Provisioner + config := testConfig() + delete(config["module_dirs"].([]map[string]interface{})[0], "destination") + + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if err.Error() != "Destination (\"destination\" key) is required in Converge module dir #0" { + t.Errorf("bad error message: %s", err) + } + }) + }) + + t.Run("modules", func(t *testing.T) { + t.Run("none specified", func(t *testing.T) { + var p Provisioner + config := testConfig() + delete(config, "modules") + + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if err.Error() != "Converge requires at least one module (\"modules\" key) to provision the system" { + t.Errorf("bad error message: %s", err) + } + }) + + t.Run("missing module", func(t *testing.T) { + var p Provisioner + config := testConfig() + delete(config["modules"].([]map[string]interface{})[0], "module") + + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if err.Error() != "Module (\"module\" key) is required in Converge module #0" { + t.Errorf("bad error message: %s", err) + } + }) + }) + }) +} From 549ff50a3c390a9f6bed050f00ae047f14bb3765 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 14:58:41 -0600 Subject: [PATCH 09/23] provisioner(converge): handle http error --- provisioner/converge/provisioner.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 85dd1da6f..194e85083 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -137,13 +137,15 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err ui.Message("bootstrapping converge") bootstrap, err := http.Get("https://get.converge.sh") - defer bootstrap.Body.Close() if err != nil { return fmt.Errorf("Error downloading bootstrap script: %s", err) // TODO: is github.com/pkg/error allowed? } if err := comm.Upload("/tmp/install-converge.sh", bootstrap.Body, nil); err != nil { return fmt.Errorf("Error uploading script: %s", err) } + if err := bootstrap.Body.Close(); err != nil { + return fmt.Errorf("Error getting bootstrap script: %s", err) + } // construct command command := "/bin/sh /tmp/install-converge.sh" From 6365e40126a73e1079b99283431e2bdddf69fe22 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 15:57:11 -0600 Subject: [PATCH 10/23] provisioners(converge): document --- .../source/docs/provisioners/converge.html.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 website/source/docs/provisioners/converge.html.md diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md new file mode 100644 index 000000000..6a26fbb1f --- /dev/null +++ b/website/source/docs/provisioners/converge.html.md @@ -0,0 +1,81 @@ +--- +description: |- + The Converge Packer provisioner uses Converge modules to provision the machine. +layout: docs +page_title: Converge Provisioner +... + +# Converge Provisioner + +Type: `converge` + +The [Converge](http://converge.aster.is) Packer provisioner uses Converge +modules to provision the machine. It uploads module directories to use as +source, or you can use remote modules. + +The provisioner can optionally bootstrap the Converge client/server binary onto +new images. + +## Basic Example + +The example below is fully functional. + +``` {.javascript} +{ + "type": "converge", + "modules": [ + { + "module": "https://raw.githubusercontent.com/asteris-llc/converge/master/samples/fileContent.hcl", + "params": { + "message": "Hello, Packer!" + } + } + ] +} +``` + +## Configuration Reference + +The reference of available configuration options is listed below. The only +required element is "modules", of which there must be at least one. Every other +option is optional. + +- `modules` (array of module specifications) - The root modules to run by + Converge. See below for the specification. + +Optional parameters: + +- `bootstrap` (boolean) - Set to allow the provisioner to download the latest + Converge bootstrap script and the specified `version` of Converge from the + internet. + +- `version` (string) - Set to a [released Converge version](https://github.com/asteris-llc/converge/releases) for bootstrap. + +- `module_dirs` (array of directory specifications) - Module directories to + transfer to the remote host for execution. See below for the specification. + +### Modules + +Modules control what Converge applies to your system. The `modules` key should +be a list of objects with the following keys. Of these, only `module` is +required. + +- `module` (string) - the path (or URL) to the root module. + +- `directory` (string) - the directory to run Converge in. If not set, the + provisioner will use `/tmp` by default. + +- `params` (maps of string to string) - parameters to pass into the root module. + +### Module Directories + +The provisioner can transfer module directories to the remote host for +provisioning. Of these fields, `source` and `destination` are required in every +directory. + +- `source` (string) - the path to the folder on the local machine. + +- `destination` (string) - the path to the folder on the remote machine. Parent + directories will not be created; use the shell module to do this. + +- `exclude` (array of string) - files and directories to exclude from transfer. From e6ba4c1929bc5b223c46864035fc4907f58a641c Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:03:35 -0600 Subject: [PATCH 11/23] provisioner(converge): remove version check --- provisioner/converge/provisioner.go | 47 ++++++----------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 194e85083..17fab3f5b 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -112,11 +112,6 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { return err // error messages are already user-friendly } - // check version (really, this make sure that Converge is installed before we try to run it) - if err := p.checkVersion(ui, comm); err != nil { - return err // error messages are already user-friendly - } - // send module directories to the remote host if err := p.sendModuleDirectories(ui, comm); err != nil { return err // error messages are already user-friendly @@ -175,38 +170,6 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err return nil } -func (p *Provisioner) checkVersion(ui packer.Ui, comm packer.Communicator) error { - var versionOut bytes.Buffer - cmd := &packer.RemoteCmd{ - Command: "converge version", - Stdin: nil, - Stdout: &versionOut, - Stderr: &versionOut, - } - if err := comm.Start(cmd); err != nil { - return fmt.Errorf("Error running `converge version`: %s", err) - } - - cmd.Wait() - if cmd.ExitStatus == 127 { - ui.Error("Could not determine Converge version. Is it installed and in PATH?") - if !p.config.Bootstrap { - ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") - } - - return errors.New("could not determine Converge version") - - } else if cmd.ExitStatus != 0 { - ui.Error(versionOut.String()) - ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) - return errors.New("Error running `converge version`") - } - - ui.Say(fmt.Sprintf("Provisioning with %s", strings.TrimSpace(versionOut.String()))) - - return nil -} - func (p *Provisioner) sendModuleDirectories(ui packer.Ui, comm packer.Communicator) error { for _, dir := range p.config.ModuleDirs { if err := comm.UploadDir(dir.Destination, dir.Source, dir.Exclude); err != nil { @@ -244,7 +207,15 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error } cmd.Wait() - if cmd.ExitStatus != 0 { + if cmd.ExitStatus == 127 { + ui.Error("Could not find Converge. Is it installed and in PATH?") + if !p.config.Bootstrap { + ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") + } + + return errors.New("Could not find Converge") + + } else if cmd.ExitStatus != 0 { ui.Error(strings.TrimSpace(runOut.String())) ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) return fmt.Errorf("Error applying %q", module.Module) From de918ac7a7c8a2caa6f28c8862e05b4df2976dce Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:07:16 -0600 Subject: [PATCH 12/23] provisioner(converge): rename directory to working_directory --- provisioner/converge/provisioner.go | 12 ++++++------ provisioner/converge/provisioner_test.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 17fab3f5b..013b190f7 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -48,9 +48,9 @@ type ModuleDir struct { // Module contains information needed to run a module type Module struct { - Module string `mapstructure:"module"` - Directory string `mapstructure:"directory"` - Params map[string]string `mapstucture:"params"` + Module string `mapstructure:"module"` + WorkingDirectory string `mapstructure:"working_directory"` + Params map[string]string `mapstucture:"params"` } // Provisioner for Converge @@ -95,8 +95,8 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { if module.Module == "" { return fmt.Errorf("Module (\"module\" key) is required in Converge module #%d", i) } - if module.Directory == "" { - p.config.Modules[i].Directory = "/tmp" + if module.WorkingDirectory == "" { + p.config.Modules[i].WorkingDirectory = "/tmp" } } @@ -194,7 +194,7 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error cmd := &packer.RemoteCmd{ Command: fmt.Sprintf( "cd %s && converge apply --local --log-level=WARNING --paramsJSON '%s' %s", - module.Directory, + module.WorkingDirectory, string(params), module.Module, ), diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index 1be925fef..687210139 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -46,8 +46,8 @@ func TestProvisionerPrepare(t *testing.T) { t.Errorf("err: %s", err) } - if p.config.Modules[0].Directory != "/tmp" { - t.Errorf("unexpected module directory: %s", p.config.Modules[0].Directory) + if p.config.Modules[0].WorkingDirectory != "/tmp" { + t.Errorf("unexpected module directory: %s", p.config.Modules[0].WorkingDirectory) } }) From 3658a0b6b50a196f0a3dea0c736e2e5223693125 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:31:12 -0600 Subject: [PATCH 13/23] provisioner(converge): remove log line --- provisioner/converge/provisioner.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 013b190f7..d7beef4de 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -229,5 +229,6 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error // Cancel the provisioning process func (p *Provisioner) Cancel() { - log.Println("cancel called in Converge provisioner") + // there's not an awful lot we can do to cancel Converge at the moment. + // The default semantics are fine. } From fe4b972d327c835ce7e3cf6a13de8c567faadbcd Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:32:19 -0600 Subject: [PATCH 14/23] provisioner(converge): flatten execution fields --- provisioner/converge/provisioner.go | 101 +++++++++++------------ provisioner/converge/provisioner_test.go | 47 ++++------- 2 files changed, 61 insertions(+), 87 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index d7beef4de..d4fe0b743 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -7,7 +7,6 @@ import ( "bytes" "errors" "fmt" - "log" "net/http" "strings" @@ -34,7 +33,11 @@ type Config struct { // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` - Modules []Module `mapstructure:"modules"` + + // Execution + Module string `mapstructure:"module"` + WorkingDirectory string `mapstructure:"working_directory"` + Params map[string]string `mapstucture:"params"` ctx interpolate.Context } @@ -46,13 +49,6 @@ type ModuleDir struct { Exclude []string `mapstructure:"exclude"` } -// Module contains information needed to run a module -type Module struct { - Module string `mapstructure:"module"` - WorkingDirectory string `mapstructure:"working_directory"` - Params map[string]string `mapstucture:"params"` -} - // Provisioner for Converge type Provisioner struct { config Config @@ -65,6 +61,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { &config.DecodeOpts{ Interpolate: true, InterpolateContext: &p.config.ctx, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{"execute_command"}, + }, }, raws..., ) @@ -88,16 +87,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } // validate modules - if len(p.config.Modules) == 0 { - return errors.New("Converge requires at least one module (\"modules\" key) to provision the system") + if p.config.Module == "" { + return errors.New("Converge requires a module to provision the system") } - for i, module := range p.config.Modules { - if module.Module == "" { - return fmt.Errorf("Module (\"module\" key) is required in Converge module #%d", i) - } - if module.WorkingDirectory == "" { - p.config.Modules[i].WorkingDirectory = "/tmp" - } + + if p.config.WorkingDirectory == "" { + p.config.WorkingDirectory = "/tmp" } return err @@ -182,48 +177,46 @@ func (p *Provisioner) sendModuleDirectories(ui packer.Ui, comm packer.Communicat } func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error { - for _, module := range p.config.Modules { - // create params JSON file - params, err := json.Marshal(module.Params) - if err != nil { - return fmt.Errorf("Could not marshal parameters as JSON: %s", err) - } - - // run Converge in the specified directory - var runOut bytes.Buffer - cmd := &packer.RemoteCmd{ - Command: fmt.Sprintf( - "cd %s && converge apply --local --log-level=WARNING --paramsJSON '%s' %s", - module.WorkingDirectory, - string(params), - module.Module, - ), - Stdin: nil, - Stdout: &runOut, - Stderr: &runOut, - } - if err := comm.Start(cmd); err != nil { - return fmt.Errorf("Error applying %q: %s", module.Module, err) - } - - cmd.Wait() - if cmd.ExitStatus == 127 { - ui.Error("Could not find Converge. Is it installed and in PATH?") - if !p.config.Bootstrap { - ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") - } + // create params JSON file + params, err := json.Marshal(p.config.Params) + if err != nil { + return fmt.Errorf("Could not marshal parameters as JSON: %s", err) + } - return errors.New("Could not find Converge") + // run Converge in the specified directory + var runOut bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: fmt.Sprintf( + "cd %s && converge apply --local --log-level=WARNING --paramsJSON '%s' %s", + p.config.WorkingDirectory, + string(params), + p.config.Module, + ), + Stdin: nil, + Stdout: &runOut, + Stderr: &runOut, + } + if err := comm.Start(cmd); err != nil { + return fmt.Errorf("Error applying %q: %s", p.config.Module, err) + } - } else if cmd.ExitStatus != 0 { - ui.Error(strings.TrimSpace(runOut.String())) - ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) - return fmt.Errorf("Error applying %q", module.Module) + cmd.Wait() + if cmd.ExitStatus == 127 { + ui.Error("Could not find Converge. Is it installed and in PATH?") + if !p.config.Bootstrap { + ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") } - ui.Message(strings.TrimSpace(runOut.String())) + return errors.New("Could not find Converge") + + } else if cmd.ExitStatus != 0 { + ui.Error(strings.TrimSpace(runOut.String())) + ui.Error(fmt.Sprintf("exited with error code %d", cmd.ExitStatus)) + return fmt.Errorf("Error applying %q", p.config.Module) } + ui.Message(strings.TrimSpace(runOut.String())) + return nil } diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index 687210139..fd32b104a 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -17,11 +17,7 @@ func testConfig() map[string]interface{} { "destination": "/opt/converge", }, }, - "modules": []map[string]interface{}{ - { - "module": "/opt/converge/test.hcl", - }, - }, + "module": "/opt/converge/test.hcl", } } @@ -40,14 +36,14 @@ func TestProvisionerPrepare(t *testing.T) { // delete any keys that we're testing here to make sure they're actually // being set by `Prepare` - delete(config["modules"].([]map[string]interface{})[0], "directory") + delete(config, "working_directory") if err := p.Prepare(config); err != nil { t.Errorf("err: %s", err) } - if p.config.Modules[0].WorkingDirectory != "/tmp" { - t.Errorf("unexpected module directory: %s", p.config.Modules[0].WorkingDirectory) + if p.config.WorkingDirectory != "/tmp" { + t.Errorf("unexpected module directory: %s", p.config.WorkingDirectory) } }) @@ -93,32 +89,17 @@ func TestProvisionerPrepare(t *testing.T) { }) }) - t.Run("modules", func(t *testing.T) { - t.Run("none specified", func(t *testing.T) { - var p Provisioner - config := testConfig() - delete(config, "modules") - - err := p.Prepare(config) - if err == nil { - t.Error("expected error") - } else if err.Error() != "Converge requires at least one module (\"modules\" key) to provision the system" { - t.Errorf("bad error message: %s", err) - } - }) - - t.Run("missing module", func(t *testing.T) { - var p Provisioner - config := testConfig() - delete(config["modules"].([]map[string]interface{})[0], "module") + t.Run("no module specified", func(t *testing.T) { + var p Provisioner + config := testConfig() + delete(config, "module") - err := p.Prepare(config) - if err == nil { - t.Error("expected error") - } else if err.Error() != "Module (\"module\" key) is required in Converge module #0" { - t.Errorf("bad error message: %s", err) - } - }) + err := p.Prepare(config) + if err == nil { + t.Error("expected error") + } else if err.Error() != "Converge requires a module to provision the system" { + t.Errorf("bad error message: %s", err) + } }) }) } From 3311d3a48d47c79a7bcb0efc8e87d0e401708444 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:48:11 -0600 Subject: [PATCH 15/23] provisioner(converge): interpolate execute_command --- provisioner/converge/provisioner.go | 49 +++++++++++++++--------- provisioner/converge/provisioner_test.go | 37 ++++++++++++------ 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index d4fe0b743..aabaf45e7 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -38,6 +38,7 @@ type Config struct { Module string `mapstructure:"module"` WorkingDirectory string `mapstructure:"working_directory"` Params map[string]string `mapstucture:"params"` + ExecuteCommand string `mapstructure:"execute_command"` ctx interpolate.Context } @@ -71,6 +72,20 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { return err } + // require a single module + if p.config.Module == "" { + return errors.New("Converge requires a module to provision the system") + } + + // set defaults + if p.config.WorkingDirectory == "" { + p.config.WorkingDirectory = "/tmp" + } + + if p.config.ExecuteCommand == "" { + p.config.ExecuteCommand = "cd {{.WorkingDirectory}} && sudo converge apply --local --log-level=WARNING --paramsJSON '{{.ParamsJSON}}' {{.Module}}" + } + // validate version if !versionRegex.Match([]byte(p.config.Version)) { return fmt.Errorf("Invalid Converge version %q specified. Valid versions include only letters, numbers, dots, and dashes", p.config.Version) @@ -86,15 +101,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } - // validate modules - if p.config.Module == "" { - return errors.New("Converge requires a module to provision the system") - } - - if p.config.WorkingDirectory == "" { - p.config.WorkingDirectory = "/tmp" - } - return err } @@ -183,18 +189,25 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error return fmt.Errorf("Could not marshal parameters as JSON: %s", err) } + p.config.ctx.Data = struct { + ParamsJSON, WorkingDirectory, Module string + }{ + ParamsJSON: string(params), + WorkingDirectory: p.config.WorkingDirectory, + Module: p.config.Module, + } + command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) + if err != nil { + return fmt.Errorf("Could not interpolate execute command: %s", err) + } + // run Converge in the specified directory var runOut bytes.Buffer cmd := &packer.RemoteCmd{ - Command: fmt.Sprintf( - "cd %s && converge apply --local --log-level=WARNING --paramsJSON '%s' %s", - p.config.WorkingDirectory, - string(params), - p.config.Module, - ), - Stdin: nil, - Stdout: &runOut, - Stderr: &runOut, + Command: command, + Stdin: nil, + Stdout: &runOut, + Stderr: &runOut, } if err := comm.Start(cmd); err != nil { return fmt.Errorf("Error applying %q: %s", p.config.Module, err) diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index fd32b104a..7a7e5ac09 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -31,20 +31,35 @@ func TestProvisioner_Impl(t *testing.T) { func TestProvisionerPrepare(t *testing.T) { t.Run("defaults", func(t *testing.T) { - var p Provisioner - config := testConfig() + t.Run("working_directory", func(t *testing.T) { + var p Provisioner + config := testConfig() + + delete(config, "working_directory") + + if err := p.Prepare(config); err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.WorkingDirectory != "/tmp" { + t.Fatalf("unexpected module directory: %s", p.config.WorkingDirectory) + } + }) - // delete any keys that we're testing here to make sure they're actually - // being set by `Prepare` - delete(config, "working_directory") + t.Run("execute_command", func(t *testing.T) { + var p Provisioner + config := testConfig() - if err := p.Prepare(config); err != nil { - t.Errorf("err: %s", err) - } + delete(config, "execute_command") - if p.config.WorkingDirectory != "/tmp" { - t.Errorf("unexpected module directory: %s", p.config.WorkingDirectory) - } + if err := p.Prepare(config); err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.ExecuteCommand == "" { + t.Fatal("execute command unexpectedly blank") + } + }) }) t.Run("validate", func(t *testing.T) { From 30a393d4c2cbd97c51ee2e1215799b68dd1c2ca0 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Tue, 27 Dec 2016 16:50:44 -0600 Subject: [PATCH 16/23] provisioner(converge): update documentation with new schema --- .../source/docs/provisioners/converge.html.md | 55 +++++++++++-------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index 6a26fbb1f..32624b936 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -23,25 +23,19 @@ The example below is fully functional. ``` {.javascript} { "type": "converge", - "modules": [ - { - "module": "https://raw.githubusercontent.com/asteris-llc/converge/master/samples/fileContent.hcl", - "params": { - "message": "Hello, Packer!" - } - } - ] + "module": "https://raw.githubusercontent.com/asteris-llc/converge/master/samples/fileContent.hcl", + "params": { + "message": "Hello, Packer!" + } } ``` ## Configuration Reference The reference of available configuration options is listed below. The only -required element is "modules", of which there must be at least one. Every other -option is optional. +required element is "module". Every other option is optional. -- `modules` (array of module specifications) - The root modules to run by - Converge. See below for the specification. +- `module` (string) - Path (or URL) to the root module that Converge will apply. Optional parameters: @@ -54,19 +48,15 @@ Optional parameters: - `module_dirs` (array of directory specifications) - Module directories to transfer to the remote host for execution. See below for the specification. -### Modules - -Modules control what Converge applies to your system. The `modules` key should -be a list of objects with the following keys. Of these, only `module` is -required. - -- `module` (string) - the path (or URL) to the root module. - -- `directory` (string) - the directory to run Converge in. If not set, the - provisioner will use `/tmp` by default. +- `working_directory` (string) - The directory that Converge will change to + before execution. - `params` (maps of string to string) - parameters to pass into the root module. +- `execute_command` (string) - the command used to execute Converge. This has + various + [configuration template variables](/docs/templates/configuration-templates.html) available. + ### Module Directories The provisioner can transfer module directories to the remote host for @@ -79,3 +69,24 @@ directory. directories will not be created; use the shell module to do this. - `exclude` (array of string) - files and directories to exclude from transfer. + +### Execute Command + +By default, Packer uses the following command (broken across multiple lines for readability) to execute Converge: + +``` {.liquid} +cd {{.WorkingDirectory}} && \ +sudo converge apply \ + --local \ + --log-level=WARNING \ + --paramsJSON '{{.ParamsJSON}}' \ + {{.Module}} +``` + +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: + +- `WorkingDirectory` - `directory` from the configuration +- `ParamsJSON` - The unquoted JSONified form of `params` from the configuration. +- `Module` - `module` from the configuration. From 843731d98dfa9c2dc8cfc92c4a49a9134c2179af Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 08:19:03 -0600 Subject: [PATCH 17/23] provisioner(converge): add prevent_sudo --- provisioner/converge/provisioner.go | 5 ++++- website/source/docs/provisioners/converge.html.md | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index aabaf45e7..55233b504 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -39,6 +39,7 @@ type Config struct { WorkingDirectory string `mapstructure:"working_directory"` Params map[string]string `mapstucture:"params"` ExecuteCommand string `mapstructure:"execute_command"` + PreventSudo bool `mapstructure:"prevent_sudo"` ctx interpolate.Context } @@ -83,7 +84,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = "cd {{.WorkingDirectory}} && sudo converge apply --local --log-level=WARNING --paramsJSON '{{.ParamsJSON}}' {{.Module}}" + p.config.ExecuteCommand = "cd {{.WorkingDirectory}} && {{if .Sudo}}sudo {{end}}converge apply --local --log-level=WARNING --paramsJSON '{{.ParamsJSON}}' {{.Module}}" } // validate version @@ -191,10 +192,12 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error p.config.ctx.Data = struct { ParamsJSON, WorkingDirectory, Module string + Sudo bool }{ ParamsJSON: string(params), WorkingDirectory: p.config.WorkingDirectory, Module: p.config.Module, + Sudo: !p.config.PreventSudo, } command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) if err != nil { diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index 32624b936..057604324 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -57,6 +57,9 @@ Optional parameters: various [configuration template variables](/docs/templates/configuration-templates.html) available. +- `prevent_sudo` (bool) - stop Converge from running with adminstrator + privileges via sudo + ### Module Directories The provisioner can transfer module directories to the remote host for @@ -76,7 +79,7 @@ By default, Packer uses the following command (broken across multiple lines for ``` {.liquid} cd {{.WorkingDirectory}} && \ -sudo converge apply \ +{{if .Sudo}}sudo {{end}}converge apply \ --local \ --log-level=WARNING \ --paramsJSON '{{.ParamsJSON}}' \ @@ -87,6 +90,7 @@ 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: -- `WorkingDirectory` - `directory` from the configuration +- `WorkingDirectory` - `directory` from the configuration. +- `Sudo` - the opposite of `prevent_sudo` from the configuration. - `ParamsJSON` - The unquoted JSONified form of `params` from the configuration. - `Module` - `module` from the configuration. From 5d935767f04c7374eb0fbc833e6b332f6bc4ba43 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 08:45:19 -0600 Subject: [PATCH 18/23] provisioner(converge): add bootstrap_command --- provisioner/converge/provisioner.go | 36 +++++++++---------- provisioner/converge/provisioner_test.go | 15 ++++++++ .../source/docs/provisioners/converge.html.md | 18 ++++++++++ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 55233b504..daaf484d2 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -7,7 +7,6 @@ import ( "bytes" "errors" "fmt" - "net/http" "strings" @@ -28,8 +27,9 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - Bootstrap bool `mapstructure:"bootstrap"` - Version string `mapstructure:"version"` + Bootstrap bool `mapstructure:"bootstrap"` + Version string `mapstructure:"version"` + BootstrapCommand string `mapstructure:"bootstrap_command"` // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` @@ -64,7 +64,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { Interpolate: true, InterpolateContext: &p.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ - Exclude: []string{"execute_command"}, + Exclude: []string{ + "execute_command", + "bootstrap_command", + }, }, }, raws..., @@ -87,6 +90,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.ExecuteCommand = "cd {{.WorkingDirectory}} && {{if .Sudo}}sudo {{end}}converge apply --local --log-level=WARNING --paramsJSON '{{.ParamsJSON}}' {{.Module}}" } + if p.config.BootstrapCommand == "" { + p.config.BootstrapCommand = "curl -s https://get.converge.sh | sh {{if ne .Version \"\"}}-s -- -v {{.Version}}{{end}}" + } + // validate version if !versionRegex.Match([]byte(p.config.Version)) { return fmt.Errorf("Invalid Converge version %q specified. Valid versions include only letters, numbers, dots, and dashes", p.config.Version) @@ -133,21 +140,14 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err } ui.Message("bootstrapping converge") - bootstrap, err := http.Get("https://get.converge.sh") - if err != nil { - return fmt.Errorf("Error downloading bootstrap script: %s", err) // TODO: is github.com/pkg/error allowed? - } - if err := comm.Upload("/tmp/install-converge.sh", bootstrap.Body, nil); err != nil { - return fmt.Errorf("Error uploading script: %s", err) - } - if err := bootstrap.Body.Close(); err != nil { - return fmt.Errorf("Error getting bootstrap script: %s", err) + p.config.ctx.Data = struct { + Version string + }{ + Version: p.config.Version, } - - // construct command - command := "/bin/sh /tmp/install-converge.sh" - if p.config.Version != "" { - command += " -v " + p.config.Version + command, err := interpolate.Render(p.config.BootstrapCommand, &p.config.ctx) + if err != nil { + return fmt.Errorf("Could not interpolate bootstrap command: %s", err) } var out bytes.Buffer diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index 7a7e5ac09..7c97982ae 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -60,6 +60,21 @@ func TestProvisionerPrepare(t *testing.T) { t.Fatal("execute command unexpectedly blank") } }) + + t.Run("bootstrap_command", func(t *testing.T) { + var p Provisioner + config := testConfig() + + delete(config, "bootstrap_command") + + if err := p.Prepare(config); err != nil { + t.Fatalf("err: %s", err) + } + + if p.config.BootstrapCommand == "" { + t.Fatal("bootstrap command unexpectedly blank") + } + }) }) t.Run("validate", func(t *testing.T) { diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index 057604324..cdef6b5a4 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -60,6 +60,10 @@ Optional parameters: - `prevent_sudo` (bool) - stop Converge from running with adminstrator privileges via sudo +- `bootstrap_command` (string) - the command used to bootstrap Converge. This + has various + [configuration template variables](/docs/templates/configuration-templates.html) available. + ### Module Directories The provisioner can transfer module directories to the remote host for @@ -94,3 +98,17 @@ contain various template variables: - `Sudo` - the opposite of `prevent_sudo` from the configuration. - `ParamsJSON` - The unquoted JSONified form of `params` from the configuration. - `Module` - `module` from the configuration. + +### Bootstrap Command + +By default, Packer uses the following command to bootstrap Converge: + +``` {.liquid} +curl -s https://get.converge.sh | sh {{if ne .Version ""}}-s -- -v {{.Version}}{{end}} +``` + +This command can be customized using the `bootstrap_command` configuration. As you +can see from the default values above, the value of this configuration can +contain various template variables: + +- `Version` - `version` from the configuration. From 18425c45d0b97c977b938afe6da45a9448f4f005 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 08:48:02 -0600 Subject: [PATCH 19/23] provisioner(converge): change bootstrap to skip_bootstrap --- provisioner/converge/provisioner.go | 6 +++--- provisioner/converge/provisioner_test.go | 2 -- website/source/docs/provisioners/converge.html.md | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index daaf484d2..4f0897e76 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -27,7 +27,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - Bootstrap bool `mapstructure:"bootstrap"` + SkipBootstrap bool `mapstructure:"skip_bootstrap"` Version string `mapstructure:"version"` BootstrapCommand string `mapstructure:"bootstrap_command"` @@ -135,7 +135,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) error { - if !p.config.Bootstrap { + if p.config.SkipBootstrap { return nil } ui.Message("bootstrapping converge") @@ -219,7 +219,7 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error cmd.Wait() if cmd.ExitStatus == 127 { ui.Error("Could not find Converge. Is it installed and in PATH?") - if !p.config.Bootstrap { + if p.config.SkipBootstrap { ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") } diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index 7c97982ae..337cc60db 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -9,8 +9,6 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "bootstrap": false, - "version": "", "module_dirs": []map[string]interface{}{ { "source": "from", diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index cdef6b5a4..8ec101b33 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -39,9 +39,9 @@ required element is "module". Every other option is optional. Optional parameters: -- `bootstrap` (boolean) - Set to allow the provisioner to download the latest - Converge bootstrap script and the specified `version` of Converge from the - internet. +- `skip_bootstrap` (boolean) - If unset or `false`, the provisioner will + download the latest Converge bootstrap script and the specified `version` of + Converge from the internet. - `version` (string) - Set to a [released Converge version](https://github.com/asteris-llc/converge/releases) for bootstrap. From 64ebd3af8ddf7d3f59b7d8b28f774ef0a5e475bb Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 08:49:08 -0600 Subject: [PATCH 20/23] provisioner(converge): remove version validation --- provisioner/converge/provisioner.go | 9 --------- provisioner/converge/provisioner_test.go | 14 -------------- 2 files changed, 23 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 4f0897e76..07ef4f6c7 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -12,16 +12,12 @@ import ( "encoding/json" - "regexp" - "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/helper/config" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/template/interpolate" ) -var versionRegex = regexp.MustCompile(`^[\.\-\da-zA-Z]*$`) - // Config for Converge provisioner type Config struct { common.PackerConfig `mapstructure:",squash"` @@ -94,11 +90,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.BootstrapCommand = "curl -s https://get.converge.sh | sh {{if ne .Version \"\"}}-s -- -v {{.Version}}{{end}}" } - // validate version - if !versionRegex.Match([]byte(p.config.Version)) { - return fmt.Errorf("Invalid Converge version %q specified. Valid versions include only letters, numbers, dots, and dashes", p.config.Version) - } - // validate sources and destinations for i, dir := range p.config.ModuleDirs { if dir.Source == "" { diff --git a/provisioner/converge/provisioner_test.go b/provisioner/converge/provisioner_test.go index 337cc60db..b4cd2d361 100644 --- a/provisioner/converge/provisioner_test.go +++ b/provisioner/converge/provisioner_test.go @@ -1,7 +1,6 @@ package converge import ( - "strings" "testing" "github.com/mitchellh/packer/packer" @@ -76,19 +75,6 @@ func TestProvisionerPrepare(t *testing.T) { }) t.Run("validate", func(t *testing.T) { - t.Run("bad version", func(t *testing.T) { - var p Provisioner - config := testConfig() - config["version"] = "bad version with spaces" - - err := p.Prepare(config) - if err == nil { - t.Error("expected error") - } else if !strings.HasPrefix(err.Error(), "Invalid Converge version") { - t.Errorf("expected error starting with \"Invalid Converge version\". Got: %s", err) - } - }) - t.Run("module dir", func(t *testing.T) { t.Run("missing source", func(t *testing.T) { var p Provisioner From fb6a5c5bbc237e41f10b12e7aaa1743738daeb87 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 12:49:40 -0600 Subject: [PATCH 21/23] provisioner(converge): change skip_bootstrap back to bootstrap --- provisioner/converge/provisioner.go | 6 +++--- website/source/docs/provisioners/converge.html.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index 07ef4f6c7..e58675008 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -23,7 +23,7 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - SkipBootstrap bool `mapstructure:"skip_bootstrap"` + Bootstrap bool `mapstructure:"bootstrap"` Version string `mapstructure:"version"` BootstrapCommand string `mapstructure:"bootstrap_command"` @@ -126,7 +126,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) error { - if p.config.SkipBootstrap { + if !p.config.Bootstrap { return nil } ui.Message("bootstrapping converge") @@ -210,7 +210,7 @@ func (p *Provisioner) applyModules(ui packer.Ui, comm packer.Communicator) error cmd.Wait() if cmd.ExitStatus == 127 { ui.Error("Could not find Converge. Is it installed and in PATH?") - if p.config.SkipBootstrap { + if !p.config.Bootstrap { ui.Error("Bootstrapping was disabled for this run. That might be why Converge isn't present.") } diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index 8ec101b33..cdef6b5a4 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -39,9 +39,9 @@ required element is "module". Every other option is optional. Optional parameters: -- `skip_bootstrap` (boolean) - If unset or `false`, the provisioner will - download the latest Converge bootstrap script and the specified `version` of - Converge from the internet. +- `bootstrap` (boolean) - Set to allow the provisioner to download the latest + Converge bootstrap script and the specified `version` of Converge from the + internet. - `version` (string) - Set to a [released Converge version](https://github.com/asteris-llc/converge/releases) for bootstrap. From feab6f096e70a8757b71679d9ce9d9ee9419bdd2 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 28 Dec 2016 12:53:22 -0600 Subject: [PATCH 22/23] provisioner(converge): add prevent_bootstrap_sudo --- provisioner/converge/provisioner.go | 11 +++++++---- website/source/docs/provisioners/converge.html.md | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/provisioner/converge/provisioner.go b/provisioner/converge/provisioner.go index e58675008..dab861115 100644 --- a/provisioner/converge/provisioner.go +++ b/provisioner/converge/provisioner.go @@ -23,9 +23,10 @@ type Config struct { common.PackerConfig `mapstructure:",squash"` // Bootstrapping - Bootstrap bool `mapstructure:"bootstrap"` - Version string `mapstructure:"version"` - BootstrapCommand string `mapstructure:"bootstrap_command"` + Bootstrap bool `mapstructure:"bootstrap"` + Version string `mapstructure:"version"` + BootstrapCommand string `mapstructure:"bootstrap_command"` + PreventBootstrapSudo bool `mapstructure:"prevent_bootstrap_sudo"` // Modules ModuleDirs []ModuleDir `mapstructure:"module_dirs"` @@ -87,7 +88,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.BootstrapCommand == "" { - p.config.BootstrapCommand = "curl -s https://get.converge.sh | sh {{if ne .Version \"\"}}-s -- -v {{.Version}}{{end}}" + p.config.BootstrapCommand = "curl -s https://get.converge.sh | {{if .Sudo}}sudo {{end}}sh {{if ne .Version \"\"}}-s -- -v {{.Version}}{{end}}" } // validate sources and destinations @@ -133,8 +134,10 @@ func (p *Provisioner) maybeBootstrap(ui packer.Ui, comm packer.Communicator) err p.config.ctx.Data = struct { Version string + Sudo bool }{ Version: p.config.Version, + Sudo: !p.config.PreventBootstrapSudo, } command, err := interpolate.Render(p.config.BootstrapCommand, &p.config.ctx) if err != nil { diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index cdef6b5a4..cbb0d44c3 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -64,6 +64,9 @@ Optional parameters: has various [configuration template variables](/docs/templates/configuration-templates.html) available. +- `prevent_bootstrap_sudo` (bool) - stop Converge from bootstrapping with + administrator privileges via sudo + ### Module Directories The provisioner can transfer module directories to the remote host for @@ -104,11 +107,12 @@ contain various template variables: By default, Packer uses the following command to bootstrap Converge: ``` {.liquid} -curl -s https://get.converge.sh | sh {{if ne .Version ""}}-s -- -v {{.Version}}{{end}} +curl -s https://get.converge.sh | {{if .Sudo}}sudo {{end}}sh {{if ne .Version ""}}-s -- -v {{.Version}}{{end}} ``` This command can be customized using the `bootstrap_command` configuration. As you can see from the default values above, the value of this configuration can contain various template variables: +- `Sudo` - the opposite of `prevent_bootstrap_sudo` from the configuration. - `Version` - `version` from the configuration. From d9683c0817ca9b005f8672082bd9cd4dce1d9936 Mon Sep 17 00:00:00 2001 From: Brian Hicks Date: Wed, 4 Jan 2017 12:12:35 -0500 Subject: [PATCH 23/23] website: add default note on converge bootstrap parameter --- website/source/docs/provisioners/converge.html.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/source/docs/provisioners/converge.html.md b/website/source/docs/provisioners/converge.html.md index cbb0d44c3..c2df56d71 100644 --- a/website/source/docs/provisioners/converge.html.md +++ b/website/source/docs/provisioners/converge.html.md @@ -39,9 +39,9 @@ required element is "module". Every other option is optional. Optional parameters: -- `bootstrap` (boolean) - Set to allow the provisioner to download the latest - Converge bootstrap script and the specified `version` of Converge from the - internet. +- `bootstrap` (boolean, defaults to false) - Set to allow the provisioner to + download the latest Converge bootstrap script and the specified `version` of + Converge from the internet. - `version` (string) - Set to a [released Converge version](https://github.com/asteris-llc/converge/releases) for bootstrap.