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

     1  // This package implements a provisioner for Packer that executes
     2  // Puppet on the remote machine, configured to apply a local manifest
     3  // versus connecting to a Puppet master.
     4  package puppetmasterless
     5  
     6  import (
     7  	"fmt"
     8  	"github.com/mitchellh/packer/common"
     9  	"github.com/mitchellh/packer/packer"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  )
    14  
    15  type Config struct {
    16  	common.PackerConfig `mapstructure:",squash"`
    17  	tpl                 *packer.ConfigTemplate
    18  
    19  	// The command used to execute Puppet.
    20  	ExecuteCommand string `mapstructure:"execute_command"`
    21  
    22  	// Additional facts to set when executing Puppet
    23  	Facter map[string]string
    24  
    25  	// Path to a hiera configuration file to upload and use.
    26  	HieraConfigPath string `mapstructure:"hiera_config_path"`
    27  
    28  	// An array of local paths of modules to upload.
    29  	ModulePaths []string `mapstructure:"module_paths"`
    30  
    31  	// The main manifest file to apply to kick off the entire thing.
    32  	ManifestFile string `mapstructure:"manifest_file"`
    33  
    34  	// A directory of manifest files that will be uploaded to the remote
    35  	// machine.
    36  	ManifestDir string `mapstructure:"manifest_dir"`
    37  
    38  	// If true, `sudo` will NOT be used to execute Puppet.
    39  	PreventSudo bool `mapstructure:"prevent_sudo"`
    40  
    41  	// The directory where files will be uploaded. Packer requires write
    42  	// permissions in this directory.
    43  	StagingDir string `mapstructure:"staging_directory"`
    44  }
    45  
    46  type Provisioner struct {
    47  	config Config
    48  }
    49  
    50  type ExecuteTemplate struct {
    51  	FacterVars      string
    52  	HieraConfigPath string
    53  	ModulePath      string
    54  	ManifestFile    string
    55  	ManifestDir     string
    56  	Sudo            bool
    57  }
    58  
    59  func (p *Provisioner) Prepare(raws ...interface{}) error {
    60  	md, err := common.DecodeConfig(&p.config, raws...)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	p.config.tpl, err = packer.NewConfigTemplate()
    66  	if err != nil {
    67  		return err
    68  	}
    69  	p.config.tpl.UserVars = p.config.PackerUserVars
    70  
    71  	// Accumulate any errors
    72  	errs := common.CheckUnusedConfig(md)
    73  
    74  	// Set some defaults
    75  	if p.config.ExecuteCommand == "" {
    76  		p.config.ExecuteCommand = "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
    77  			"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
    78  			"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
    79  			"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
    80  			"--detailed-exitcodes " +
    81  			"{{.ManifestFile}}"
    82  	}
    83  
    84  	if p.config.StagingDir == "" {
    85  		p.config.StagingDir = "/tmp/packer-puppet-masterless"
    86  	}
    87  
    88  	// Templates
    89  	templates := map[string]*string{
    90  		"hiera_config_path": &p.config.HieraConfigPath,
    91  		"manifest_file":     &p.config.ManifestFile,
    92  		"manifest_dir":      &p.config.ManifestDir,
    93  		"staging_dir":       &p.config.StagingDir,
    94  	}
    95  
    96  	for n, ptr := range templates {
    97  		var err error
    98  		*ptr, err = p.config.tpl.Process(*ptr, nil)
    99  		if err != nil {
   100  			errs = packer.MultiErrorAppend(
   101  				errs, fmt.Errorf("Error processing %s: %s", n, err))
   102  		}
   103  	}
   104  
   105  	sliceTemplates := map[string][]string{
   106  		"module_paths": p.config.ModulePaths,
   107  	}
   108  
   109  	for n, slice := range sliceTemplates {
   110  		for i, elem := range slice {
   111  			var err error
   112  			slice[i], err = p.config.tpl.Process(elem, nil)
   113  			if err != nil {
   114  				errs = packer.MultiErrorAppend(
   115  					errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
   116  			}
   117  		}
   118  	}
   119  
   120  	validates := map[string]*string{
   121  		"execute_command": &p.config.ExecuteCommand,
   122  	}
   123  
   124  	for n, ptr := range validates {
   125  		if err := p.config.tpl.Validate(*ptr); err != nil {
   126  			errs = packer.MultiErrorAppend(
   127  				errs, fmt.Errorf("Error parsing %s: %s", n, err))
   128  		}
   129  	}
   130  
   131  	newFacts := make(map[string]string)
   132  	for k, v := range p.config.Facter {
   133  		k, err := p.config.tpl.Process(k, nil)
   134  		if err != nil {
   135  			errs = packer.MultiErrorAppend(errs,
   136  				fmt.Errorf("Error processing facter key %s: %s", k, err))
   137  			continue
   138  		}
   139  
   140  		v, err := p.config.tpl.Process(v, nil)
   141  		if err != nil {
   142  			errs = packer.MultiErrorAppend(errs,
   143  				fmt.Errorf("Error processing facter value '%s': %s", v, err))
   144  			continue
   145  		}
   146  
   147  		newFacts[k] = v
   148  	}
   149  
   150  	p.config.Facter = newFacts
   151  
   152  	// Validation
   153  	if p.config.HieraConfigPath != "" {
   154  		info, err := os.Stat(p.config.HieraConfigPath)
   155  		if err != nil {
   156  			errs = packer.MultiErrorAppend(errs,
   157  				fmt.Errorf("hiera_config_path is invalid: %s", err))
   158  		} else if info.IsDir() {
   159  			errs = packer.MultiErrorAppend(errs,
   160  				fmt.Errorf("hiera_config_path must point to a file"))
   161  		}
   162  	}
   163  
   164  	if p.config.ManifestDir != "" {
   165  		info, err := os.Stat(p.config.ManifestDir)
   166  		if err != nil {
   167  			errs = packer.MultiErrorAppend(errs,
   168  				fmt.Errorf("manifest_dir is invalid: %s", err))
   169  		} else if !info.IsDir() {
   170  			errs = packer.MultiErrorAppend(errs,
   171  				fmt.Errorf("manifest_dir must point to a directory"))
   172  		}
   173  	}
   174  
   175  	if p.config.ManifestFile == "" {
   176  		errs = packer.MultiErrorAppend(errs,
   177  			fmt.Errorf("A manifest_file must be specified."))
   178  	} else {
   179  		info, err := os.Stat(p.config.ManifestFile)
   180  		if err != nil {
   181  			errs = packer.MultiErrorAppend(errs,
   182  				fmt.Errorf("manifest_file is invalid: %s", err))
   183  		} else if info.IsDir() {
   184  			errs = packer.MultiErrorAppend(errs,
   185  				fmt.Errorf("manifest_file must point to a file"))
   186  		}
   187  	}
   188  
   189  	for i, path := range p.config.ModulePaths {
   190  		info, err := os.Stat(path)
   191  		if err != nil {
   192  			errs = packer.MultiErrorAppend(errs,
   193  				fmt.Errorf("module_path[%d] is invalid: %s", i, err))
   194  		} else if !info.IsDir() {
   195  			errs = packer.MultiErrorAppend(errs,
   196  				fmt.Errorf("module_path[%d] must point to a directory"))
   197  		}
   198  	}
   199  
   200  	if errs != nil && len(errs.Errors) > 0 {
   201  		return errs
   202  	}
   203  
   204  	return nil
   205  }
   206  
   207  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   208  	ui.Say("Provisioning with Puppet...")
   209  	ui.Message("Creating Puppet staging directory...")
   210  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   211  		return fmt.Errorf("Error creating staging directory: %s", err)
   212  	}
   213  
   214  	// Upload hiera config if set
   215  	remoteHieraConfigPath := ""
   216  	if p.config.HieraConfigPath != "" {
   217  		var err error
   218  		remoteHieraConfigPath, err = p.uploadHieraConfig(ui, comm)
   219  		if err != nil {
   220  			return fmt.Errorf("Error uploading hiera config: %s", err)
   221  		}
   222  	}
   223  
   224  	// Upload manifest dir if set
   225  	remoteManifestDir := ""
   226  	if p.config.ManifestDir != "" {
   227  		ui.Message(fmt.Sprintf(
   228  			"Uploading manifest directory from: %s", p.config.ManifestDir))
   229  		remoteManifestDir = fmt.Sprintf("%s/manifests", p.config.StagingDir)
   230  		err := p.uploadDirectory(ui, comm, remoteManifestDir, p.config.ManifestDir)
   231  		if err != nil {
   232  			return fmt.Errorf("Error uploading manifest dir: %s", err)
   233  		}
   234  	}
   235  
   236  	// Upload all modules
   237  	modulePaths := make([]string, 0, len(p.config.ModulePaths))
   238  	for i, path := range p.config.ModulePaths {
   239  		ui.Message(fmt.Sprintf("Uploading local modules from: %s", path))
   240  		targetPath := fmt.Sprintf("%s/module-%d", p.config.StagingDir, i)
   241  		if err := p.uploadDirectory(ui, comm, targetPath, path); err != nil {
   242  			return fmt.Errorf("Error uploading modules: %s", err)
   243  		}
   244  
   245  		modulePaths = append(modulePaths, targetPath)
   246  	}
   247  
   248  	// Upload manifests
   249  	remoteManifestFile, err := p.uploadManifests(ui, comm)
   250  	if err != nil {
   251  		return fmt.Errorf("Error uploading manifests: %s", err)
   252  	}
   253  
   254  	// Compile the facter variables
   255  	facterVars := make([]string, 0, len(p.config.Facter))
   256  	for k, v := range p.config.Facter {
   257  		facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
   258  	}
   259  
   260  	// Execute Puppet
   261  	command, err := p.config.tpl.Process(p.config.ExecuteCommand, &ExecuteTemplate{
   262  		FacterVars:      strings.Join(facterVars, " "),
   263  		HieraConfigPath: remoteHieraConfigPath,
   264  		ManifestDir:     remoteManifestDir,
   265  		ManifestFile:    remoteManifestFile,
   266  		ModulePath:      strings.Join(modulePaths, ":"),
   267  		Sudo:            !p.config.PreventSudo,
   268  	})
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	cmd := &packer.RemoteCmd{
   274  		Command: command,
   275  	}
   276  
   277  	ui.Message(fmt.Sprintf("Running Puppet: %s", command))
   278  	if err := cmd.StartWithUi(comm, ui); err != nil {
   279  		return err
   280  	}
   281  
   282  	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 {
   283  		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  func (p *Provisioner) Cancel() {
   290  	// Just hard quit. It isn't a big deal if what we're doing keeps
   291  	// running on the other side.
   292  	os.Exit(0)
   293  }
   294  
   295  func (p *Provisioner) uploadHieraConfig(ui packer.Ui, comm packer.Communicator) (string, error) {
   296  	ui.Message("Uploading hiera configuration...")
   297  	f, err := os.Open(p.config.HieraConfigPath)
   298  	if err != nil {
   299  		return "", err
   300  	}
   301  	defer f.Close()
   302  
   303  	path := fmt.Sprintf("%s/hiera.yaml", p.config.StagingDir)
   304  	if err := comm.Upload(path, f, nil); err != nil {
   305  		return "", err
   306  	}
   307  
   308  	return path, nil
   309  }
   310  
   311  func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (string, error) {
   312  	// Create the remote manifests directory...
   313  	ui.Message("Uploading manifests...")
   314  	remoteManifestsPath := fmt.Sprintf("%s/manifests", p.config.StagingDir)
   315  	if err := p.createDir(ui, comm, remoteManifestsPath); err != nil {
   316  		return "", fmt.Errorf("Error creating manifests directory: %s", err)
   317  	}
   318  
   319  	// Upload the main manifest
   320  	f, err := os.Open(p.config.ManifestFile)
   321  	if err != nil {
   322  		return "", err
   323  	}
   324  	defer f.Close()
   325  
   326  	manifestFilename := filepath.Base(p.config.ManifestFile)
   327  	remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
   328  	if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
   329  		return "", err
   330  	}
   331  
   332  	return remoteManifestFile, nil
   333  }
   334  
   335  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   336  	cmd := &packer.RemoteCmd{
   337  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   338  	}
   339  
   340  	if err := cmd.StartWithUi(comm, ui); err != nil {
   341  		return err
   342  	}
   343  
   344  	if cmd.ExitStatus != 0 {
   345  		return fmt.Errorf("Non-zero exit status.")
   346  	}
   347  
   348  	return nil
   349  }
   350  
   351  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   352  	if err := p.createDir(ui, comm, dst); err != nil {
   353  		return err
   354  	}
   355  
   356  	// Make sure there is a trailing "/" so that the directory isn't
   357  	// created on the other side.
   358  	if src[len(src)-1] != '/' {
   359  		src = src + "/"
   360  	}
   361  
   362  	return comm.UploadDir(dst, src, nil)
   363  }