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