Merge branch 'master' of https://github.com/hashicorp/packer into vsphere-tpl

This commit is contained in:
bugbuilder 2017-08-31 22:22:51 -03:00
commit abc21406cb
19 changed files with 561 additions and 185 deletions

View File

@ -5,6 +5,9 @@
* provisioner/salt-masterless: Also use sudo to clean up if we used sudo to install. [GH-5240]
* builder/profitbricks: added support for Cloud API v4. [GH-5233]
* builder/vmware: Set artifact ID to `VMName`. [GH-5187]
* core: Fix issue where some builders wouldn't respect `-on-error` behavior. [GH-5297]
* builder/cloudstack: Add support for Security Groups. [GH-5175]
* provisioner/puppet: Add `guest_os_type` option to add support for Windows. [GH-5252]
### BACKWARDS INCOMPATIBILITIES:

View File

@ -1,6 +1,8 @@
package cloudstack
import (
"fmt"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/communicator"
"github.com/hashicorp/packer/packer"
@ -61,8 +63,18 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
HTTPPortMin: b.config.HTTPPortMin,
HTTPPortMax: b.config.HTTPPortMax,
},
&stepKeypair{
Debug: b.config.PackerDebug,
DebugKeyPath: fmt.Sprintf("cs_%s.pem", b.config.PackerBuildName),
KeyPair: b.config.Keypair,
PrivateKeyFile: b.config.Comm.SSHPrivateKey,
SSHAgentAuth: b.config.Comm.SSHAgentAuth,
TemporaryKeyPairName: b.config.TemporaryKeypairName,
},
&stepCreateSecurityGroup{},
&stepCreateInstance{
Ctx: b.config.ctx,
Ctx: b.config.ctx,
Debug: b.config.PackerDebug,
},
&stepSetupNetworking{},
&communicator.StepConnect{
@ -78,17 +90,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&stepCreateTemplate{},
}
// Configure the runner.
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
// Run the steps.
// Configure the runner and run the steps.
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// If there was an error, return that

View File

