mirror of https://github.com/hashicorp/packer
This closely modeled after the same provisioner in Vagrant.pull/919/head
parent
73e57d918f
commit
646f4a6dd3
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer/plugin"
|
||||
"github.com/mitchellh/packer/provisioner/puppet-server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
server, err := plugin.Server()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
server.RegisterProvisioner(new(puppetserver.Provisioner))
|
||||
server.Serve()
|
||||
}
|
||||
@ -0,0 +1,244 @@
|
||||
// This package implements a provisioner for Packer that executes
|
||||
// Puppet on the remote machine connecting to a Puppet master.
|
||||
package puppetserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mitchellh/packer/common"
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
tpl *packer.ConfigTemplate
|
||||
|
||||
// Additional facts to set when executing Puppet
|
||||
Facter map[string]string
|
||||
|
||||
// A path to the client certificate
|
||||
ClientCertPath string `mapstructure:"client_cert_path"`
|
||||
|
||||
// A path to a directory containing the client private keys
|
||||
ClientPrivateKeyPath string `mapstructure:"client_private_key_path"`
|
||||
|
||||
PuppetNode string `mapstructure:"puppet_node"`
|
||||
PuppetServer string `mapstructure:"puppet_server"`
|
||||
Options string `mapstructure:"options"`
|
||||
|
||||
StagingDir string `mapstructure:"staging_dir"`
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
}
|
||||
|
||||
type ExecuteTemplate struct {
|
||||
FacterVars string
|
||||
ClientCertPath string
|
||||
ClientPrivateKeyPath string
|
||||
PuppetNode string
|
||||
PuppetServer string
|
||||
Options string
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
md, err := common.DecodeConfig(&p.config, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.config.tpl, err = packer.NewConfigTemplate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.config.tpl.UserVars = p.config.PackerUserVars
|
||||
|
||||
// Accumulate any errors
|
||||
errs := common.CheckUnusedConfig(md)
|
||||
|
||||
if p.config.StagingDir == "" {
|
||||
p.config.StagingDir = "/tmp/packer-puppet-server"
|
||||
}
|
||||
|
||||
// Templates
|
||||
templates := map[string]*string{
|
||||
"client_cert_dir": &p.config.ClientCertPath,
|
||||
"client_private_key_dir": &p.config.ClientPrivateKeyPath,
|
||||
"puppet_server": &p.config.PuppetServer,
|
||||
"puppet_node": &p.config.PuppetNode,
|
||||
"options": &p.config.Options,
|
||||
}
|
||||
|
||||
for n, ptr := range templates {
|
||||
var err error
|
||||
*ptr, err = p.config.tpl.Process(*ptr, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("Error processing %s: %s", n, err))
|
||||
}
|
||||
}
|
||||
|
||||
newFacts := make(map[string]string)
|
||||
for k, v := range p.config.Facter {
|
||||
k, err := p.config.tpl.Process(k, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Error processing facter key %s: %s", k, err))
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := p.config.tpl.Process(v, nil)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("Error processing facter value '%s': %s", v, err))
|
||||
continue
|
||||
}
|
||||
|
||||
newFacts[k] = v
|
||||
}
|
||||
|
||||
p.config.Facter = newFacts
|
||||
|
||||
if p.config.ClientCertPath != "" {
|
||||
info, err := os.Stat(p.config.ClientCertPath)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("client_cert_dir is invalid: %s", err))
|
||||
} else if !info.IsDir() {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("client_cert_dir must point to a directory"))
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.ClientPrivateKeyPath != "" {
|
||||
info, err := os.Stat(p.config.ClientPrivateKeyPath)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("client_private_key_dir is invalid: %s", err))
|
||||
} else if !info.IsDir() {
|
||||
errs = packer.MultiErrorAppend(errs,
|
||||
fmt.Errorf("client_private_key_dir must point to a directory"))
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say("Provisioning with Puppet...")
|
||||
ui.Message("Creating Puppet staging directory...")
|
||||
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
|
||||
return fmt.Errorf("Error creating staging directory: %s", err)
|
||||
}
|
||||
|
||||
// Upload client cert dir if set
|
||||
remoteClientCertPath := ""
|
||||
if p.config.ClientCertPath != "" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Uploading client cert from: %s", p.config.ClientCertPath))
|
||||
remoteClientCertPath = fmt.Sprintf("%s/certs", p.config.StagingDir)
|
||||
err := p.uploadDirectory(ui, comm, remoteClientCertPath, p.config.ClientCertPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error uploading client cert: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Upload client cert dir if set
|
||||
remoteClientPrivateKeyPath := ""
|
||||
if p.config.ClientPrivateKeyPath != "" {
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Uploading client private keys from: %s", p.config.ClientPrivateKeyPath))
|
||||
remoteClientPrivateKeyPath = fmt.Sprintf("%s/private_keys", p.config.StagingDir)
|
||||
err := p.uploadDirectory(ui, comm, remoteClientPrivateKeyPath, p.config.ClientPrivateKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error uploading client private keys: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the facter variables
|
||||
facterVars := make([]string, 0, len(p.config.Facter))
|
||||
for k, v := range p.config.Facter {
|
||||
facterVars = append(facterVars, fmt.Sprintf("FACTER_%s='%s'", k, v))
|
||||
}
|
||||
|
||||
// Execute Puppet
|
||||
command, err := p.config.tpl.Process(p.commandTemplate(), &ExecuteTemplate{
|
||||
FacterVars: strings.Join(facterVars, " "),
|
||||
ClientCertPath: remoteClientCertPath,
|
||||
ClientPrivateKeyPath: remoteClientPrivateKeyPath,
|
||||
PuppetNode: p.config.PuppetNode,
|
||||
PuppetServer: p.config.PuppetServer,
|
||||
Options: p.config.Options,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: command,
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf("Running Puppet: %s", command))
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 {
|
||||
return fmt.Errorf("Puppet exited with a non-zero exit status: %d", cmd.ExitStatus)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Cancel() {
|
||||
// Just hard quit. It isn't a big deal if what we're doing keeps
|
||||
// running on the other side.
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
|
||||
cmd := &packer.RemoteCmd{
|
||||
Command: fmt.Sprintf("mkdir -p '%s'", dir),
|
||||
}
|
||||
|
||||
if err := cmd.StartWithUi(comm, ui); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.ExitStatus != 0 {
|
||||
return fmt.Errorf("Non-zero exit status.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
|
||||
if err := p.createDir(ui, comm, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure there is a trailing "/" so that the directory isn't
|
||||
// created on the other side.
|
||||
if src[len(src)-1] != '/' {
|
||||
src = src + "/"
|
||||
}
|
||||
|
||||
return comm.UploadDir(dst, src, nil)
|
||||
}
|
||||
|
||||
func (p *Provisioner) commandTemplate() string {
|
||||
return "{{.FacterVars}} " +
|
||||
"puppet agent --onetime --no-daemonize " +
|
||||
"{{if ne .PuppetServer \"\"}}--server='{{.PuppetServer}}' {{end}}" +
|
||||
"{{if ne .Options \"\"}}{{.Options}} {{end}}" +
|
||||
"{{if ne .PuppetNode \"\"}}--certname={{.PuppetNode}} {{end}}" +
|
||||
"{{if ne .ClientCertPath \"\"}}--certdir='{{.ClientCertPath}}' {{end}}" +
|
||||
"{{if ne .ClientPrivateKeyPath \"\"}}--privatekeydir='{{.ClientPrivateKeyPath}}' {{end}}" +
|
||||
"--detailed-exitcodes"
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
package puppetserver
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/packer/packer"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() map[string]interface{} {
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"puppet_server": tf.Name(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_clientPrivateKeyPath(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "client_private_key_path")
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with bad paths
|
||||
config["client_private_key_path"] = "i-should-not-exist"
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should be an error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config["client_private_key_path"] = td
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_clientCertPath(t *testing.T) {
|
||||
config := testConfig()
|
||||
|
||||
delete(config, "client_cert_path")
|
||||
p := new(Provisioner)
|
||||
err := p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test with bad paths
|
||||
config["client_cert_path"] = "i-should-not-exist"
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should be an error")
|
||||
}
|
||||
|
||||
// Test with a good one
|
||||
td, err := ioutil.TempDir("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("error: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
config["client_cert_path"] = td
|
||||
p = new(Provisioner)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue