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