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