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