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