@ -27,23 +27,26 @@ type Config struct {
HTTPGetOnly bool `mapstructure:"http_get_only"`
SSLNoVerify bool `mapstructure:"ssl_no_verify"`
CIDRList []string `mapstructure:"cidr_list"`
DiskOffering string `mapstructure:"disk_offering"`
DiskSize int64 `mapstructure:"disk_size"`
Expunge bool `mapstructure:"expunge"`
Hypervisor string `mapstructure:"hypervisor"`
InstanceName string `mapstructure:"instance_name"`
Keypair string `mapstructure:"keypair"`
Network string `mapstructure:"network"`
Project string `mapstructure:"project"`
PublicIPAddress string `mapstructure:"public_ip_address"`
ServiceOffering string `mapstructure:"service_offering"`
SourceTemplate string `mapstructure:"source_template"`
SourceISO string `mapstructure:"source_iso"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
UseLocalIPAddress bool `mapstructure:"use_local_ip_address"`
Zone string `mapstructure:"zone"`
CIDRList []string `mapstructure:"cidr_list"`
CreateSecurityGroup bool `mapstructure:"create_security_group"`
DiskOffering string `mapstructure:"disk_offering"`
DiskSize int64 `mapstructure:"disk_size"`
Expunge bool `mapstructure:"expunge"`
Hypervisor string `mapstructure:"hypervisor"`
InstanceName string `mapstructure:"instance_name"`
Keypair string `mapstructure:"keypair"`
Network string `mapstructure:"network"`
Project string `mapstructure:"project"`
PublicIPAddress string `mapstructure:"public_ip_address"`
SecurityGroups []string `mapstructure:"security_groups"`
ServiceOffering string `mapstructure:"service_offering"`
SourceISO string `mapstructure:"source_iso"`
SourceTemplate string `mapstructure:"source_template"`
TemporaryKeypairName string `mapstructure:"temporary_keypair_name"`
UseLocalIPAddress bool `mapstructure:"use_local_ip_address"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
Zone string `mapstructure:"zone"`
TemplateName string `mapstructure:"template_name"`
TemplateDisplayText string `mapstructure:"template_display_text"`
@ -98,7 +101,7 @@ func NewConfig(raws ...interface{}) (*Config, error) {
c.AsyncTimeout = 30 * time.Minute
}
if len(c.CIDRList) == 0 && !c.UseLocalIPAddress {
if len(c.CIDRList) == 0 {
c.CIDRList = []string{"0.0.0.0/0"}
}
@ -120,6 +123,14 @@ func NewConfig(raws ...interface{}) (*Config, error) {
c.TemplateDisplayText = c.TemplateName
}
// If we are not given an explicit keypair, ssh_password or ssh_private_key_file,
// then create a temporary one, but only if the temporary_keypair_name has not
// been provided.
if c.Keypair == "" && c.TemporaryKeypairName == "" &&
c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" {
c.TemporaryKeypairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
}
// Process required parameters.
if c.APIURL == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a api_url must be specified"))
@ -137,6 +148,10 @@ func NewConfig(raws ...interface{}) (*Config, error) {
errs = packer.MultiErrorAppend(errs, errors.New("a network must be specified"))
}
if c.CreateSecurityGroup && !c.Expunge {
errs = packer.MultiErrorAppend(errs, errors.New("auto creating a temporary security group requires expunge"))
}
if c.ServiceOffering == "" {
errs = packer.MultiErrorAppend(errs, errors.New("a service_offering must be specified"))
}

View File

@ -22,7 +22,8 @@ type userDataTemplateData struct {
// stepCreateInstance represents a Packer build step that creates CloudStack instances.
type stepCreateInstance struct {
Ctx interpolate.Context
Debug bool
Ctx interpolate.Context
}
// Run executes the Packer build step that creates a CloudStack instance.
@ -44,8 +45,12 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
p.SetName(config.InstanceName)
p.SetDisplayname("Created by Packer")
if config.Keypair != "" {
p.SetKeypair(config.Keypair)
if keypair, ok := state.GetOk("keypair"); ok {
p.SetKeypair(keypair.(string))
}
if securitygroups, ok := state.GetOk("security_groups"); ok {
p.SetSecuritygroupids(securitygroups.([]string))
}
// If we use an ISO, configure the disk offering.
@ -115,6 +120,12 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction
ui.Message("Instance has been created!")
// In debug-mode, we output the password
if s.Debug {
ui.Message(fmt.Sprintf(
"Password (since debug is enabled) \"%s\"", instance.Password))
}
// Set the auto generated password if a password was not explicitly configured.
switch config.Comm.Type {
case "ssh":

View File

@ -0,0 +1,94 @@
package cloudstack
import (
"fmt"
"github.com/hashicorp/packer/common/uuid"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"github.com/xanzy/go-cloudstack/cloudstack"
)
type stepCreateSecurityGroup struct {
tempSG string
}
func (s *stepCreateSecurityGroup) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(*cloudstack.CloudStackClient)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if len(config.SecurityGroups) > 0 {
state.Put("security_groups", config.SecurityGroups)
return multistep.ActionContinue
}
if !config.CreateSecurityGroup {
return multistep.ActionContinue
}
ui.Say("Creating temporary Security Group...")
p := client.SecurityGroup.NewCreateSecurityGroupParams(
fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()),
)
p.SetDescription("Temporary SG created by Packer")
if config.Project != "" {
p.SetProjectid(config.Project)
}
sg, err := client.SecurityGroup.CreateSecurityGroup(p)
if err != nil {
err := fmt.Errorf("Failed to create security group: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.tempSG = sg.Id
state.Put("security_groups", []string{sg.Id})
// Create Ingress rule
i := client.SecurityGroup.NewAuthorizeSecurityGroupIngressParams()
i.SetCidrlist(config.CIDRList)
i.SetProtocol("TCP")
i.SetSecuritygroupid(sg.Id)
i.SetStartport(config.Comm.Port())
i.SetEndport(config.Comm.Port())
if config.Project != "" {
i.SetProjectid(config.Project)
}
_, err = client.SecurityGroup.AuthorizeSecurityGroupIngress(i)
if err != nil {
err := fmt.Errorf("Failed to authorize security group ingress rule: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
return multistep.ActionContinue
}
// Cleanup any resources that may have been created during the Run phase.
func (s *stepCreateSecurityGroup) Cleanup(state multistep.StateBag) {
client := state.Get("client").(*cloudstack.CloudStackClient)
config := state.Get("config").(*Config)
ui := state.Get("ui").(packer.Ui)
if s.tempSG == "" {
return
}
ui.Say(fmt.Sprintf("Cleanup temporary security group: %s ...", s.tempSG))
p := client.SecurityGroup.NewDeleteSecurityGroupParams()
p.SetId(s.tempSG)
if config.Project != "" {
p.SetProjectid(config.Project)
}
if _, err := client.SecurityGroup.DeleteSecurityGroup(p); err != nil {
ui.Error(err.Error())
ui.Error(fmt.Sprintf("Error deleting security group: %s. Please destroy it manually.\n", s.tempSG))
}
}

View File

@ -0,0 +1,133 @@
package cloudstack
import (
"fmt"
"io/ioutil"
"os"
"runtime"
"github.com/hashicorp/packer/packer"
"github.com/mitchellh/multistep"
"github.com/xanzy/go-cloudstack/cloudstack"
)
type stepKeypair struct {
Debug bool
DebugKeyPath string
KeyPair string
PrivateKeyFile string
SSHAgentAuth bool
TemporaryKeyPairName string
}
func (s *stepKeypair) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui)
if s.PrivateKeyFile != "" {
privateKeyBytes, err := ioutil.ReadFile(s.PrivateKeyFile)
if err != nil {
state.Put("error", fmt.Errorf(
"Error loading configured private key file: %s", err))
return multistep.ActionHalt
}
state.Put("keypair", s.KeyPair)
state.Put("privateKey", string(privateKeyBytes))
return multistep.ActionContinue
}
if s.SSHAgentAuth && s.KeyPair == "" {
ui.Say("Using SSH Agent with keypair in Source image")
return multistep.ActionContinue
}
if s.SSHAgentAuth && s.KeyPair != "" {
ui.Say(fmt.Sprintf("Using SSH Agent for existing keypair %s", s.KeyPair))
state.Put("keypair", s.KeyPair)
return multistep.ActionContinue
}
if s.TemporaryKeyPairName == "" {
ui.Say("Not using a keypair")
state.Put("keypair", "")
return multistep.ActionContinue
}
client := state.Get("client").(*cloudstack.CloudStackClient)
ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", s.TemporaryKeyPairName))
p := client.SSH.NewCreateSSHKeyPairParams(s.TemporaryKeyPairName)
keypair, err := client.SSH.CreateSSHKeyPair(p)
if err != nil {
err := fmt.Errorf("Error creating temporary keypair: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if keypair.Privatekey == "" {
err := fmt.Errorf("The temporary keypair returned was blank")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Created temporary keypair: %s", s.TemporaryKeyPairName))
// If we're in debug mode, output the private key to the working directory.
if s.Debug {
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
f, err := os.Create(s.DebugKeyPath)
if err != nil {
state.Put("error", fmt.Errorf("Error saving debug key: %s", err))
return multistep.ActionHalt
}
defer f.Close()
// Write the key out
if _, err := f.Write([]byte(keypair.Privatekey)); err != nil {
err := fmt.Errorf("Error saving debug key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
// Chmod it so that it is SSH ready
if runtime.GOOS != "windows" {
if err := f.Chmod(0600); err != nil {
err := fmt.Errorf("Error setting permissions of debug key: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
}
// Set some state data for use in future steps
state.Put("keypair", s.TemporaryKeyPairName)
state.Put("privateKey", keypair.Privatekey)
return multistep.ActionContinue
}
func (s *stepKeypair) Cleanup(state multistep.StateBag) {
if s.TemporaryKeyPairName == "" {
return
}
ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(*cloudstack.CloudStackClient)
ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.TemporaryKeyPairName))
_, err := client.SSH.DeleteSSHKeyPair(client.SSH.NewDeleteSSHKeyPairParams(
s.TemporaryKeyPairName,
))
if err != nil {
ui.Error(err.Error())
ui.Error(fmt.Sprintf(
"Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPairName))
}
}

View File

@ -22,15 +22,6 @@ func (s *stepPrepareConfig) Run(state multistep.StateBag) multistep.StepAction {
var err error
var errs *packer.MultiError
if config.Comm.SSHPrivateKey != "" {
privateKey, err := ioutil.ReadFile(config.Comm.SSHPrivateKey)
if err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error loading configured private key file: %s", err))
}
state.Put("privateKey", privateKey)
}
// First get the project and zone UUID's so we can use them in other calls when needed.
if config.Project != "" && !isUUID(config.Project) {
config.Project, _, err = client.Project.GetProjectID(config.Project)
@ -92,6 +83,18 @@ func (s *stepPrepareConfig) Run(state multistep.StateBag) multistep.StepAction {
}
}
// Then try to get the SG's UUID's.
if len(config.SecurityGroups) > 0 {
for i := range config.SecurityGroups {
if !isUUID(config.SecurityGroups[i]) {
config.SecurityGroups[i], _, err = client.SecurityGroup.GetSecurityGroupID(config.SecurityGroups[i], cloudstack.WithProject(config.Project))
if err != nil {
errs = packer.MultiErrorAppend(errs, &retrieveErr{"network", config.SecurityGroups[i], err})
}
}
}
}
if !isUUID(config.ServiceOffering) {
config.ServiceOffering, _, err = client.ServiceOffering.GetServiceOfferingID(config.ServiceOffering)
if err != nil {

View File

@ -405,17 +405,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
// Run the steps.
if b.config.PackerDebug {
pauseFn := common.MultistepDebugFn(ui)
state.Put("pauseFn", pauseFn)
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: pauseFn,
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
// Report any errors.

View File

@ -50,15 +50,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new(stepTakeSnapshot),
}
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {

View File

@ -49,15 +49,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
config := state.Get("config").(*Config)
if b.config.PackerDebug {
b.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
b.runner = &multistep.BasicRunner{Steps: steps}
}
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
b.runner.Run(state)
if rawErr, ok := state.GetOk("error"); ok {

View File

@ -200,6 +200,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
default:
dir = new(vmwcommon.LocalOutputDir)
}
exportOutputPath := b.config.OutputDir
if b.config.RemoteType != "" && b.config.Format != "" {
b.config.OutputDir = b.config.VMName
}
@ -307,6 +310,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
&StepExport{
Format: b.config.Format,
SkipExport: b.config.SkipExport,
OutputDir: exportOutputPath,
},
}
@ -332,7 +336,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
var files []string
if b.config.RemoteType != "" && b.config.Format != "" {
dir = new(vmwcommon.LocalOutputDir)
dir.SetOutputDir(b.config.OutputDir)
dir.SetOutputDir(exportOutputPath)
files, err = dir.ListFiles()
} else {
files, err = state.Get("dir").(OutputDir).ListFiles()

View File

@ -6,7 +6,6 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
@ -17,9 +16,10 @@ import (
type StepExport struct {
Format string
SkipExport bool
OutputDir string
}
func (s *StepExport) generateArgs(c *Config, outputPath string, hidePassword bool) []string {
func (s *StepExport) generateArgs(c *Config, hidePassword bool) []string {
password := url.QueryEscape(c.RemotePassword)
if hidePassword {
password = "****"
@ -29,7 +29,7 @@ func (s *StepExport) generateArgs(c *Config, outputPath string, hidePassword boo
"--skipManifestCheck",
"-tt=" + s.Format,
"vi://" + c.RemoteUser + ":" + password + "@" + c.RemoteHost + "/" + c.VMName,
outputPath,
s.OutputDir,
}
return append(c.OVFToolOptions, args...)
}
@ -62,16 +62,18 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
}
// Export the VM
outputPath := filepath.Join(c.VMName, c.VMName+"."+s.Format)
if s.OutputDir == "" {
s.OutputDir = c.VMName + "." + s.Format
}
if s.Format == "ova" {
os.MkdirAll(outputPath, 0755)
os.MkdirAll(s.OutputDir, 0755)
}
ui.Say("Exporting virtual machine...")
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(s.generateArgs(c, outputPath, true), " ")))
ui.Message(fmt.Sprintf("Executing: %s %s", ovftool, strings.Join(s.generateArgs(c, true), " ")))
var out bytes.Buffer
cmd := exec.Command(ovftool, s.generateArgs(c, outputPath, false)...)
cmd := exec.Command(ovftool, s.generateArgs(c, false)...)
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
err := fmt.Errorf("Error exporting virtual machine: %s\n%s\n", err, out.String())
@ -82,8 +84,6 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction {
ui.Message(fmt.Sprintf("%s", out.String()))
state.Put("exportPath", outputPath)
return multistep.ActionContinue
}

View File

@ -120,14 +120,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
}
// Run the steps.
if p.config.PackerDebug {
p.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
p.runner = &multistep.BasicRunner{Steps: steps}
}
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
}

View File

@ -164,15 +164,7 @@ func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (pac
}
// Run the steps
if p.config.PackerDebug {
p.runner = &multistep.DebugRunner{
Steps: steps,
PauseFn: common.MultistepDebugFn(ui),
}
} else {
p.runner = &multistep.BasicRunner{Steps: steps}
}
p.runner = common.NewRunner(steps, p.config.PackerConfig, ui)
p.runner.Run(state)
// If there was an error, return that

View File

@ -1,4 +1,4 @@
// This package implements a provisioner for Packer that executes
// Package puppetmasterless implements a provisioner for Packer that executes
// Puppet on the remote machine, configured to apply a local manifest
// versus connecting to a Puppet master.
package puppetmasterless
@ -12,6 +12,7 @@ import (
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate"
)
@ -61,10 +62,51 @@ type Config struct {
// If true, packer will ignore all exit-codes from a puppet run
IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"`
// The Guest OS Type (unix or windows)
GuestOSType string `mapstructure:"guest_os_type"`
}
type guestOSTypeConfig struct {
stagingDir string
executeCommand string
facterVarsFmt string
modulePathJoiner string
}
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
provisioner.UnixOSType: {
stagingDir: "/tmp/packer-puppet-masterless",
executeCommand: "cd {{.WorkingDir}} && " +
"{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}",
facterVarsFmt: "FACTER_%s='%s'",
modulePathJoiner: ":",
},
provisioner.WindowsOSType: {
stagingDir: "C:/Windows/Temp/packer-puppet-masterless",
executeCommand: "cd {{.WorkingDir}} && " +
"{{.FacterVars}} && " +
"puppet apply --verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}",
facterVarsFmt: "SET \"FACTER_%s=%s\" &",
modulePathJoiner: ";",
},
}
type Provisioner struct {
config Config
config Config
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
type ExecuteTemplate struct {
@ -94,20 +136,32 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
// Set some defaults
if p.config.GuestOSType == "" {
p.config.GuestOSType = provisioner.DefaultOSType
}
p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
var ok bool
p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
if !ok {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
if err != nil {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = "cd {{.WorkingDir}} && " +
"{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}puppet apply " +
"--verbose --modulepath='{{.ModulePath}}' " +
"{{if ne .HieraConfigPath \"\"}}--hiera_config='{{.HieraConfigPath}}' {{end}}" +
"{{if ne .ManifestDir \"\"}}--manifestdir='{{.ManifestDir}}' {{end}}" +
"--detailed-exitcodes " +
"{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" +
"{{.ManifestFile}}"
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.StagingDir == "" {
p.config.StagingDir = "/tmp/packer-puppet-masterless"
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
}
if p.config.WorkingDir == "" {
@ -223,7 +277,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// 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))
facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v))
}
// Execute Puppet
@ -232,7 +286,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
HieraConfigPath: remoteHieraConfigPath,
ManifestDir: remoteManifestDir,
ManifestFile: remoteManifestFile,
ModulePath: strings.Join(modulePaths, ":"),
ModulePath: strings.Join(modulePaths, p.guestOSTypeConfig.modulePathJoiner),
PuppetBinDir: p.config.PuppetBinDir,
Sudo: !p.config.PreventSudo,
WorkingDir: p.config.WorkingDir,
@ -249,7 +303,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Message(fmt.Sprintf("Running Puppet: %s", command))
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
return fmt.Errorf("Got an error starting command: %s", err)
}
if cmd.ExitStatus != 0 && cmd.ExitStatus != 2 && !p.config.IgnoreExitCodes {
@ -314,30 +368,29 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s
return "", fmt.Errorf("Error uploading manifest dir: %s", err)
}
return remoteManifestDir, nil
} else {
// Otherwise manifest_file is a file and we'll upload it
ui.Message(fmt.Sprintf(
"Uploading manifest file from: %s", p.config.ManifestFile))
f, err := os.Open(p.config.ManifestFile)
if err != nil {
return "", err
}
defer f.Close()
manifestFilename := filepath.Base(p.config.ManifestFile)
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
return "", err
}
return remoteManifestFile, nil
}
// Otherwise manifest_file is a file and we'll upload it
ui.Message(fmt.Sprintf(
"Uploading manifest file from: %s", p.config.ManifestFile))
f, err := os.Open(p.config.ManifestFile)
if err != nil {
return "", err
}
defer f.Close()
manifestFilename := filepath.Base(p.config.ManifestFile)
remoteManifestFile := fmt.Sprintf("%s/%s", remoteManifestsPath, manifestFilename)
if err := comm.Upload(remoteManifestFile, f, nil); err != nil {
return "", err
}
return remoteManifestFile, nil
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err

View File

@ -1,4 +1,4 @@
// This package implements a provisioner for Packer that executes
// Package puppetserver implements a provisioner for Packer that executes
// Puppet on the remote machine connecting to a Puppet master.
package puppetserver
@ -10,9 +10,45 @@ import (
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/provisioner"
"github.com/hashicorp/packer/template/interpolate"
)
type guestOSTypeConfig struct {
executeCommand string
facterVarsFmt string
stagingDir string
}
var guestOSTypeConfigs = map[string]guestOSTypeConfig{
provisioner.UnixOSType: {
executeCommand: "{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}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",
facterVarsFmt: "FACTER_%s='%s'",
stagingDir: "/tmp/packer-puppet-server",
},
provisioner.WindowsOSType: {
executeCommand: "{{.FacterVars}} " +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}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",
facterVarsFmt: "SET \"FACTER_%s=%s\" &",
stagingDir: "C:/Windows/Temp/packer-puppet-server",
},
}
type Config struct {
common.PackerConfig `mapstructure:",squash"`
ctx interpolate.Context
@ -20,6 +56,9 @@ type Config struct {
// The command used to execute Puppet.
ExecuteCommand string `mapstructure:"execute_command"`
// The Guest OS Type (unix or windows)
GuestOSType string `mapstructure:"guest_os_type"`
// Additional facts to set when executing Puppet
Facter map[string]string
@ -54,7 +93,9 @@ type Config struct {
}
type Provisioner struct {
config Config
config Config
guestOSTypeConfig guestOSTypeConfig
guestCommands *provisioner.GuestCommands
}
type ExecuteTemplate struct {
@ -82,12 +123,28 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return err
}
if p.config.GuestOSType == "" {
p.config.GuestOSType = provisioner.DefaultOSType
}
p.config.GuestOSType = strings.ToLower(p.config.GuestOSType)
var ok bool
p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType]
if !ok {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo)
if err != nil {
return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType)
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = p.commandTemplate()
p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand
}
if p.config.StagingDir == "" {
p.config.StagingDir = "/tmp/packer-puppet-server"
p.config.StagingDir = p.guestOSTypeConfig.stagingDir
}
if p.config.Facter == nil {
@ -160,7 +217,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// 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))
facterVars = append(facterVars, fmt.Sprintf(p.guestOSTypeConfig.facterVarsFmt, k, v))
}
// Execute Puppet
@ -202,16 +259,23 @@ func (p *Provisioner) Cancel() {
}
func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
}
ui.Message(fmt.Sprintf("Creating directory: %s", dir))
cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status.")
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
// Chmod the directory to 0777 just so that we can access it as our user
cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")}
if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}
if cmd.ExitStatus != 0 {
return fmt.Errorf("Non-zero exit status. See output above for more info.")
}
return nil
@ -230,15 +294,3 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds
return comm.UploadDir(dst, src, nil)
}
func (p *Provisioner) commandTemplate() string {
return "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" +
"{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}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"
}

View File

@ -74,6 +74,11 @@ builder.
connect to the instance. Defaults to `[ "0.0.0.0/0" ]`. Only required
when `use_local_ip_address` is `false`.
- `create_security_group` (boolean) - If `true` a temporary security group
will be created which allows traffic towards the instance from the
`cidr_list`. This option will be ignored if `security_groups` is also
defined. Requires `expunge` set to `true`. Defaults to `false`.
- `disk_offering` (string) - The name or ID of the disk offering used for the
instance. This option is only available (and also required) when using
`source_iso`.
@ -118,6 +123,9 @@ builder.
connecting any provisioners to. If not provided, a temporary public IP
address will be associated and released during the Packer run.
- `security_groups` (array of strings) - A list of security group IDs or names
to associate the instance with.
- `ssh_agent_auth` (boolean) - If true, the local SSH agent will be used to
authenticate connections to the source instance. No temporary keypair will
be created, and the values of `ssh_password` and `ssh_private_key_file` will
@ -149,6 +157,10 @@ builder.
- `template_scalable` (boolean) - Set to `true` to indicate that the template
contains tools to support dynamic scaling of VM cpu/memory. Defaults to `false`.
- `temporary_keypair_name` (string) - The name of the temporary SSH key pair
to generate. By default, Packer generates a name that looks like
`packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
- `user_data` (string) - User data to launch with the instance. This is a
[template engine](/docs/templates/engine.html) see _User Data_ bellow for more
details.

View File

@ -59,6 +59,10 @@ Optional parameters:
variables](/docs/templates/engine.html) available. See
below for more information.
- `guest_os_type` (string) - The target guest OS type, either "unix" or
"windows". Setting this to "windows" will cause the provisioner to use
Windows friendly paths and commands. By default, this is "unix".
- `extra_arguments` (array of strings) - This is an array of additional options to
pass to the puppet command when executing puppet. This allows for
customization of the `execute_command` without having to completely replace
@ -99,12 +103,13 @@ multiple manifests you should use `manifest_file` instead.
executed to run Puppet are executed with `sudo`. If this is true, then the
sudo will be omitted.
- `staging_directory` (string) - This is the directory where all the
configuration of Puppet by Packer will be placed. By default this
is "/tmp/packer-puppet-masterless". This directory doesn't need to exist but
must have proper permissions so that the SSH user that Packer uses is able
to create directories and write into this folder. If the permissions are not
correct, use a shell provisioner prior to this to configure it properly.
- `staging_directory` (string) - This is the directory where all the configuration
of Puppet by Packer will be placed. By default this is "/tmp/packer-puppet-masterless"
when guest OS type is unix and "C:/Windows/Temp/packer-puppet-masterless" when windows.
This directory doesn't need to exist but must have proper permissions so that the SSH
user that Packer uses is able to create directories and write into this folder.
If the permissions are not correct, use a shell provisioner prior to this to configure
it properly.
- `working_directory` (string) - This is the directory from which the puppet
command will be run. When using hiera with a relative path, this option
@ -117,17 +122,28 @@ multiple manifests you should use `manifest_file` instead.
By default, Packer uses the following command (broken across multiple lines for
readability) to execute Puppet:
``` liquid
cd {{.WorkingDir}} && \
{{.FacterVars}}{{if .Sudo}} sudo -E {{end}} \
{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}{{end}}puppet apply \
--verbose \
--modulepath='{{.ModulePath}}' \
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}} \
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}} \
--detailed-exitcodes \
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}} \
{{.ManifestFile}}
```
cd {{.WorkingDir}} &&
{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}
puppet apply --verbose --modulepath='{{.ModulePath}}'
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}}
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}}
--detailed-exitcodes
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}}
{{.ManifestFile}}
```
The following command is used if guest OS type is windows:
```
cd {{.WorkingDir}} &&
{{.FacterVars}} &&
puppet apply --verbose --modulepath='{{.ModulePath}}'
{{if ne .HieraConfigPath ""}}--hiera_config='{{.HieraConfigPath}}' {{end}}
{{if ne .ManifestDir ""}}--manifestdir='{{.ManifestDir}}' {{end}}
--detailed-exitcodes
{{if ne .ExtraArguments ""}}{{.ExtraArguments}} {{end}}
{{.ManifestFile}}
```
This command can be customized using the `execute_command` configuration. As you

