github.com/amanya/packer@v0.12.1-0.20161117214323-902ac5ab2eb6/provisioner/puppet-server/provisioner.go (about)

     1  // This package implements a provisioner for Packer that executes
     2  // Puppet on the remote machine connecting to a Puppet master.
     3  package puppetserver
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/mitchellh/packer/common"
    11  	"github.com/mitchellh/packer/helper/config"
    12  	"github.com/mitchellh/packer/packer"
    13  	"github.com/mitchellh/packer/template/interpolate"
    14  )
    15  
    16  type Config struct {
    17  	common.PackerConfig `mapstructure:",squash"`
    18  	ctx                 interpolate.Context
    19  
    20  	// The command used to execute Puppet.
    21  	ExecuteCommand string `mapstructure:"execute_command"`
    22  
    23  	// Additional facts to set when executing Puppet
    24  	Facter map[string]string
    25  
    26  	// A path to the client certificate
    27  	ClientCertPath string `mapstructure:"client_cert_path"`
    28  
    29  	// A path to a directory containing the client private keys
    30  	ClientPrivateKeyPath string `mapstructure:"client_private_key_path"`
    31  
    32  	// The hostname of the Puppet node.
    33  	PuppetNode string `mapstructure:"puppet_node"`
    34  
    35  	// The hostname of the Puppet server.
    36  	PuppetServer string `mapstructure:"puppet_server"`
    37  
    38  	// Additional options to be passed to `puppet agent`.
    39  	Options string `mapstructure:"options"`
    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_dir"`
    47  
    48  	// The directory that contains the puppet binary.
    49  	// E.g. if it can't be found on the standard path.
    50  	PuppetBinDir string `mapstructure:"puppet_bin_dir"`
    51  
    52  	// If true, packer will ignore all exit-codes from a puppet run
    53  	IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"`
    54  }
    55  
    56  type Provisioner struct {
    57  	config Config
    58  }
    59  
    60  type ExecuteTemplate struct {
    61  	FacterVars           string
    62  	ClientCertPath       string
    63  	ClientPrivateKeyPath string
    64  	PuppetNode           string
    65  	PuppetServer         string
    66  	Options              string
    67  	PuppetBinDir         string
    68  	Sudo                 bool
    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  	if p.config.ExecuteCommand == "" {
    86  		p.config.ExecuteCommand = p.commandTemplate()
    87  	}
    88  
    89  	if p.config.StagingDir == "" {
    90  		p.config.StagingDir = "/tmp/packer-puppet-server"
    91  	}
    92  
    93  	var errs *packer.MultiError
    94  	if p.config.ClientCertPath != "" {
    95  		info, err := os.Stat(p.config.ClientCertPath)
    96  		if err != nil {
    97  			errs = packer.MultiErrorAppend(errs,
    98  				fmt.Errorf("client_cert_dir is invalid: %s", err))
    99  		} else if !info.IsDir() {
   100  			errs = packer.MultiErrorAppend(errs,
   101  				fmt.Errorf("client_cert_dir must point to a directory"))
   102  		}
   103  	}
   104  
   105  	if p.config.ClientPrivateKeyPath != "" {
   106  		info, err := os.Stat(p.config.ClientPrivateKeyPath)
   107  		if err != nil {
   108  			errs = packer.MultiErrorAppend(errs,
   109  				fmt.Errorf("client_private_key_dir is invalid: %s", err))
   110  		} else if !info.IsDir() {
   111  			errs = packer.MultiErrorAppend(errs,
   112  				fmt.Errorf("client_private_key_dir must point to a directory"))
   113  		}
   114  	}
   115  
   116  	if errs != nil && len(errs.Errors) > 0 {
   117  		return errs
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   124  	ui.Say("Provisioning with Puppet...")
   125  	ui.Message("Creating Puppet staging directory...")
   126  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   127  		return fmt.Errorf("Error creating staging directory: %s", err)
   128  	}
   129  
   130  	// Upload client cert dir if set
   131  	remoteClientCertPath := ""
   132  	if p.config.ClientCertPath != "" {
   133  		ui.Message(fmt.Sprintf(
   134  			"Uploading client cert from: %s", p.config.ClientCertPath))
   135  		remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir)
   136  		err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath)
   137  		if err != nil {
   138  			return fmt.Errorf("Error uploading client cert: %s", err)
   139  		}
   140  	}
   141  
   142  	// Upload client cert dir if set
   143  	remoteClientPrivateKeyPath := ""
   144  	if p.config.ClientPrivateKeyPath != "" {
   145  		ui.Message(fmt.Sprintf(
   146  			"Uploading client private keys from: %s", p.config.ClientPrivateKeyPath))
   147  		remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir)
   148  		err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath)
   149  		if err != nil {
   150  			return fmt.Errorf("Error uploading client private keys: %s", err)
   151  		}
   152  	}
   153  
   154  	// Compile the facter variables
   155  	facterVars := make([]string, 0, len(p.config.Facter))
   156  	for k, v := range p.config.Facter {
   157  		facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
   158  	}
   159  
   160  	// Execute Puppet
   161  	p.config.ctx.Data = &ExecuteTemplate{
   162  		FacterVars:           strings.Join(facterVars, " "),
   163  		ClientCertPath:       remoteClientCertPath,
   164  		ClientPrivateKeyPath: remoteClientPrivateKeyPath,
   165  		PuppetNode:           p.config.PuppetNode,
   166  		PuppetServer:         p.config.PuppetServer,
   167  		Options:              p.config.Options,
   168  		PuppetBinDir:         p.config.PuppetBinDir,
   169  		Sudo:                 !p.config.PreventSudo,
   170  	}
   171  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	cmd := &packer.RemoteCmd{
   177  		Command: command,
   178  	}
   179  
   180  	ui.Message(fmt.Sprintf("Running Puppet: %s", command))
   181  	if err := cmd.StartWithUi(comm, ui); err != nil {
   182  		return err
   183  	}
   184  
   185  	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
   186  		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func (p *Provisioner) Cancel() {
   193  	// Just hard quit. It isn't a big deal if what we're doing keeps
   194  	// running on the other side.
   195  	os.Exit(0)
   196  }
   197  
   198  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   199  	cmd := &packer.RemoteCmd{
   200  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   201  	}
   202  
   203  	if err := cmd.StartWithUi(comm, ui); err != nil {
   204  		return err
   205  	}
   206  
   207  	if cmd.ExitStatus != 0 {
   208  		return fmt.Errorf("Non-zero exit status.")
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   215  	if err := p.createDir(ui, comm, dst); err != nil {
   216  		return err
   217  	}
   218  
   219  	// Make sure there is a trailing "/" so that the directory isn't
   220  	// created on the other side.
   221  	if src[len(src)-1] != '/' {
   222  		src = src + "/"
   223  	}
   224  
   225  	return comm.UploadDir(dst, src, nil)
   226  }
   227  
   228  func (p *Provisioner) commandTemplate() string {
   229  	return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
   230  		"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
   231  		"--onetime --no-daemonize " +
   232  		"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
   233  		"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
   234  		"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
   235  		"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
   236  		"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
   237  		"--detailed-exitcodes"
   238  }