From 9bdd5a927aa02e7c4ecddbdb4c5254d78145cacb Mon Sep 17 00:00:00 2001 From: Rafael Garcia Date: Sun, 21 Jul 2013 21:20:39 -0700 Subject: [PATCH] provisioner/salt: install salt --- config.go | 3 +- plugin/provisioner-salt/main.go | 10 +++ provisioner/salt/provisioner.go | 147 ++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 plugin/provisioner-salt/main.go create mode 100644 provisioner/salt/provisioner.go diff --git a/config.go b/config.go index 4660fad1c..dac02d0cd 100644 --- a/config.go +++ b/config.go @@ -38,7 +38,8 @@ const defaultConfig = ` "provisioners": { "file": "packer-provisioner-file", - "shell": "packer-provisioner-shell" + "shell": "packer-provisioner-shell", + "salt": "packer-provisioner-salt" } } ` diff --git a/plugin/provisioner-salt/main.go b/plugin/provisioner-salt/main.go new file mode 100644 index 000000000..622611e67 --- /dev/null +++ b/plugin/provisioner-salt/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/mitchellh/packer/packer/plugin" + "github.com/mitchellh/packer/provisioner/salt" +) + +func main() { + plugin.ServeProvisioner(new(salt.Provisioner)) +} diff --git a/provisioner/salt/provisioner.go b/provisioner/salt/provisioner.go new file mode 100644 index 000000000..2912a3df8 --- /dev/null +++ b/provisioner/salt/provisioner.go @@ -0,0 +1,147 @@ +// This package implements a provisioner for Packer that executes a +// saltstack highstate within the remote machine +package salt + +import ( + "fmt" + "github.com/mitchellh/iochan" + "github.com/mitchellh/mapstructure" + "github.com/mitchellh/packer/packer" + "io" + "log" + "sort" + "strings" +) + +var Ui packer.Ui + +type config struct { + // If true, skips installing Salt. Defaults to false. + SkipInstall bool `mapstructure:"skip_install"` +} + +type Provisioner struct { + config config +} + +func (p *Provisioner) Prepare(raws ...interface{}) error { + var md mapstructure.Metadata + decoderConfig := &mapstructure.DecoderConfig{ + Metadata: &md, + Result: &p.config, + } + + decoder, err := mapstructure.NewDecoder(decoderConfig) + if err != nil { + return err + } + + for _, raw := range raws { + err := decoder.Decode(raw) + if err != nil { + return err + } + } + + // Accumulate any errors + errs := make([]error, 0) + + // Unused keys are errors + if len(md.Unused) > 0 { + sort.Strings(md.Unused) + for _, unused := range md.Unused { + if unused != "type" && !strings.HasPrefix(unused, "packer_") { + errs = append( + errs, fmt.Errorf("Unknown configuration key: %s", unused)) + } + } + } + + if len(errs) > 0 { + return &packer.MultiError{errs} + } + + return nil +} + +func InstallSalt(comm packer.Communicator) (err error) { + Ui.Say("Installing Salt") + cmd := "wget -O - http://bootstrap.saltstack.org | sudo sh" + if err = executeCommand(cmd, comm); err != nil { + return fmt.Errorf("Unable to install Salt: %d", err) + } + + return nil +} + +func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { + var err error + Ui = ui + + if !p.config.SkipInstall { + if err = InstallSalt(comm); err != nil { + return fmt.Errorf("Error installing Salt: %s", err) + } + } + + return nil +} + +func executeCommand(command string, comm packer.Communicator) (err error) { + // Setup the remote command + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + var cmd packer.RemoteCmd + cmd.Command = command + cmd.Stdout = stdout_w + cmd.Stderr = stderr_w + + log.Printf("Executing command: %s", cmd.Command) + err = comm.Start(&cmd) + if err != nil { + return fmt.Errorf("Failed executing command: %s", err) + } + + exitChan := make(chan int, 1) + stdoutChan := iochan.DelimReader(stdout_r, '\n') + stderrChan := iochan.DelimReader(stderr_r, '\n') + + go func() { + defer stdout_w.Close() + defer stderr_w.Close() + + cmd.Wait() + exitChan <- cmd.ExitStatus + }() + +OutputLoop: + for { + select { + case output := <-stderrChan: + Ui.Message(strings.TrimSpace(output)) + case output := <-stdoutChan: + Ui.Message(strings.TrimSpace(output)) + case exitStatus := <-exitChan: + log.Printf("Chef Solo provisioner exited with status %d", exitStatus) + + if exitStatus != 0 { + return fmt.Errorf("Command exited with non-zero exit status: %d", exitStatus) + } + + break OutputLoop + } + } + + // Make sure we finish off stdout/stderr because we may have gotten + // a message from the exit channel first. + for output := range stdoutChan { + Ui.Message(output) + } + + for output := range stderrChan { + Ui.Message(output) + } + + return nil +}