From cfece501d09c94f979a567ce3b082624b94b2cc8 Mon Sep 17 00:00:00 2001 From: Jeff Wong Date: Wed, 9 Sep 2020 21:46:57 -0700 Subject: [PATCH] Implement proxmox-clone --- builder/proxmox/clone/builder.go | 49 +++++++++- builder/proxmox/clone/config.go | 1 + builder/proxmox/clone/config.hcl2spec.go | 2 + builder/proxmox/clone/step_ssh_key_pair.go | 107 +++++++++++++++++++++ builder/proxmox/common/builder.go | 11 ++- 5 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 builder/proxmox/clone/step_ssh_key_pair.go diff --git a/builder/proxmox/clone/builder.go b/builder/proxmox/clone/builder.go index c56db14d4..c9e935686 100644 --- a/builder/proxmox/clone/builder.go +++ b/builder/proxmox/clone/builder.go @@ -2,10 +2,14 @@ package proxmoxclone import ( "context" + "time" + "fmt" + proxmoxapi "github.com/Telmate/proxmox-api-go/proxmox" "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/proxmox/common" "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/packer" ) @@ -30,10 +34,51 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { state := new(multistep.BasicStateBag) state.Put("clone-config", &b.config) + state.Put("vm-creator", &cloneVMCreator{}) + state.Put("comm", &b.config.Comm) - steps := []multistep.Step{} + preSteps := []multistep.Step{ + &StepSshKeyPair{ + Debug: b.config.PackerDebug, + DebugKeyPath: fmt.Sprintf("%s.pem", b.config.PackerBuildName), + Comm: &b.config.Comm, + }, + } postSteps := []multistep.Step{} - sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, steps, postSteps) + sb := proxmox.NewSharedBuilder(BuilderID, b.config.Config, preSteps, postSteps) return sb.Run(ctx, ui, hook, state) } + +type cloneVMCreator struct{} + +var _ proxmox.ProxmoxVMCreator = &cloneVMCreator{} + +func (*cloneVMCreator) Create(vmRef *proxmoxapi.VmRef, config proxmoxapi.ConfigQemu, state multistep.StateBag) error { + client := state.Get("proxmoxClient").(*proxmoxapi.Client) + c := state.Get("clone-config").(*Config) + comm := state.Get("comm").(*communicator.Config) + + fullClone := 1 + if c.FullClone { + fullClone = 0 + } + + config.FullClone = &fullClone + config.CIuser = comm.SSHUsername + config.Sshkeys = string(comm.SSHPublicKey) + sourceVmr, err := client.GetVmRefByName(c.CloneVM) + if err != nil { + return err + } + err = config.CloneVm(sourceVmr, vmRef, client) + if err != nil { + return err + } + err = config.UpdateConfig(vmRef, client) + if err != nil { + return err + } + time.Sleep(time.Duration(15) * time.Second) + return nil +} diff --git a/builder/proxmox/clone/config.go b/builder/proxmox/clone/config.go index bede7db14..d481d3c39 100644 --- a/builder/proxmox/clone/config.go +++ b/builder/proxmox/clone/config.go @@ -11,6 +11,7 @@ type Config struct { proxmox.Config `mapstructure:",squash"` CloneVM string `mapstructure:"clone_vm"` + FullClone bool `mapstructure:"full_clone"` } func (c *Config) Prepare(raws ...interface{}) ([]string, []string, error) { diff --git a/builder/proxmox/clone/config.hcl2spec.go b/builder/proxmox/clone/config.hcl2spec.go index cc5a87d09..99133765a 100644 --- a/builder/proxmox/clone/config.hcl2spec.go +++ b/builder/proxmox/clone/config.hcl2spec.go @@ -105,6 +105,7 @@ type FlatConfig struct { BuildType *string `cty:"build_type" hcl:"build_type"` TemplatePath *string `cty:"template_path" hcl:"template_path"` CloneVM *string `mapstructure:"clone_vm" cty:"clone_vm" hcl:"clone_vm"` + FullClone *bool `mapstructure:"full_clone" cty:"full_clone" hcl:"full_clone"` } // FlatMapstructure returns a new FlatConfig. @@ -214,6 +215,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "build_type": &hcldec.AttrSpec{Name: "build_type", Type: cty.String, Required: false}, "template_path": &hcldec.AttrSpec{Name: "template_path", Type: cty.String, Required: false}, "clone_vm": &hcldec.AttrSpec{Name: "clone_vm", Type: cty.String, Required: false}, + "full_clone": &hcldec.AttrSpec{Name: "full_clone", Type: cty.Bool, Required: false}, } return s } diff --git a/builder/proxmox/clone/step_ssh_key_pair.go b/builder/proxmox/clone/step_ssh_key_pair.go new file mode 100644 index 000000000..96e97d5a8 --- /dev/null +++ b/builder/proxmox/clone/step_ssh_key_pair.go @@ -0,0 +1,107 @@ +package proxmoxclone + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/packer/common/uuid" + "github.com/hashicorp/packer/helper/communicator" + "github.com/hashicorp/packer/helper/multistep" + "github.com/hashicorp/packer/helper/ssh" + "github.com/hashicorp/packer/packer" +) + +// StepSshKeyPair executes the business logic for setting the SSH key pair in +// the specified communicator.Config. +type StepSshKeyPair struct { + Debug bool + DebugKeyPath string + Comm *communicator.Config +} + +func (s *StepSshKeyPair) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.Comm.SSHPassword != "" { + return multistep.ActionContinue + } + + if s.Comm.SSHPrivateKeyFile != "" { + ui.Say("Using existing SSH private key for the communicator...") + privateKeyBytes, err := s.Comm.ReadSSHPrivateKeyFile() + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + kp, err := ssh.KeyPairFromPrivateKey(ssh.FromPrivateKeyConfig{ + RawPrivateKeyPemBlock: privateKeyBytes, + Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + }) + if err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + s.Comm.SSHPrivateKey = privateKeyBytes + s.Comm.SSHKeyPairName = kp.Comment + s.Comm.SSHTemporaryKeyPairName = kp.Comment + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine + + return multistep.ActionContinue + } + + if s.Comm.SSHAgentAuth { + ui.Say("Using local SSH Agent to authenticate connections for the communicator...") + return multistep.ActionContinue + } + + ui.Say("Creating ephemeral key pair for SSH communicator...") + + kp, err := ssh.NewKeyPair(ssh.CreateKeyPairConfig{ + Comment: fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()), + }) + if err != nil { + state.Put("error", fmt.Errorf("Error creating temporary keypair: %s", err)) + return multistep.ActionHalt + } + + s.Comm.SSHKeyPairName = kp.Comment + s.Comm.SSHTemporaryKeyPairName = kp.Comment + s.Comm.SSHPrivateKey = kp.PrivateKeyPemBlock + s.Comm.SSHPublicKey = kp.PublicKeyAuthorizedKeysLine + s.Comm.SSHClearAuthorizedKeys = true + + ui.Say("Created ephemeral SSH key pair for communicator") + + // If we're in debug mode, output the private key to the working + // directory. + if s.Debug { + ui.Message(fmt.Sprintf("Saving communicator private key for debug purposes: %s", s.DebugKeyPath)) + f, err := os.OpenFile(s.DebugKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + 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(kp.PrivateKeyPemBlock); err != nil { + state.Put("error", fmt.Errorf("Error saving debug key: %s", err)) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepSshKeyPair) Cleanup(state multistep.StateBag) { + if s.Debug { + if err := os.Remove(s.DebugKeyPath); err != nil { + ui := state.Get("ui").(packer.Ui) + ui.Error(fmt.Sprintf( + "Error removing debug key '%s': %s", s.DebugKeyPath, err)) + } + } +} diff --git a/builder/proxmox/common/builder.go b/builder/proxmox/common/builder.go index b6742b1ee..83e1cecd3 100644 --- a/builder/proxmox/common/builder.go +++ b/builder/proxmox/common/builder.go @@ -52,6 +52,11 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook, state state.Put("hook", hook) state.Put("ui", ui) + comm := &b.config.Comm + if(state.Get("comm") != nil) { + comm = state.Get("comm").(*communicator.Config) + } + // Build the steps coreSteps := []multistep.Step{ &stepStartVM{}, @@ -66,9 +71,9 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook, state Ctx: b.config.Ctx, }, &communicator.StepConnect{ - Config: &b.config.Comm, - Host: commHost(b.config.Comm.Host()), - SSHConfig: b.config.Comm.SSHConfigFunc(), + Config: comm, + Host: commHost((*comm).Host()), + SSHConfig: (*comm).SSHConfigFunc(), }, &common.StepProvision{}, &common.StepCleanupTempKeys{