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