Implement proxmox-clone

This commit is contained in:
Jeff Wong 2020-09-09 21:46:57 -07:00
parent 905869308d
commit cfece501d0
5 changed files with 165 additions and 5 deletions

View File

@ -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
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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))
}
}
}

View File

@ -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{