github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/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  	// If true, packer will ignore all exit-codes from a puppet run
    49  	IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"`
    50  }
    51  
    52  type Provisioner struct {
    53  	config Config
    54  }
    55  
    56  type ExecuteTemplate struct {
    57  	FacterVars           string
    58  	ClientCertPath       string
    59  	ClientPrivateKeyPath string
    60  	PuppetNode           string
    61  	PuppetServer         string
    62  	Options              string
    63  	Sudo                 bool
    64  }
    65  
    66  func (p *Provisioner) Prepare(raws ...interface{}) error {
    67  	err := config.Decode(&p.config, &config.DecodeOpts{
    68  		Interpolate:        true,
    69  		InterpolateContext: &p.config.ctx,
    70  		InterpolateFilter: &interpolate.RenderFilter{
    71  			Exclude: []string{
    72  				"execute_command",
    73  			},
    74  		},
    75  	}, raws...)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	
    80  	if p.config.ExecuteCommand == "" {
    81  		p.config.ExecuteCommand = p.commandTemplate()
    82  	}
    83  	
    84  	if p.config.StagingDir == "" {
    85  		p.config.StagingDir = "/tmp/packer-puppet-server"
    86  	}
    87  
    88  	var errs *packer.MultiError
    89  	if p.config.ClientCertPath != "" {
    90  		info, err := os.Stat(p.config.ClientCertPath)
    91  		if err != nil {
    92  			errs = packer.MultiErrorAppend(errs,
    93  				fmt.Errorf("client_cert_dir is invalid: %s", err))
    94  		} else if !info.IsDir() {
    95  			errs = packer.MultiErrorAppend(errs,
    96  				fmt.Errorf("client_cert_dir must point to a directory"))
    97  		}
    98  	}
    99  
   100  	if p.config.ClientPrivateKeyPath != "" {
   101  		info, err := os.Stat(p.config.ClientPrivateKeyPath)
   102  		if err != nil {
   103  			errs = packer.MultiErrorAppend(errs,
   104  				fmt.Errorf("client_private_key_dir is invalid: %s", err))
   105  		} else if !info.IsDir() {
   106  			errs = packer.MultiErrorAppend(errs,
   107  				fmt.Errorf("client_private_key_dir must point to a directory"))
   108  		}
   109  	}
   110  
   111  	if errs != nil && len(errs.Errors) > 0 {
   112  		return errs
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   119  	ui.Say("Provisioning with Puppet...")
   120  	ui.Message("Creating Puppet staging directory...")
   121  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   122  		return fmt.Errorf("Error creating staging directory: %s", err)
   123  	}
   124  
   125  	// Upload client cert dir if set
   126  	remoteClientCertPath := ""
   127  	if p.config.ClientCertPath != "" {
   128  		ui.Message(fmt.Sprintf(
   129  			"Uploading client cert from: %s", p.config.ClientCertPath))
   130  		remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir)
   131  		err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath)
   132  		if err != nil {
   133  			return fmt.Errorf("Error uploading client cert: %s", err)
   134  		}
   135  	}
   136  
   137  	// Upload client cert dir if set
   138  	remoteClientPrivateKeyPath := ""
   139  	if p.config.ClientPrivateKeyPath != "" {
   140  		ui.Message(fmt.Sprintf(
   141  			"Uploading client private keys from: %s", p.config.ClientPrivateKeyPath))
   142  		remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir)
   143  		err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath)
   144  		if err != nil {
   145  			return fmt.Errorf("Error uploading client private keys: %s", err)
   146  		}
   147  	}
   148  
   149  	// Compile the facter variables
   150  	facterVars := make([]string, 0, len(p.config.Facter))
   151  	for k, v := range p.config.Facter {
   152  		facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
   153  	}
   154  
   155  	// Execute Puppet
   156  	p.config.ctx.Data = &ExecuteTemplate{
   157  		FacterVars:           strings.Join(facterVars, " "),
   158  		ClientCertPath:       remoteClientCertPath,
   159  		ClientPrivateKeyPath: remoteClientPrivateKeyPath,
   160  		PuppetNode:           p.config.PuppetNode,
   161  		PuppetServer:         p.config.PuppetServer,
   162  		Options:              p.config.Options,
   163  		Sudo:                 !p.config.PreventSudo,
   164  	}
   165  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	cmd := &packer.RemoteCmd{
   171  		Command: command,
   172  	}
   173  
   174  	ui.Message(fmt.Sprintf("Running Puppet: %s", command))
   175  	if err := cmd.StartWithUi(comm, ui); err != nil {
   176  		return err
   177  	}
   178  
   179  	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
   180  		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (p *Provisioner) Cancel() {
   187  	// Just hard quit. It isn't a big deal if what we're doing keeps
   188  	// running on the other side.
   189  	os.Exit(0)
   190  }
   191  
   192  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   193  	cmd := &packer.RemoteCmd{
   194  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   195  	}
   196  
   197  	if err := cmd.StartWithUi(comm, ui); err != nil {
   198  		return err
   199  	}
   200  
   201  	if cmd.ExitStatus != 0 {
   202  		return fmt.Errorf("Non-zero exit status.")
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   209  	if err := p.createDir(ui, comm, dst); err != nil {
   210  		return err
   211  	}
   212  
   213  	// Make sure there is a trailing "/" so that the directory isn't
   214  	// created on the other side.
   215  	if src[len(src)-1] != '/' {
   216  		src = src + "/"
   217  	}
   218  
   219  	return comm.UploadDir(dst, src, nil)
   220  }
   221  
   222  func (p *Provisioner) commandTemplate() string {
   223  	return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
   224  		"puppet agent --onetime --no-daemonize " +
   225  		"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
   226  		"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
   227  		"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
   228  		"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
   229  		"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
   230  		"--detailed-exitcodes"
   231  }