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