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