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