github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/provisioner/puppet-server/provisioner.go (about)

     1  // Package puppetserver 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/hashicorp/packer/common"
    11  	"github.com/hashicorp/packer/helper/config"
    12  	"github.com/hashicorp/packer/packer"
    13  	"github.com/hashicorp/packer/provisioner"
    14  	"github.com/hashicorp/packer/template/interpolate"
    15  )
    16  
    17  type guestOSTypeConfig struct {
    18  	executeCommand   string
    19  	facterVarsFmt    string
    20  	facterVarsJoiner string
    21  	stagingDir       string
    22  }
    23  
    24  var guestOSTypeConfigs = map[string]guestOSTypeConfig{
    25  	provisioner.UnixOSType: {
    26  		executeCommand: "{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}" +
    27  			"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
    28  			"--onetime --no-daemonize " +
    29  			"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
    30  			"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
    31  			"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
    32  			"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
    33  			"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
    34  			"--detailed-exitcodes",
    35  		facterVarsFmt:    "FACTER_%s='%s'",
    36  		facterVarsJoiner: " ",
    37  		stagingDir:       "/tmp/packer-puppet-server",
    38  	},
    39  	provisioner.WindowsOSType: {
    40  		executeCommand: "{{.FacterVars}} " +
    41  			"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet agent " +
    42  			"--onetime --no-daemonize " +
    43  			"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
    44  			"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
    45  			"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
    46  			"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
    47  			"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
    48  			"--detailed-exitcodes",
    49  		facterVarsFmt:    "SET \"FACTER_%s=%s\"",
    50  		facterVarsJoiner: " & ",
    51  		stagingDir:       "C:/Windows/Temp/packer-puppet-server",
    52  	},
    53  }
    54  
    55  type Config struct {
    56  	common.PackerConfig `mapstructure:",squash"`
    57  	ctx                 interpolate.Context
    58  
    59  	// The command used to execute Puppet.
    60  	ExecuteCommand string `mapstructure:"execute_command"`
    61  
    62  	// The Guest OS Type (unix or windows)
    63  	GuestOSType string `mapstructure:"guest_os_type"`
    64  
    65  	// Additional facts to set when executing Puppet
    66  	Facter map[string]string
    67  
    68  	// A path to the client certificate
    69  	ClientCertPath string `mapstructure:"client_cert_path"`
    70  
    71  	// A path to a directory containing the client private keys
    72  	ClientPrivateKeyPath string `mapstructure:"client_private_key_path"`
    73  
    74  	// The hostname of the Puppet node.
    75  	PuppetNode string `mapstructure:"puppet_node"`
    76  
    77  	// The hostname of the Puppet server.
    78  	PuppetServer string `mapstructure:"puppet_server"`
    79  
    80  	// Additional options to be passed to `puppet agent`.
    81  	Options string `mapstructure:"options"`
    82  
    83  	// If true, `sudo` will NOT be used to execute Puppet.
    84  	PreventSudo bool `mapstructure:"prevent_sudo"`
    85  
    86  	// The directory where files will be uploaded. Packer requires write
    87  	// permissions in this directory.
    88  	StagingDir string `mapstructure:"staging_dir"`
    89  
    90  	// The directory that contains the puppet binary.
    91  	// E.g. if it can't be found on the standard path.
    92  	PuppetBinDir string `mapstructure:"puppet_bin_dir"`
    93  
    94  	// If true, packer will ignore all exit-codes from a puppet run
    95  	IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"`
    96  }
    97  
    98  type Provisioner struct {
    99  	config            Config
   100  	guestOSTypeConfig guestOSTypeConfig
   101  	guestCommands     *provisioner.GuestCommands
   102  }
   103  
   104  type ExecuteTemplate struct {
   105  	FacterVars           string
   106  	ClientCertPath       string
   107  	ClientPrivateKeyPath string
   108  	PuppetNode           string
   109  	PuppetServer         string
   110  	Options              string
   111  	PuppetBinDir         string
   112  	Sudo                 bool
   113  }
   114  
   115  func (p *Provisioner) Prepare(raws ...interface{}) error {
   116  	err := config.Decode(&p.config, &config.DecodeOpts{
   117  		Interpolate:        true,
   118  		InterpolateContext: &p.config.ctx,
   119  		InterpolateFilter: &interpolate.RenderFilter{
   120  			Exclude: []string{
   121  				"execute_command",
   122  			},
   123  		},
   124  	}, raws...)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	if p.config.GuestOSType == "" {
   130  		p.config.GuestOSType = provisioner.DefaultOSType
   131  	}
   132  	p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
   133  
   134  	var ok bool
   135  	p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
   136  	if !ok {
   137  		return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
   138  	}
   139  
   140  	p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
   141  	if err != nil {
   142  		return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
   143  	}
   144  
   145  	if p.config.ExecuteCommand == "" {
   146  		p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
   147  	}
   148  
   149  	if p.config.StagingDir == "" {
   150  		p.config.StagingDir = p.guestOSTypeConfig.stagingDir
   151  	}
   152  
   153  	if p.config.Facter == nil {
   154  		p.config.Facter = make(map[string]string)
   155  	}
   156  	p.config.Facter["packer_build_name"] = p.config.PackerBuildName
   157  	p.config.Facter["packer_builder_type"] = p.config.PackerBuilderType
   158  
   159  	var errs *packer.MultiError
   160  	if p.config.ClientCertPath != "" {
   161  		info, err := os.Stat(p.config.ClientCertPath)
   162  		if err != nil {
   163  			errs = packer.MultiErrorAppend(errs,
   164  				fmt.Errorf("client_cert_dir is invalid: %s", err))
   165  		} else if !info.IsDir() {
   166  			errs = packer.MultiErrorAppend(errs,
   167  				fmt.Errorf("client_cert_dir must point to a directory"))
   168  		}
   169  	}
   170  
   171  	if p.config.ClientPrivateKeyPath != "" {
   172  		info, err := os.Stat(p.config.ClientPrivateKeyPath)
   173  		if err != nil {
   174  			errs = packer.MultiErrorAppend(errs,
   175  				fmt.Errorf("client_private_key_dir is invalid: %s", err))
   176  		} else if !info.IsDir() {
   177  			errs = packer.MultiErrorAppend(errs,
   178  				fmt.Errorf("client_private_key_dir must point to a directory"))
   179  		}
   180  	}
   181  
   182  	if errs != nil && len(errs.Errors) > 0 {
   183  		return errs
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   190  	ui.Say("Provisioning with Puppet...")
   191  	ui.Message("Creating Puppet staging directory...")
   192  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   193  		return fmt.Errorf("Error creating staging directory: %s", err)
   194  	}
   195  
   196  	// Upload client cert dir if set
   197  	remoteClientCertPath := ""
   198  	if p.config.ClientCertPath != "" {
   199  		ui.Message(fmt.Sprintf(
   200  			"Uploading client cert from: %s", p.config.ClientCertPath))
   201  		remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir)
   202  		err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath)
   203  		if err != nil {
   204  			return fmt.Errorf("Error uploading client cert: %s", err)
   205  		}
   206  	}
   207  
   208  	// Upload client cert dir if set
   209  	remoteClientPrivateKeyPath := ""
   210  	if p.config.ClientPrivateKeyPath != "" {
   211  		ui.Message(fmt.Sprintf(
   212  			"Uploading client private keys from: %s", p.config.ClientPrivateKeyPath))
   213  		remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir)
   214  		err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath)
   215  		if err != nil {
   216  			return fmt.Errorf("Error uploading client private keys: %s", err)
   217  		}
   218  	}
   219  
   220  	// Compile the facter variables
   221  	facterVars := make([]string, 0, len(p.config.Facter))
   222  	for k, v := range p.config.Facter {
   223  		facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v))
   224  	}
   225  
   226  	// Execute Puppet
   227  	p.config.ctx.Data = &ExecuteTemplate{
   228  		FacterVars:           strings.Join(facterVars, p.guestOSTypeConfig.facterVarsJoiner),
   229  		ClientCertPath:       remoteClientCertPath,
   230  		ClientPrivateKeyPath: remoteClientPrivateKeyPath,
   231  		PuppetNode:           p.config.PuppetNode,
   232  		PuppetServer:         p.config.PuppetServer,
   233  		Options:              p.config.Options,
   234  		PuppetBinDir:         p.config.PuppetBinDir,
   235  		Sudo:                 !p.config.PreventSudo,
   236  	}
   237  	command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	cmd := &packer.RemoteCmd{
   243  		Command: command,
   244  	}
   245  
   246  	ui.Message(fmt.Sprintf("Running Puppet: %s", command))
   247  	if err := cmd.StartWithUi(comm, ui); err != nil {
   248  		return err
   249  	}
   250  
   251  	if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
   252  		return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  func (p *Provisioner) Cancel() {
   259  	// Just hard quit. It isn't a big deal if what we're doing keeps
   260  	// running on the other side.
   261  	os.Exit(0)
   262  }
   263  
   264  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   265  	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
   266  
   267  	cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
   268  	if err := cmd.StartWithUi(comm, ui); err != nil {
   269  		return err
   270  	}
   271  	if cmd.ExitStatus != 0 {
   272  		return fmt.Errorf("Non-zero exit status. See output above for more info.")
   273  	}
   274  
   275  	// Chmod the directory to 0777 just so that we can access it as our user
   276  	cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
   277  	if err := cmd.StartWithUi(comm, ui); err != nil {
   278  		return err
   279  	}
   280  	if cmd.ExitStatus != 0 {
   281  		return fmt.Errorf("Non-zero exit status. See output above for more info.")
   282  	}
   283  
   284  	return nil
   285  }
   286  
   287  func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
   288  	if err := p.createDir(ui, comm, dst); err != nil {
   289  		return err
   290  	}
   291  
   292  	// Make sure there is a trailing "/" so that the directory isn't
   293  	// created on the other side.
   294  	if src[len(src)-1] != '/' {
   295  		src = src + "/"
   296  	}
   297  
   298  	return comm.UploadDir(dst, src, nil)
   299  }