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