From 56c56282058c748f2a748718c35a11c93370dcbb Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Mon, 23 Jan 2017 17:47:35 -0800 Subject: [PATCH 01/22] builder/vmware-iso: set ovftool output path --- builder/vmware/iso/step_export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index 974c89852..13f5cf77f 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -59,7 +59,7 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { } // Export the VM - outputPath := filepath.Join(c.VMName, c.VMName+"."+s.Format) + outputPath := filepath.Join(c.OutputDir, c.VMName+"."+s.Format) if s.Format == "ova" { os.MkdirAll(outputPath, 0755) From f8df5f81dbc3851090122db31d30afe2e17c0701 Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 24 Feb 2017 12:46:00 +0000 Subject: [PATCH 02/22] builder/vmware-iso: set local output dir --- builder/vmware/iso/builder.go | 4 ++++ builder/vmware/iso/step_export.go | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 10fce5911..6f512ac82 100755 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -199,7 +199,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe default: dir = new(vmwcommon.LocalOutputDir) } + + var localDir localOutputDir if b.config.RemoteType != "" && b.config.Format != "" { + localDir = localOutputDir{b.config.OutputDir} b.config.OutputDir = b.config.VMName } dir.SetOutputDir(b.config.OutputDir) @@ -210,6 +213,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("dir", dir) + state.Put("localDir", localDir) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index 13f5cf77f..b7fcbd24c 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -59,7 +59,8 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { } // Export the VM - outputPath := filepath.Join(c.OutputDir, c.VMName+"."+s.Format) + localDir := state.Get("localDir").(localOutputDir) + outputPath := filepath.Join(fmt.Sprintf("%v", localDir), c.VMName+"."+s.Format) if s.Format == "ova" { os.MkdirAll(outputPath, 0755) From 7a2b30dcc44107560c89421fa4f1774b90e768ff Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 24 Feb 2017 13:18:28 +0000 Subject: [PATCH 03/22] builder/vmware-iso: need to always set local output dir, or non-remote build exports will fail --- builder/vmware/iso/builder.go | 4 ++-- builder/vmware/iso/step_export.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 6f512ac82..163d90184 100755 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -200,9 +200,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe dir = new(vmwcommon.LocalOutputDir) } - var localDir localOutputDir + localDir := localOutputDir{b.config.OutputDir} + log.Printf("b.config.OutputDir: %s, localDir: %s", b.config.OutputDir, localDir.dir) if b.config.RemoteType != "" && b.config.Format != "" { - localDir = localOutputDir{b.config.OutputDir} b.config.OutputDir = b.config.VMName } dir.SetOutputDir(b.config.OutputDir) diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index b7fcbd24c..c3ba3024a 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -60,7 +60,7 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { // Export the VM localDir := state.Get("localDir").(localOutputDir) - outputPath := filepath.Join(fmt.Sprintf("%v", localDir), c.VMName+"."+s.Format) + outputPath := filepath.Join(localDir.dir, c.VMName+"."+s.Format) if s.Format == "ova" { os.MkdirAll(outputPath, 0755) From 14810523b8aaf7f72951de2bbb42ce32d7981e7b Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 24 Feb 2017 14:48:38 +0000 Subject: [PATCH 04/22] builder/vmware-iso: get artifact files from local dir --- builder/vmware/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index 163d90184..877b779ff 100755 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -333,7 +333,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(localDir.dir) files, err = dir.ListFiles() } else { files, err = state.Get("dir").(OutputDir).ListFiles() From ce41055ac6b19c8040de9d8ec01e95eefeded5c2 Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 24 Feb 2017 14:49:40 +0000 Subject: [PATCH 05/22] builder/vmware-iso: do not append to output dir, as ovftool does that --- builder/vmware/iso/step_export.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index c3ba3024a..1e43d12fd 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "os/exec" - "path/filepath" "runtime" "strings" ) @@ -60,7 +59,7 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { // Export the VM localDir := state.Get("localDir").(localOutputDir) - outputPath := filepath.Join(localDir.dir, c.VMName+"."+s.Format) + outputPath := localDir.dir if s.Format == "ova" { os.MkdirAll(outputPath, 0755) From e851efb1b6e176176dd6498a310f5e62b0d5f33a Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 10 Mar 2017 08:20:48 +0000 Subject: [PATCH 06/22] Set export_dir to the output_dir property --- builder/vmware/iso/builder.go | 8 ++++---- builder/vmware/iso/step_export.go | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) mode change 100755 => 100644 builder/vmware/iso/builder.go diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go old mode 100755 new mode 100644 index 6eb6a30f1..b01cb1f32 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -201,8 +201,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe dir = new(vmwcommon.LocalOutputDir) } - localDir := localOutputDir{b.config.OutputDir} - log.Printf("b.config.OutputDir: %s, localDir: %s", b.config.OutputDir, localDir.dir) + exportOutputPath := b.config.OutputDir + if b.config.RemoteType != "" && b.config.Format != "" { b.config.OutputDir = b.config.VMName } @@ -214,8 +214,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("dir", dir) - state.Put("localDir", localDir) state.Put("driver", driver) + state.Put("exportPath", exportOutputPath) state.Put("hook", hook) state.Put("ui", ui) @@ -334,7 +334,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(localDir.dir) + dir.SetOutputDir(exportOutputPath) files, err = dir.ListFiles() } else { files, err = state.Get("dir").(OutputDir).ListFiles() diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index 67a12a570..04c526aae 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -60,8 +60,7 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { } // Export the VM - localDir := state.Get("localDir").(localOutputDir) - outputPath := localDir.dir + outputPath := state.Get("exportPath").(string) if s.Format == "ova" { os.MkdirAll(outputPath, 0755) @@ -81,8 +80,6 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { ui.Message(fmt.Sprintf("%s", out.String())) - state.Put("exportPath", outputPath) - return multistep.ActionContinue } From 1e9b0f7b8f7bdff29c10b8c0934aa904999b2561 Mon Sep 17 00:00:00 2001 From: Jimmy The Dog Date: Fri, 10 Mar 2017 10:43:45 +0000 Subject: [PATCH 07/22] Replace export output dir in state bag with params step_export now has the OutputDir as a param instead of getting it from the state bag, on the advice of @mwhooker in PR comment --- builder/vmware/iso/builder.go | 2 +- builder/vmware/iso/step_export.go | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/vmware/iso/builder.go b/builder/vmware/iso/builder.go index b01cb1f32..9a0738842 100644 --- a/builder/vmware/iso/builder.go +++ b/builder/vmware/iso/builder.go @@ -215,7 +215,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("debug", b.config.PackerDebug) state.Put("dir", dir) state.Put("driver", driver) - state.Put("exportPath", exportOutputPath) state.Put("hook", hook) state.Put("ui", ui) @@ -309,6 +308,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, }, } diff --git a/builder/vmware/iso/step_export.go b/builder/vmware/iso/step_export.go index 04c526aae..d8c91f82c 100644 --- a/builder/vmware/iso/step_export.go +++ b/builder/vmware/iso/step_export.go @@ -16,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 = "****" @@ -28,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...) } @@ -60,16 +61,18 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { } // Export the VM - outputPath := state.Get("exportPath").(string) + 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()) From fc964bfab28ac182463576adb8fb6777506ccde7 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 16 Jul 2017 08:26:48 +0200 Subject: [PATCH 08/22] cloudstack: Setup temporary SSH keypair --- builder/cloudstack/builder.go | 10 ++ builder/cloudstack/config.go | 10 ++ builder/cloudstack/step_create_instance.go | 4 +- builder/cloudstack/step_keypair.go | 141 ++++++++++++++++++ builder/cloudstack/step_prepare_config.go | 9 -- .../source/docs/builders/cloudstack.html.md | 4 + 6 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 builder/cloudstack/step_keypair.go diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index 369fe0cc6..34dd67dbb 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -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,6 +63,14 @@ 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), + SSHAgentAuth: b.config.Comm.SSHAgentAuth, + TemporaryKeyPair: b.config.TemporaryKeypair, + KeyPair: b.config.Keypair, + PrivateKeyFile: b.config.Comm.SSHPrivateKey, + }, &stepCreateInstance{ Ctx: b.config.ctx, }, diff --git a/builder/cloudstack/config.go b/builder/cloudstack/config.go index 5d6e9da0a..8571a0e03 100644 --- a/builder/cloudstack/config.go +++ b/builder/cloudstack/config.go @@ -34,6 +34,7 @@ type Config struct { Hypervisor string `mapstructure:"hypervisor"` InstanceName string `mapstructure:"instance_name"` Keypair string `mapstructure:"keypair"` + TemporaryKeypair string `mapstructure:"temporary_keypair"` Network string `mapstructure:"network"` Project string `mapstructure:"project"` PublicIPAddress string `mapstructure:"public_ip_address"` @@ -120,6 +121,15 @@ func NewConfig(raws ...interface{}) (*Config, error) { c.TemplateDisplayText = c.TemplateName } + // If we are not given an explicit keypair or ssh_private_key_file, then create + // a temporary one, but only if the temporary_keypair has not been provided and + // we are not using ssh_password. + if c.Keypair == "" && c.TemporaryKeypair == "" && + c.Comm.SSHPrivateKey == "" && c.Comm.SSHPassword == "" { + + c.TemporaryKeypair = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) + } + // Process required parameters. if c.APIURL == "" { errs = packer.MultiErrorAppend(errs, errors.New("a api_url must be specified")) diff --git a/builder/cloudstack/step_create_instance.go b/builder/cloudstack/step_create_instance.go index 3b624de3c..1e3dbae99 100644 --- a/builder/cloudstack/step_create_instance.go +++ b/builder/cloudstack/step_create_instance.go @@ -44,8 +44,8 @@ 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 we use an ISO, configure the disk offering. diff --git a/builder/cloudstack/step_keypair.go b/builder/cloudstack/step_keypair.go new file mode 100644 index 000000000..41e180fc0 --- /dev/null +++ b/builder/cloudstack/step_keypair.go @@ -0,0 +1,141 @@ +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 + SSHAgentAuth bool + DebugKeyPath string + TemporaryKeyPair string + KeyPair string + PrivateKeyFile string + + doCleanup bool +} + +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.TemporaryKeyPair == "" { + ui.Say("Not using temporary keypair") + state.Put("keypair", "") + return multistep.ActionContinue + } + + client := state.Get("client").(*cloudstack.CloudStackClient) + + ui.Say(fmt.Sprintf("Creating temporary keypair: %s ...", s.TemporaryKeyPair)) + p := client.SSH.NewCreateSSHKeyPairParams( + s.TemporaryKeyPair, + ) + + 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.TemporaryKeyPair)) + + // 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 + } + } + } + + // we created a temporary key, so remember to clean it up + s.doCleanup = true + + // Set some state data for use in future steps + state.Put("keypair", s.TemporaryKeyPair) + state.Put("privateKey", keypair.Privatekey) + + return multistep.ActionContinue +} + +func (s *stepKeypair) Cleanup(state multistep.StateBag) { + if !s.doCleanup { + return + } + + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(*cloudstack.CloudStackClient) + + ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.TemporaryKeyPair)) + + _, err := client.SSH.DeleteSSHKeyPair(client.SSH.NewDeleteSSHKeyPairParams( + s.TemporaryKeyPair, + )) + if err != nil { + ui.Error(err.Error()) + ui.Error(fmt.Sprintf( + "Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPair)) + } +} diff --git a/builder/cloudstack/step_prepare_config.go b/builder/cloudstack/step_prepare_config.go index de397308e..af28a76a8 100644 --- a/builder/cloudstack/step_prepare_config.go +++ b/builder/cloudstack/step_prepare_config.go @@ -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) diff --git a/website/source/docs/builders/cloudstack.html.md b/website/source/docs/builders/cloudstack.html.md index 9b6bc2cab..a3f091315 100644 --- a/website/source/docs/builders/cloudstack.html.md +++ b/website/source/docs/builders/cloudstack.html.md @@ -149,6 +149,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_`, where <UUID> 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. From 89dcc93f1c3c9eb8a902d35a5b3b806332f6b09f Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Tue, 18 Jul 2017 21:05:27 +0200 Subject: [PATCH 09/22] cloudstack: Print instance password if debug mode --- builder/cloudstack/builder.go | 3 ++- builder/cloudstack/step_create_instance.go | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index 34dd67dbb..528e34a01 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -72,7 +72,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe PrivateKeyFile: b.config.Comm.SSHPrivateKey, }, &stepCreateInstance{ - Ctx: b.config.ctx, + Debug: b.config.PackerDebug, + Ctx: b.config.ctx, }, &stepSetupNetworking{}, &communicator.StepConnect{ diff --git a/builder/cloudstack/step_create_instance.go b/builder/cloudstack/step_create_instance.go index 1e3dbae99..894d0394e 100644 --- a/builder/cloudstack/step_create_instance.go +++ b/builder/cloudstack/step_create_instance.go @@ -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. @@ -115,6 +116,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": From 26cd27dc7cb7ea2dd8bb3d3afbaf033ae4da385c Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Wed, 26 Jul 2017 21:34:11 +0200 Subject: [PATCH 10/22] cloudstack: Updated after review --- builder/cloudstack/builder.go | 14 ++++----- builder/cloudstack/config.go | 47 +++++++++++++++--------------- builder/cloudstack/step_keypair.go | 42 +++++++++++--------------- 3 files changed, 47 insertions(+), 56 deletions(-) diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index 528e34a01..c4ad8a5b2 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -64,16 +64,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe HTTPPortMax: b.config.HTTPPortMax, }, &stepKeypair{ - Debug: b.config.PackerDebug, - DebugKeyPath: fmt.Sprintf("cs_%s.pem", b.config.PackerBuildName), - SSHAgentAuth: b.config.Comm.SSHAgentAuth, - TemporaryKeyPair: b.config.TemporaryKeypair, - KeyPair: b.config.Keypair, - PrivateKeyFile: b.config.Comm.SSHPrivateKey, + 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, }, &stepCreateInstance{ - Debug: b.config.PackerDebug, Ctx: b.config.ctx, + Debug: b.config.PackerDebug, }, &stepSetupNetworking{}, &communicator.StepConnect{ diff --git a/builder/cloudstack/config.go b/builder/cloudstack/config.go index 8571a0e03..513ad8eed 100644 --- a/builder/cloudstack/config.go +++ b/builder/cloudstack/config.go @@ -27,24 +27,24 @@ 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"` - TemporaryKeypair string `mapstructure:"temporary_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"` + 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"` + TemporaryKeypairName string `mapstructure:"temporary_keypair_name"` + 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"` TemplateName string `mapstructure:"template_name"` TemplateDisplayText string `mapstructure:"template_display_text"` @@ -121,13 +121,12 @@ func NewConfig(raws ...interface{}) (*Config, error) { c.TemplateDisplayText = c.TemplateName } - // If we are not given an explicit keypair or ssh_private_key_file, then create - // a temporary one, but only if the temporary_keypair has not been provided and - // we are not using ssh_password. - if c.Keypair == "" && c.TemporaryKeypair == "" && + // 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.TemporaryKeypair = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) + c.TemporaryKeypairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) } // Process required parameters. diff --git a/builder/cloudstack/step_keypair.go b/builder/cloudstack/step_keypair.go index 41e180fc0..675994fc1 100644 --- a/builder/cloudstack/step_keypair.go +++ b/builder/cloudstack/step_keypair.go @@ -12,14 +12,12 @@ import ( ) type stepKeypair struct { - Debug bool - SSHAgentAuth bool - DebugKeyPath string - TemporaryKeyPair string - KeyPair string - PrivateKeyFile string - - doCleanup bool + Debug bool + DebugKeyPath string + KeyPair string + PrivateKeyFile string + SSHAgentAuth bool + TemporaryKeyPairName string } func (s *stepKeypair) Run(state multistep.StateBag) multistep.StepAction { @@ -50,19 +48,17 @@ func (s *stepKeypair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } - if s.TemporaryKeyPair == "" { - ui.Say("Not using temporary keypair") + 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.TemporaryKeyPair)) - p := client.SSH.NewCreateSSHKeyPairParams( - s.TemporaryKeyPair, - ) + 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) @@ -78,10 +74,9 @@ func (s *stepKeypair) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - ui.Say(fmt.Sprintf("Created temporary keypair: %s", s.TemporaryKeyPair)) + 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 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) @@ -110,32 +105,29 @@ func (s *stepKeypair) Run(state multistep.StateBag) multistep.StepAction { } } - // we created a temporary key, so remember to clean it up - s.doCleanup = true - // Set some state data for use in future steps - state.Put("keypair", s.TemporaryKeyPair) + state.Put("keypair", s.TemporaryKeyPairName) state.Put("privateKey", keypair.Privatekey) return multistep.ActionContinue } func (s *stepKeypair) Cleanup(state multistep.StateBag) { - if !s.doCleanup { + 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.TemporaryKeyPair)) + ui.Say(fmt.Sprintf("Deleting temporary keypair: %s ...", s.TemporaryKeyPairName)) _, err := client.SSH.DeleteSSHKeyPair(client.SSH.NewDeleteSSHKeyPairParams( - s.TemporaryKeyPair, + s.TemporaryKeyPairName, )) if err != nil { ui.Error(err.Error()) ui.Error(fmt.Sprintf( - "Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPair)) + "Error cleaning up keypair. Please delete the key manually: %s", s.TemporaryKeyPairName)) } } From 1c592f291e39fc5f683b3ea9985882316bcfe9ee Mon Sep 17 00:00:00 2001 From: c22 Date: Fri, 13 Jan 2017 11:45:18 +1100 Subject: [PATCH 11/22] Better Windows support in puppet-server Reworking the puppet-server provisioner based on chef-client. --- provisioner/puppet-server/provisioner.go | 104 +++++++++++++----- .../docs/provisioners/puppet-server.html.md | 30 ++--- 2 files changed, 90 insertions(+), 44 deletions(-) diff --git a/provisioner/puppet-server/provisioner.go b/provisioner/puppet-server/provisioner.go index 9e5e6a498..0c8babab1 100644 --- a/provisioner/puppet-server/provisioner.go +++ b/provisioner/puppet-server/provisioner.go @@ -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 @@ -7,12 +7,48 @@ import ( "os" "strings" - "github.com/hashicorp/packer/common" - "github.com/hashicorp/packer/helper/config" - "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/template/interpolate" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/provisioner" + "github.com/mitchellh/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" -} diff --git a/website/source/docs/provisioners/puppet-server.html.md b/website/source/docs/provisioners/puppet-server.html.md index 61a06d228..b81d49088 100644 --- a/website/source/docs/provisioners/puppet-server.html.md +++ b/website/source/docs/provisioners/puppet-server.html.md @@ -69,12 +69,13 @@ listed below: - `puppet_server` (string) - Hostname of the Puppet server. By default "puppet" will be used. -- `staging_dir` (string) - This is the directory where all the - configuration of Puppet by Packer will be placed. By default this - is /tmp/packer-puppet-server. 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_dir` (string) - This is the directory where all the configuration + of Puppet by Packer will be placed. By default this is "/tmp/packer-puppet-server" + when guest_os_type unix and "C:/Windows/Temp/packer-puppet-server" 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. - `puppet_bin_dir` (string) - The path to the directory that contains the puppet binary for running `puppet agent`. Usually, this would be found via the `$PATH` @@ -83,19 +84,12 @@ listed below: - `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: + variables](/docs/templates/configuration-templates.html) available. See + below for more information. -``` 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 -``` +- `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". ## Default Facts From bcd30ad2f22e204bf96347f2872f46753e281e9b Mon Sep 17 00:00:00 2001 From: Sam Kerr Date: Mon, 2 May 2016 11:17:04 -0400 Subject: [PATCH 12/22] Update puppet-masterless commands to be OS specific Previous implementation hardcoded "mkdir -p" which is fine for Unix, but fails on Windows. This change draws on the example in the chef-solo provisioner on how to detect the OS in use and use an appropriate mkdir command. In addition to updating the mkdir command, the actual executeCommand needs to be OS specific, since Windows doesn't have sudo and Unix doesn't require 'SET' when trying to change the value of a variable. Modify the actual Windows command used to run Puppet. Since the Facter vars on Windows are set with 'SET =', a '&&' is needed between the SET commands and the actual Puppet invocation. --- provisioner/puppet-masterless/provisioner.go | 104 +++++++++++++++---- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 6f1b6eee4..f3fd7cd18 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -9,10 +9,11 @@ import ( "path/filepath" "strings" - "github.com/hashicorp/packer/common" - "github.com/hashicorp/packer/helper/config" - "github.com/hashicorp/packer/packer" - "github.com/hashicorp/packer/template/interpolate" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/provisioner" + "github.com/mitchellh/packer/template/interpolate" ) type Config struct { @@ -61,10 +62,45 @@ type Config struct { // If true, packer will ignore all exit-codes from a puppet run IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"` + + GuestOSType string `mapstructure:"guest_os_type"` + ConfigTemplate string `mapstructure:"config_template"` +} + +type guestOSTypeConfig struct { + stagingDir string + executeCommand string +} + +var guestOSTypeConfigs = map[string]guestOSTypeConfig{ + provisioner.UnixOSType: guestOSTypeConfig{ + 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}}", + }, + provisioner.WindowsOSType: guestOSTypeConfig{ + 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}}", + }, } type Provisioner struct { - config Config + config Config + guestOSTypeConfig guestOSTypeConfig + guestCommands *provisioner.GuestCommands } type ExecuteTemplate struct { @@ -95,15 +131,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { // Set some defaults 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.StagingDir == "" { @@ -166,6 +194,37 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } + 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.guestOSTypeConfig.executeCommand + } + + if p.config.ConfigTemplate != "" { + fi, err := os.Stat(p.config.ConfigTemplate) + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad config template path: %s", err)) + } else if fi.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Config template path must be a file: %s", err)) + } + } + if errs != nil && len(errs.Errors) > 0 { return errs } @@ -227,8 +286,15 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } // Execute Puppet + var facterVarsString string + if p.config.GuestOSType == provisioner.UnixOSType { + facterVarsString = strings.Join(facterVars, " ") + } else { + facterVarsString = "SET " + strings.Join(facterVars, " && SET ") + } + p.config.ctx.Data = &ExecuteTemplate{ - FacterVars: strings.Join(facterVars, " "), + FacterVars: facterVarsString, HieraConfigPath: remoteHieraConfigPath, ManifestDir: remoteManifestDir, ManifestFile: remoteManifestFile, @@ -249,7 +315,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 { @@ -335,9 +401,9 @@ func (p *Provisioner) uploadManifests(ui packer.Ui, comm packer.Communicator) (s } 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 From fbac46af91c36041d8628cfa06884f4f5dda12a7 Mon Sep 17 00:00:00 2001 From: c22 Date: Fri, 13 Jan 2017 18:51:36 +1100 Subject: [PATCH 13/22] Linting + formatting --- provisioner/puppet-masterless/provisioner.go | 35 ++++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index f3fd7cd18..bdc41e435 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -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 @@ -380,24 +380,23 @@ 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 { From 80ba99c04f26c8220c53c4430138c9e58934dc41 Mon Sep 17 00:00:00 2001 From: c22 Date: Fri, 13 Jan 2017 19:11:18 +1100 Subject: [PATCH 14/22] Update documentation + small fixes Updated the puppet-masterless documentation Removed extraneous ConfigTemplate code --- provisioner/puppet-masterless/provisioner.go | 19 ++----- .../provisioners/puppet-masterless.html.md | 50 ++++++++++++------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index bdc41e435..e64e5f7dd 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -63,8 +63,8 @@ type Config struct { // If true, packer will ignore all exit-codes from a puppet run IgnoreExitCodes bool `mapstructure:"ignore_exit_codes"` - GuestOSType string `mapstructure:"guest_os_type"` - ConfigTemplate string `mapstructure:"config_template"` + // The Guest OS Type (unix or windows) + GuestOSType string `mapstructure:"guest_os_type"` } type guestOSTypeConfig struct { @@ -73,7 +73,7 @@ type guestOSTypeConfig struct { } var guestOSTypeConfigs = map[string]guestOSTypeConfig{ - provisioner.UnixOSType: guestOSTypeConfig{ + provisioner.UnixOSType: { stagingDir: "/tmp/packer-puppet-masterless", executeCommand: "cd {{.WorkingDir}} && " + "{{.FacterVars}} {{if .Sudo}} sudo -E {{end}}" + @@ -84,7 +84,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{ "{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" + "{{.ManifestFile}}", }, - provisioner.WindowsOSType: guestOSTypeConfig{ + provisioner.WindowsOSType: { stagingDir: "C:/Windows/Temp/packer-puppet-masterless", executeCommand: "cd {{.WorkingDir}} && " + "{{.FacterVars}} && " + @@ -214,17 +214,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand } - if p.config.ConfigTemplate != "" { - fi, err := os.Stat(p.config.ConfigTemplate) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Bad config template path: %s", err)) - } else if fi.IsDir() { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Config template path must be a file: %s", err)) - } - } - if errs != nil && len(errs.Errors) > 0 { return errs } diff --git a/website/source/docs/provisioners/puppet-masterless.html.md b/website/source/docs/provisioners/puppet-masterless.html.md index c630f8412..4d697ecce 100644 --- a/website/source/docs/provisioners/puppet-masterless.html.md +++ b/website/source/docs/provisioners/puppet-masterless.html.md @@ -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 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 From bc2e3de06dd959845f69270bc22894b71bb2a851 Mon Sep 17 00:00:00 2001 From: c22 Date: Tue, 15 Aug 2017 13:01:24 +1000 Subject: [PATCH 15/22] Resolve merge conflicts and update documentation. --- provisioner/puppet-masterless/provisioner.go | 10 ++-- provisioner/puppet-server/provisioner.go | 10 ++-- .../provisioners/puppet-masterless.html.md | 4 +- .../docs/provisioners/puppet-server.html.md | 48 ++++++++++++++----- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index e64e5f7dd..1f2c5bb96 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -9,11 +9,11 @@ import ( "path/filepath" "strings" - "github.com/mitchellh/packer/common" - "github.com/mitchellh/packer/helper/config" - "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/provisioner" - "github.com/mitchellh/packer/template/interpolate" + "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 Config struct { diff --git a/provisioner/puppet-server/provisioner.go b/provisioner/puppet-server/provisioner.go index 0c8babab1..4da3ecbc6 100644 --- a/provisioner/puppet-server/provisioner.go +++ b/provisioner/puppet-server/provisioner.go @@ -7,11 +7,11 @@ import ( "os" "strings" - "github.com/mitchellh/packer/common" - "github.com/mitchellh/packer/helper/config" - "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/provisioner" - "github.com/mitchellh/packer/template/interpolate" + "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 { diff --git a/website/source/docs/provisioners/puppet-masterless.html.md b/website/source/docs/provisioners/puppet-masterless.html.md index 4d697ecce..b58d78251 100644 --- a/website/source/docs/provisioners/puppet-masterless.html.md +++ b/website/source/docs/provisioners/puppet-masterless.html.md @@ -105,7 +105,7 @@ multiple manifests you should use `manifest_file` instead. - `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 unix and "C:/Windows/Temp/packer-puppet-masterless" when windows. + 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 @@ -133,7 +133,7 @@ puppet apply --verbose --modulepath='{{.ModulePath}}' {{.ManifestFile}} ``` -The following command is used if guest_os_type is windows: +The following command is used if guest OS type is windows: ``` cd {{.WorkingDir}} && diff --git a/website/source/docs/provisioners/puppet-server.html.md b/website/source/docs/provisioners/puppet-server.html.md index b81d49088..6adfd6b0b 100644 --- a/website/source/docs/provisioners/puppet-server.html.md +++ b/website/source/docs/provisioners/puppet-server.html.md @@ -69,28 +69,52 @@ listed below: - `puppet_server` (string) - Hostname of the Puppet server. By default "puppet" will be used. -- `staging_dir` (string) - This is the directory where all the configuration - of Puppet by Packer will be placed. By default this is "/tmp/packer-puppet-server" - when guest_os_type unix and "C:/Windows/Temp/packer-puppet-server" 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. +- `staging_dir` (string) - This is the directory where all the + configuration of Puppet by Packer will be placed. By default this + is /tmp/packer-puppet-server. 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. - `puppet_bin_dir` (string) - The path to the directory that contains the puppet binary for running `puppet agent`. Usually, this would be found via the `$PATH` 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/configuration-templates.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". +- `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 In addition to being able to specify custom Facter facts using the `facter` From 42f1aa7a95684750284f94315a036d0ca93186c9 Mon Sep 17 00:00:00 2001 From: c22 Date: Mon, 21 Aug 2017 14:28:58 +1000 Subject: [PATCH 16/22] Refactor puppet-masterless based on puppet-server --- provisioner/puppet-masterless/provisioner.go | 65 ++++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/provisioner/puppet-masterless/provisioner.go b/provisioner/puppet-masterless/provisioner.go index 1f2c5bb96..1c4ad3bc1 100644 --- a/provisioner/puppet-masterless/provisioner.go +++ b/provisioner/puppet-masterless/provisioner.go @@ -68,8 +68,10 @@ type Config struct { } type guestOSTypeConfig struct { - stagingDir string - executeCommand string + stagingDir string + executeCommand string + facterVarsFmt string + modulePathJoiner string } var guestOSTypeConfigs = map[string]guestOSTypeConfig{ @@ -83,6 +85,8 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{ "--detailed-exitcodes " + "{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" + "{{.ManifestFile}}", + facterVarsFmt: "FACTER_%s='%s'", + modulePathJoiner: ":", }, provisioner.WindowsOSType: { stagingDir: "C:/Windows/Temp/packer-puppet-masterless", @@ -94,6 +98,8 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{ "--detailed-exitcodes " + "{{if ne .ExtraArguments \"\"}}{{.ExtraArguments}} {{end}}" + "{{.ManifestFile}}", + facterVarsFmt: "SET \"FACTER_%s=%s\" &", + modulePathJoiner: ";", }, } @@ -130,12 +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 = 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 == "" { @@ -194,26 +220,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } - 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.guestOSTypeConfig.executeCommand - } - if errs != nil && len(errs.Errors) > 0 { return errs } @@ -271,23 +277,16 @@ 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 - var facterVarsString string - if p.config.GuestOSType == provisioner.UnixOSType { - facterVarsString = strings.Join(facterVars, " ") - } else { - facterVarsString = "SET " + strings.Join(facterVars, " && SET ") - } - p.config.ctx.Data = &ExecuteTemplate{ - FacterVars: facterVarsString, + FacterVars: strings.Join(facterVars, " "), 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, From e45223a86757573070ded161dca08a7e56ad5539 Mon Sep 17 00:00:00 2001 From: Vijaya Bhaskar Reddy Kondreddi Date: Thu, 31 Aug 2017 23:13:00 +0530 Subject: [PATCH 17/22] Fix -on-error issue not working. When we supply -on-error flag for Packer with Hyper-v builder, it does not do anything. --- builder/cloudstack/builder.go | 13 ++----------- builder/hyperv/iso/builder.go | 12 +----------- builder/oneandone/builder.go | 10 +--------- builder/profitbricks/builder.go | 10 +--------- .../googlecompute-export/post-processor.go | 9 +-------- post-processor/vagrant-cloud/post-processor.go | 10 +--------- 6 files changed, 7 insertions(+), 57 deletions(-) diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index c4ad8a5b2..8bd6dfed2 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -89,17 +89,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 diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 657c07886..9b4065d8c 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -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. diff --git a/builder/oneandone/builder.go b/builder/oneandone/builder.go index f4f8e1dc3..ad470c152 100644 --- a/builder/oneandone/builder.go +++ b/builder/oneandone/builder.go @@ -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 { diff --git a/builder/profitbricks/builder.go b/builder/profitbricks/builder.go index 95a2b1b2d..750b843c0 100644 --- a/builder/profitbricks/builder.go +++ b/builder/profitbricks/builder.go @@ -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 { diff --git a/post-processor/googlecompute-export/post-processor.go b/post-processor/googlecompute-export/post-processor.go index 9fd39aa46..8a5befbc8 100644 --- a/post-processor/googlecompute-export/post-processor.go +++ b/post-processor/googlecompute-export/post-processor.go @@ -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) } diff --git a/post-processor/vagrant-cloud/post-processor.go b/post-processor/vagrant-cloud/post-processor.go index 01575def7..b8eeb7105 100644 --- a/post-processor/vagrant-cloud/post-processor.go +++ b/post-processor/vagrant-cloud/post-processor.go @@ -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 From e622a8788ac6824b69af677ce1b417acf68ada44 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 31 Aug 2017 11:53:01 -0700 Subject: [PATCH 18/22] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f776a04c..149ef3e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * 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] ### BACKWARDS INCOMPATIBILITIES: From 1a1ab3863c9a7a2bd8b419524be7487892c79a9b Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Tue, 18 Jul 2017 09:08:11 +0200 Subject: [PATCH 19/22] cloudstack: Add support for Security Groups Adds two new options: - `create_security_group` which automatically creates a temporary SG. - `security_groups` which takes a list of SGs to attach to the instance. --- builder/cloudstack/builder.go | 1 + builder/cloudstack/config.go | 14 ++- builder/cloudstack/step_create_instance.go | 4 + .../cloudstack/step_create_security_group.go | 94 +++++++++++++++++++ builder/cloudstack/step_prepare_config.go | 12 +++ .../source/docs/builders/cloudstack.html.md | 8 ++ 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 builder/cloudstack/step_create_security_group.go diff --git a/builder/cloudstack/builder.go b/builder/cloudstack/builder.go index 8bd6dfed2..e46e56086 100644 --- a/builder/cloudstack/builder.go +++ b/builder/cloudstack/builder.go @@ -71,6 +71,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SSHAgentAuth: b.config.Comm.SSHAgentAuth, TemporaryKeyPairName: b.config.TemporaryKeypairName, }, + &stepCreateSecurityGroup{}, &stepCreateInstance{ Ctx: b.config.ctx, Debug: b.config.PackerDebug, diff --git a/builder/cloudstack/config.go b/builder/cloudstack/config.go index 513ad8eed..35026f6e3 100644 --- a/builder/cloudstack/config.go +++ b/builder/cloudstack/config.go @@ -28,22 +28,24 @@ type Config struct { SSLNoVerify bool `mapstructure:"ssl_no_verify"` 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"` - TemporaryKeypairName string `mapstructure:"temporary_keypair_name"` Network string `mapstructure:"network"` Project string `mapstructure:"project"` PublicIPAddress string `mapstructure:"public_ip_address"` + SecurityGroups []string `mapstructure:"security_groups"` ServiceOffering string `mapstructure:"service_offering"` - SourceTemplate string `mapstructure:"source_template"` 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"` - UseLocalIPAddress bool `mapstructure:"use_local_ip_address"` Zone string `mapstructure:"zone"` TemplateName string `mapstructure:"template_name"` @@ -99,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"} } @@ -146,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")) } diff --git a/builder/cloudstack/step_create_instance.go b/builder/cloudstack/step_create_instance.go index 894d0394e..538a062b9 100644 --- a/builder/cloudstack/step_create_instance.go +++ b/builder/cloudstack/step_create_instance.go @@ -49,6 +49,10 @@ func (s *stepCreateInstance) Run(state multistep.StateBag) multistep.StepAction 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. if config.SourceISO != "" { p.SetDiskofferingid(config.DiskOffering) diff --git a/builder/cloudstack/step_create_security_group.go b/builder/cloudstack/step_create_security_group.go new file mode 100644 index 000000000..4bab0d4c7 --- /dev/null +++ b/builder/cloudstack/step_create_security_group.go @@ -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) + if config.Project != "" { + i.SetProjectid(config.Project) + } + i.SetProtocol("TCP") + i.SetSecuritygroupid(sg.Id) + i.SetStartport(config.Comm.Port()) + i.SetEndport(config.Comm.Port()) + + _, 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)) + } +} diff --git a/builder/cloudstack/step_prepare_config.go b/builder/cloudstack/step_prepare_config.go index af28a76a8..6cfb5c478 100644 --- a/builder/cloudstack/step_prepare_config.go +++ b/builder/cloudstack/step_prepare_config.go @@ -83,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 { diff --git a/website/source/docs/builders/cloudstack.html.md b/website/source/docs/builders/cloudstack.html.md index a3f091315..7e59fe718 100644 --- a/website/source/docs/builders/cloudstack.html.md +++ b/website/source/docs/builders/cloudstack.html.md @@ -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 From cad3978e6ae0a64ba052d1bbb96ab61111f60e67 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Wed, 26 Jul 2017 21:11:01 +0200 Subject: [PATCH 20/22] cloudstack: Updated after review --- builder/cloudstack/step_create_security_group.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/cloudstack/step_create_security_group.go b/builder/cloudstack/step_create_security_group.go index 4bab0d4c7..1bf23100b 100644 --- a/builder/cloudstack/step_create_security_group.go +++ b/builder/cloudstack/step_create_security_group.go @@ -51,13 +51,13 @@ func (s *stepCreateSecurityGroup) Run(state multistep.StateBag) multistep.StepAc // Create Ingress rule i := client.SecurityGroup.NewAuthorizeSecurityGroupIngressParams() i.SetCidrlist(config.CIDRList) - if config.Project != "" { - i.SetProjectid(config.Project) - } 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 { From 2f3c73b16014927790592baff5de29d62281d222 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 31 Aug 2017 12:05:36 -0700 Subject: [PATCH 21/22] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149ef3e19..cf10c3fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * 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] ### BACKWARDS INCOMPATIBILITIES: From 0047de9b96db0845e01aab46d4d5cad0c5e930ae Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Thu, 31 Aug 2017 13:47:28 -0700 Subject: [PATCH 22/22] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf10c3fec..8402e7157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * 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: