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") +}