View File

@ -81,20 +81,38 @@ listed below:
or `%PATH%` environment variable, but some builders (notably, the Docker one) do
not run profile-setup scripts, therefore the path is usually empty.
- `execute_command` (string) - This is optional. The command used to execute Puppet. This has
various [configuration template
variables](/docs/templates/engine.html) available. See
below for more information. By default, Packer uses the following command:
- `guest_os_type` (string) - The target guest OS type, either "unix" or
"windows". Setting this to "windows" will cause the provisioner to use
Windows friendly paths and commands. By default, this is "unix".
``` liquid
{{.FacterVars}} {{if .Sudo}} sudo -E {{end}} \
{{if ne .PuppetBinDir \"\"}}{{.PuppetBinDir}}/{{end}}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
- `execute_command` (string) - This is optional. The command used to execute Puppet. This has
various [configuration template variables](/docs/templates/engine.html) available. By default,
Packer uses the following command (broken across multiple lines for readability) to execute Puppet:
```
{{.FacterVars}} {{if .Sudo}}sudo -E {{end}}
{{if ne .PuppetBinDir ""}}{{.PuppetBinDir}}/{{end}}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
```
The following command is used if guest OS type is windows:
```
{{.FacterVars}}
{{if ne .PuppetBinDir ""}}{{.PuppetBinDir}}/{{end}}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
```
## Default Facts