github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/provisioner/salt-masterless/provisioner.go (about)

     1  // This package implements a provisioner for Packer that executes a
     2  // saltstack highstate within the remote machine
     3  package saltmasterless
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"github.com/mitchellh/packer/common"
     9  	"github.com/mitchellh/packer/packer"
    10  	"os"
    11  )
    12  
    13  const DefaultTempConfigDir = "/tmp/salt"
    14  
    15  type Config struct {
    16  	common.PackerConfig `mapstructure:",squash"`
    17  
    18  	// If true, run the salt-bootstrap script
    19  	SkipBootstrap bool   `mapstructure:"skip_bootstrap"`
    20  	BootstrapArgs string `mapstructure:"bootstrap_args"`
    21  
    22  	// Local path to the minion config
    23  	MinionConfig string `mapstructure:"minion_config"`
    24  
    25  	// Local path to the salt state tree
    26  	LocalStateTree string `mapstructure:"local_state_tree"`
    27  
    28  	// Local path to the salt pillar roots
    29  	LocalPillarRoots string `mapstructure:"local_pillar_roots"`
    30  
    31  	// Where files will be copied before moving to the /srv/salt directory
    32  	TempConfigDir string `mapstructure:"temp_config_dir"`
    33  
    34  	tpl *packer.ConfigTemplate
    35  }
    36  
    37  type Provisioner struct {
    38  	config Config
    39  }
    40  
    41  func (p *Provisioner) Prepare(raws ...interface{}) error {
    42  	md, err := common.DecodeConfig(&p.config, raws...)
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	p.config.tpl, err = packer.NewConfigTemplate()
    48  	if err != nil {
    49  		return err
    50  	}
    51  	p.config.tpl.UserVars = p.config.PackerUserVars
    52  
    53  	if p.config.TempConfigDir == "" {
    54  		p.config.TempConfigDir = DefaultTempConfigDir
    55  	}
    56  
    57  	// Accumulate any errors
    58  	errs := common.CheckUnusedConfig(md)
    59  
    60  	templates := map[string]*string{
    61  		"bootstrap_args":     &p.config.BootstrapArgs,
    62  		"minion_config":      &p.config.MinionConfig,
    63  		"local_state_tree":   &p.config.LocalStateTree,
    64  		"local_pillar_roots": &p.config.LocalPillarRoots,
    65  		"temp_config_dir":    &p.config.TempConfigDir,
    66  	}
    67  
    68  	for n, ptr := range templates {
    69  		var err error
    70  		*ptr, err = p.config.tpl.Process(*ptr, nil)
    71  		if err != nil {
    72  			errs = packer.MultiErrorAppend(
    73  				errs, fmt.Errorf("Error processing %s: %s", n, err))
    74  		}
    75  	}
    76  
    77  	if p.config.LocalStateTree != "" {
    78  		if _, err := os.Stat(p.config.LocalStateTree); err != nil {
    79  			errs = packer.MultiErrorAppend(errs,
    80  				errors.New("local_state_tree must exist and be accessible"))
    81  		}
    82  	}
    83  
    84  	if p.config.LocalPillarRoots != "" {
    85  		if _, err := os.Stat(p.config.LocalPillarRoots); err != nil {
    86  			errs = packer.MultiErrorAppend(errs,
    87  				errors.New("local_pillar_roots must exist and be accessible"))
    88  		}
    89  	}
    90  
    91  	if p.config.MinionConfig != "" {
    92  		if _, err := os.Stat(p.config.MinionConfig); err != nil {
    93  			errs = packer.MultiErrorAppend(errs,
    94  				errors.New("minion_config must exist and be accessible"))
    95  		}
    96  	}
    97  
    98  	if errs != nil && len(errs.Errors) > 0 {
    99  		return errs
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   106  	var err error
   107  
   108  	ui.Say("Provisioning with Salt...")
   109  	if !p.config.SkipBootstrap {
   110  		cmd := &packer.RemoteCmd{
   111  			Command: fmt.Sprintf("wget -O - http://bootstrap.saltstack.org | sudo sh -s %s", p.config.BootstrapArgs),
   112  		}
   113  		ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd))
   114  		if err = cmd.StartWithUi(comm, ui); err != nil {
   115  			return fmt.Errorf("Unable to install Salt: %d", err)
   116  		}
   117  	}
   118  
   119  	ui.Message(fmt.Sprintf("Creating remote directory: %s", p.config.TempConfigDir))
   120  	cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", p.config.TempConfigDir)}
   121  	if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
   122  		if err == nil {
   123  			err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
   124  		}
   125  
   126  		return fmt.Errorf("Error creating remote salt state directory: %s", err)
   127  	}
   128  
   129  	if p.config.MinionConfig != "" {
   130  		ui.Message(fmt.Sprintf("Uploading minion config: %s", p.config.MinionConfig))
   131  		if err = uploadMinionConfig(comm, fmt.Sprintf("%s/minion", p.config.TempConfigDir), p.config.MinionConfig); err != nil {
   132  			return fmt.Errorf("Error uploading local minion config file to remote: %s", err)
   133  		}
   134  
   135  		ui.Message(fmt.Sprintf("Moving %s/minion to /etc/salt/minion", p.config.TempConfigDir))
   136  		cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/minion /etc/salt/minion", p.config.TempConfigDir)}
   137  		if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
   138  			if err == nil {
   139  				err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
   140  			}
   141  
   142  			return fmt.Errorf("Unable to move %s/minion to /etc/salt/minion: %d", p.config.TempConfigDir, err)
   143  		}
   144  	}
   145  
   146  	ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree))
   147  	if err = comm.UploadDir(fmt.Sprintf("%s/states", p.config.TempConfigDir),
   148  		p.config.LocalStateTree, []string{".git"}); err != nil {
   149  		return fmt.Errorf("Error uploading local state tree to remote: %s", err)
   150  	}
   151  
   152  	ui.Message(fmt.Sprintf("Moving %s/states to /srv/salt", p.config.TempConfigDir))
   153  	cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/states /srv/salt", p.config.TempConfigDir)}
   154  	if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
   155  		if err == nil {
   156  			err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
   157  		}
   158  
   159  		return fmt.Errorf("Unable to move %s/states to /srv/salt: %d", p.config.TempConfigDir, err)
   160  	}
   161  
   162  	if p.config.LocalPillarRoots != "" {
   163  		ui.Message(fmt.Sprintf("Uploading local pillar roots: %s", p.config.LocalPillarRoots))
   164  		if err = comm.UploadDir(fmt.Sprintf("%s/pillar", p.config.TempConfigDir),
   165  			p.config.LocalPillarRoots, []string{".git"}); err != nil {
   166  			return fmt.Errorf("Error uploading local pillar roots to remote: %s", err)
   167  		}
   168  
   169  		ui.Message(fmt.Sprintf("Moving %s/pillar to /srv/pillar", p.config.TempConfigDir))
   170  		cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s/pillar /srv/pillar", p.config.TempConfigDir)}
   171  		if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
   172  			if err == nil {
   173  				err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
   174  			}
   175  
   176  			return fmt.Errorf("Unable to move %s/pillar to /srv/pillar: %d", p.config.TempConfigDir, err)
   177  		}
   178  	}
   179  
   180  	ui.Message("Running highstate")
   181  	cmd = &packer.RemoteCmd{Command: "sudo salt-call --local state.highstate -l info"}
   182  	if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 {
   183  		if err == nil {
   184  			err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus)
   185  		}
   186  
   187  		return fmt.Errorf("Error executing highstate: %s", err)
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func (p *Provisioner) Cancel() {
   194  	// Just hard quit. It isn't a big deal if what we're doing keeps
   195  	// running on the other side.
   196  	os.Exit(0)
   197  }
   198  
   199  func uploadMinionConfig(comm packer.Communicator, dst string, src string) error {
   200  	f, err := os.Open(src)
   201  	if err != nil {
   202  		return fmt.Errorf("Error opening minion config: %s", err)
   203  	}
   204  	defer f.Close()
   205  
   206  	if err = comm.Upload(dst, f, nil); err != nil {
   207  		return fmt.Errorf("Error uploading minion config: %s", err)
   208  	}
   209  
   210  	return nil
   211  }