github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/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  	if p.config.Facter == nil {
    94  		p.config.Facter = make(map[string]string)
    95  	}
    96  	p.config.Facter["packer_build_name"] = p.config.PackerBuildName
    97  	p.config.Facter["packer_builder_type"] = p.config.PackerBuilderType
    98  
    99  	var errs *packer.MultiError
   100  	if p.config.ClientCertPath != "" {
   101  		info, err := os.Stat(p.config.ClientCertPath)
   102  		if err != nil {
   103  			errs = packer.MultiErrorAppend(errs,
   104  				fmt.Errorf("client_cert_dir is invalid: %s", err))
   105  		} else if !info.IsDir() {
   106  			errs = packer.MultiErrorAppend(errs,
   107  				fmt.Errorf("client_cert_dir must point to a directory"))
   108  		}
   109  	}
   110  
   111  	if p.config.ClientPrivateKeyPath != "" {
   112  		info, err := os.Stat(p.config.ClientPrivateKeyPath)
   113  		if err != nil {
   114  			errs = packer.MultiErrorAppend(errs,
   115  				fmt.Errorf("client_private_key_dir is invalid: %s", err))
   116  		} else if !info.IsDir() {
   117  			errs = packer.MultiErrorAppend(errs,
   118  				fmt.Errorf("client_private_key_dir must point to a directory"))
   119  		}
   120  	}
   121  
   122  	if errs != nil && len(errs.Errors) > 0 {
   123  		return errs
   124  	}
   125  
   126  	return nil
   127  }
   128  
   129  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   130  	ui.Say("Provisioning with Puppet...")
   131  	ui.Message("Creating Puppet staging directory...")
   132  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   133  		return fmt.Errorf("Error creating staging directory: %s", err)
   134  	}
   135  
   136  	// Upload client cert dir if set
   137  	remoteClientCertPath := ""
   138  	if p.config.ClientCertPath != "" {
   139  		ui.Message(fmt.Sprintf(
   140  			"Uploading client cert from: %s", p.config.ClientCertPath))
   141  		remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir)
   142  		err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath)
   143  		if err != nil {
   144  			return fmt.Errorf("Error uploading client cert: %s", err)
   145  		}
   146  	}
   147  
   148  	// Upload client cert dir if set
   149  	remoteClientPrivateKeyPath := ""
   150  	if p.config.ClientPrivateKeyPath != "" {
   151  		ui.Message(fmt.Sprintf(
   152  			"Uploading client private keys from: %s", p.config.ClientPrivateKeyPath))
   153  		remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir)
   154  		err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath)
   155  		if err != nil {
   156  			return fmt.Errorf("Error uploading client private keys: %s", err)
   157  		}
   158  	}
   159  
   160  	// Compile the facter variables
   161  	facterVars := make([]string, 0, len(p.config.Facter))
   162  	for k, v := range p.config.Facter {
   163  		facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
   164  	}
   165  
   166  	// Execute Puppet
   167  	p.config.ctx.Data = &ExecuteTemplate{
   168  		FacterVars:           strings.Join(facterVars, " "),
   169  		ClientCertPath:       remoteClientCertPath,
   170  		ClientPrivateKeyPath: remoteClientPrivateKeyPath,
   171  		PuppetNode:           p.config.PuppetNode,
   172  		PuppetServer:         p.config.PuppetServer,
   173  		Options:              p.config.Options,
   174  		PuppetBinDir:         p.config.PuppetBinDir,
   175  		Sudo:                 !p.config.PreventSudo,
   176  	}
   177  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	cmd := &packer.RemoteCmd{
   183  		Command: command,
   184  	}
   185  
   186  	ui.Message(fmt.Sprintf("Running Puppet: %s", command))
   187  	if err := cmd.StartWithUi(comm, ui); err != nil {
   188  		return err
   189  	}
   190  
   191  	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
   192  		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func (p *Provisioner) Cancel() {
   199  	// Just hard quit. It isn't a big deal if what we're doing keeps
   200  	// running on the other side.
   201  	os.Exit(0)
   202  }
   203  
   204  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   205  	cmd := &packer.RemoteCmd{
   206  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   207  	}
   208  
   209  	if err := cmd.StartWithUi(comm, ui); err != nil {
   210  		return err
   211  	}
   212  
   213  	if cmd.ExitStatus != 0 {
   214  		return fmt.Errorf("Non-zero exit status.")
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   221  	if err := p.createDir(ui, comm, dst); err != nil {
   222  		return err
   223  	}
   224  
   225  	// Make sure there is a trailing "/" so that the directory isn't
   226  	// created on the other side.
   227  	if src[len(src)-1] != '/' {
   228  		src = src + "/"
   229  	}
   230  
   231  	return comm.UploadDir(dst, src, nil)
   232  }
   233  
   234  func (p *Provisioner) commandTemplate() string {
   235  	return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
   236  		"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
   237  		"--onetime --no-daemonize " +
   238  		"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
   239  		"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
   240  		"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
   241  		"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
   242  		"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
   243  		"--detailed-exitcodes"
   244  }