From fc964bfab28ac182463576adb8fb6777506ccde7 Mon Sep 17 00:00:00 2001 From: Rickard von Essen Date: Sun, 16 Jul 2017 08:26:48 +0200 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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)) } }